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 }