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