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