1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 import sun.awt.ExtendedKeyCodes;
  27 import sun.awt.SunToolkit;
  28 
  29 import java.awt.AWTException;
  30 import java.awt.Robot;
  31 import java.awt.GraphicsDevice;
  32 import java.awt.Toolkit;
  33 import java.awt.Point;
  34 import java.awt.MouseInfo;
  35 import java.awt.event.InputEvent;
  36 
  37 /**
  38  * ExtendedRobot is a subclass of {@link java.awt.Robot}. It provides some convenience methods that are
  39  * ought to be moved to {@link java.awt.Robot} class.
  40  * <p>
  41  * ExtendedRobot uses delay {@link #getSyncDelay()} to make syncing threads with {@link #waitForIdle()}
  42  * more stable. This delay can be set once on creating object and could not be changed throughout object
  43  * lifecycle. Constructor reads vm integer property {@code java.awt.robotdelay} and sets the delay value
  44  * equal to the property value. If the property was not set 500 milliseconds default value is used.
  45  * <p>
  46  * When using jtreg you would include this class via something like:
  47  * <pre>
  48  * {@literal @}library ../../../../lib/testlibrary
  49  * {@literal @}build ExtendedRobot
  50  * </pre>
  51  *
  52  * @author      Dmitriy Ermashov
  53  * @since       1.9
  54  */
  55 
  56 public class ExtendedRobot extends Robot {
  57 
  58     private static int DEFAULT_SPEED = 20;       // Speed for mouse glide and click
  59     private static int DEFAULT_SYNC_DELAY = 500; // Default Additional delay for waitForIdle()
  60     private static int DEFAULT_STEP_LENGTH = 2;  // Step length (in pixels) for mouse glide
  61 
  62     private final int syncDelay = DEFAULT_SYNC_DELAY;
  63 
  64     //TODO: uncomment three lines below after moving functionality to java.awt.Robot
  65     //{
  66     //    syncDelay = AccessController.doPrivileged(new GetIntegerAction("java.awt.robotdelay", DEFAULT_SYNC_DELAY));
  67     //}
  68 
  69     /**
  70      * Constructs an ExtendedRobot object in the coordinate system of the primary screen.
  71      *
  72      * @throws  AWTException if the platform configuration does not allow low-level input
  73      *          control. This exception is always thrown when
  74      *          GraphicsEnvironment.isHeadless() returns true
  75      * @throws  SecurityException if {@code createRobot} permission is not granted
  76      *
  77      * @see     java.awt.GraphicsEnvironment#isHeadless
  78      * @see     SecurityManager#checkPermission
  79      * @see     java.awt.AWTPermission
  80      */
  81     public ExtendedRobot() throws AWTException {
  82         super();
  83     }
  84 
  85     /**
  86      * Creates an ExtendedRobot for the given screen device. Coordinates passed
  87      * to ExtendedRobot method calls like mouseMove and createScreenCapture will
  88      * be interpreted as being in the same coordinate system as the specified screen.
  89      * Note that depending on the platform configuration, multiple screens may either:
  90      * <ul>
  91      * <li>share the same coordinate system to form a combined virtual screen</li>
  92      * <li>use different coordinate systems to act as independent screens</li>
  93      * </ul>
  94      * This constructor is meant for the latter case.
  95      * <p>
  96      * If screen devices are reconfigured such that the coordinate system is
  97      * affected, the behavior of existing ExtendedRobot objects is undefined.
  98      *
  99      * @param   screen  A screen GraphicsDevice indicating the coordinate
 100      *                  system the Robot will operate in.
 101      * @throws  AWTException if the platform configuration does not allow low-level input
 102      *          control. This exception is always thrown when
 103      *          GraphicsEnvironment.isHeadless() returns true.
 104      * @throws  IllegalArgumentException if {@code screen} is not a screen
 105      *          GraphicsDevice.
 106      * @throws  SecurityException if {@code createRobot} permission is not granted
 107      *
 108      * @see     java.awt.GraphicsEnvironment#isHeadless
 109      * @see     GraphicsDevice
 110      * @see     SecurityManager#checkPermission
 111      * @see     java.awt.AWTPermission
 112      */
 113     public ExtendedRobot(GraphicsDevice screen) throws AWTException {
 114         super(screen);
 115     }
 116 
 117     /**
 118      * Returns delay length for {@link #waitForIdle()} method
 119      *
 120      * @return  Current delay value
 121      *
 122      * @see     #waitForIdle()
 123      */
 124     public int getSyncDelay(){ return this.syncDelay; }
 125 
 126     /**
 127      * Clicks mouse button(s) by calling {@link java.awt.Robot#mousePress(int)} and
 128      * {@link java.awt.Robot#mouseRelease(int)} methods
 129      *
 130      *
 131      * @param   buttons The button mask; a combination of one or more mouse button masks.
 132      * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
 133      *          extra mouse button and support for extended mouse buttons is
 134      *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 135      * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
 136      *          extra mouse button that does not exist on the mouse and support for extended
 137      *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
 138      *          by Java
 139      *
 140      * @see     #mousePress(int)
 141      * @see     #mouseRelease(int)
 142      * @see     InputEvent#getMaskForButton(int)
 143      * @see     Toolkit#areExtraMouseButtonsEnabled()
 144      * @see     java.awt.event.MouseEvent
 145      */
 146     public void click(int buttons) {
 147         mousePress(buttons);
 148         waitForIdle(DEFAULT_SPEED);
 149         mouseRelease(buttons);
 150         waitForIdle();
 151     }
 152 
 153     /**
 154      * Clicks mouse button 1
 155      *
 156      * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
 157      *          extra mouse button and support for extended mouse buttons is
 158      *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 159      * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
 160      *          extra mouse button that does not exist on the mouse and support for extended
 161      *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
 162      *          by Java
 163      *
 164      * @see     #click(int)
 165      */
 166     public void click() {
 167         click(InputEvent.BUTTON1_DOWN_MASK);
 168     }
 169 
 170     /**
 171      * Waits until all events currently on the event queue have been processed with given
 172      * delay after syncing threads. It uses more advanced method of synchronizing threads
 173      * unlike {@link java.awt.Robot#waitForIdle()}
 174      *
 175      * @param   delayValue  Additional delay length in milliseconds to wait until thread
 176      *                      sync been completed
 177      * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
 178      *          dispatching thread
 179      */
 180     public synchronized void waitForIdle(int delayValue) {
 181         SunToolkit.flushPendingEvents();
 182         ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
 183         delay(delayValue);
 184     }
 185 
 186     /**
 187      * Waits until all events currently on the event queue have been processed with delay
 188      * {@link #getSyncDelay()} after syncing threads. It uses more advanced method of
 189      * synchronizing threads unlike {@link java.awt.Robot#waitForIdle()}
 190      *
 191      * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
 192      *          dispatching thread
 193      *
 194      * @see     #waitForIdle(int)
 195      */
 196     @Override
 197     public synchronized void waitForIdle() {
 198         waitForIdle(syncDelay);
 199     }
 200 
 201     /**
 202      * Move the mouse in multiple steps from where it is
 203      * now to the destination coordinates.
 204      *
 205      * @param   x   Destination point x coordinate
 206      * @param   y   Destination point y coordinate
 207      *
 208      * @see     #glide(int, int, int, int)
 209      */
 210     public void glide(int x, int y) {
 211         Point p = MouseInfo.getPointerInfo().getLocation();
 212         glide(p.x, p.y, x, y);
 213     }
 214 
 215     /**
 216      * Move the mouse in multiple steps from where it is
 217      * now to the destination point.
 218      *
 219      * @param   dest    Destination point
 220      *
 221      * @see     #glide(int, int)
 222      */
 223     public void glide(Point dest) {
 224         glide(dest.x, dest.y);
 225     }
 226 
 227     /**
 228      * Move the mouse in multiple steps from source coordinates
 229      * to the destination coordinates.
 230      *
 231      * @param   fromX   Source point x coordinate
 232      * @param   fromY   Source point y coordinate
 233      * @param   toX     Destination point x coordinate
 234      * @param   toY     Destination point y coordinate
 235      *
 236      * @see     #glide(int, int, int, int, int, int)
 237      */
 238     public void glide(int fromX, int fromY, int toX, int toY) {
 239         glide(fromX, fromY, toX, toY, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
 240     }
 241 
 242     /**
 243      * Move the mouse in multiple steps from source point to the
 244      * destination point with default speed and step length.
 245      *
 246      * @param   src     Source point
 247      * @param   dest    Destination point
 248      *
 249      * @see     #glide(int, int, int, int, int, int)
 250      */
 251     public void glide(Point src, Point dest) {
 252         glide(src.x, src.y, dest.x, dest.y, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
 253     }
 254 
 255     /**
 256      * Move the mouse in multiple steps from source point to the
 257      * destination point with given speed and step length.
 258      *
 259      * @param   srcX        Source point x cordinate
 260      * @param   srcY        Source point y cordinate
 261      * @param   destX       Destination point x cordinate
 262      * @param   destY       Destination point y cordinate
 263      * @param   stepLength  Approximate length of one step
 264      * @param   speed       Delay between steps.
 265      *
 266      * @see     #mouseMove(int, int)
 267      * @see     #delay(int)
 268      */
 269      public void glide(int srcX, int srcY, int destX, int destY, int stepLength, int speed) {
 270         int stepNum;
 271         double tDx, tDy;
 272         double dx, dy, ds;
 273         double x, y;
 274 
 275         dx = (destX - srcX);
 276         dy = (destY - srcY);
 277         ds = Math.sqrt(dx*dx + dy*dy);
 278 
 279         tDx = dx / ds * stepLength;
 280         tDy = dy / ds * stepLength;
 281 
 282         int stepsCount = (int) ds / stepLength;
 283 
 284         // Walk the mouse to the destination one step at a time
 285         mouseMove(srcX, srcY);
 286 
 287         for (x = srcX, y = srcY, stepNum = 0;
 288              stepNum < stepsCount;
 289              stepNum++) {
 290             x += tDx;
 291             y += tDy;
 292             mouseMove((int)x, (int)y);
 293             delay(speed);
 294         }
 295 
 296         // Ensure the mouse moves to the right destination.
 297         // The steps may have led the mouse to a slightly wrong place.
 298         mouseMove(destX, destY);
 299     }
 300 
 301     /**
 302      * Moves mouse pointer to given screen coordinates.
 303      *
 304      * @param   position    Target position
 305      *
 306      * @see     java.awt.Robot#mouseMove(int, int)
 307      */
 308     public synchronized void mouseMove(Point position) {
 309         mouseMove(position.x, position.y);
 310     }
 311 
 312 
 313     /**
 314      * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}.
 315      * The method successively moves mouse cursor to point with coordinates
 316      * ({@code fromX}, {@code fromY}), presses mouse button 1, drag mouse to
 317      * point with coordinates ({@code toX}, {@code toY}) and releases mouse
 318      * button 1 at last.
 319      *
 320      * @param   fromX   Source point x coordinate
 321      * @param   fromY   Source point y coordinate
 322      * @param   toX     Destination point x coordinate
 323      * @param   toY     Destination point y coordinate
 324      *
 325      * @see     #mousePress(int)
 326      * @see     #glide(int, int, int, int)
 327      */
 328     public void dragAndDrop(int fromX, int fromY, int toX, int toY){
 329         mouseMove(fromX, fromY);
 330         mousePress(InputEvent.BUTTON1_DOWN_MASK);
 331         waitForIdle();
 332         glide(toX, toY);
 333         mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
 334         waitForIdle();
 335     }
 336 
 337     /**
 338      * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}.
 339      * The method successively moves mouse cursor to point {@code from},
 340      * presses mouse button 1, drag mouse to point {@code to} and releases
 341      * mouse button 1 at last.
 342      *
 343      * @param   from    Source point
 344      * @param   to      Destination point
 345      *
 346      * @see     #mousePress(int)
 347      * @see     #glide(int, int, int, int)
 348      * @see     #dragAndDrop(int, int, int, int)
 349      */
 350     public void dragAndDrop(Point from, Point to){
 351         dragAndDrop(from.x, from.y, to.x, to.y);
 352     }
 353 
 354     /**
 355      * Successively presses and releases a given key.
 356      * <p>
 357      * Key codes that have more than one physical key associated with them
 358      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
 359      * left or right shift key) will map to the left key.
 360      *
 361      * @param   keycode Key to press (e.g. {@code KeyEvent.VK_A})
 362      * @throws  IllegalArgumentException if {@code keycode} is not
 363      *          a valid key
 364      *
 365      * @see     java.awt.Robot#keyPress(int)
 366      * @see     java.awt.Robot#keyRelease(int)
 367      * @see     java.awt.event.KeyEvent
 368      */
 369     public void type(int keycode) {
 370         keyPress(keycode);
 371         waitForIdle(DEFAULT_SPEED);
 372         keyRelease(keycode);
 373         waitForIdle(DEFAULT_SPEED);
 374     }
 375 
 376     /**
 377      * Types given character
 378      *
 379      * @param   c   Character to be typed (e.g. {@code 'a'})
 380      *
 381      * @see     #type(int)
 382      * @see     java.awt.event.KeyEvent
 383      */
 384     public void type(char c) {
 385         type(ExtendedKeyCodes.getExtendedKeyCodeForChar(c));
 386     }
 387 
 388     /**
 389      * Types given array of characters one by one
 390      *
 391      * @param   symbols Array of characters to be typed
 392      *
 393      * @see     #type(char)
 394      */
 395     public void type(char[] symbols) {
 396         for (int i = 0; i < symbols.length; i++) {
 397             type(symbols[i]);
 398         }
 399     }
 400 
 401     /**
 402      * Types given string
 403      *
 404      * @param   s   String to be typed
 405      *
 406      * @see     #type(char[])
 407      */
 408     public void type(String s) {
 409         type(s.toCharArray());
 410     }
 411 }