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