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