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