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 }