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