1 /* 2 * Copyright (c) 1999, 2016, 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 package java.awt; 27 28 import java.awt.event.InputEvent; 29 import java.awt.event.KeyEvent; 30 import java.awt.geom.AffineTransform; 31 import java.awt.image.BufferedImage; 32 import java.awt.image.DataBufferInt; 33 import java.awt.image.DirectColorModel; 34 import java.awt.image.Raster; 35 import java.awt.image.WritableRaster; 36 import java.awt.peer.RobotPeer; 37 38 import sun.awt.AWTPermissions; 39 import sun.awt.ComponentFactory; 40 import sun.awt.SunToolkit; 41 import sun.awt.image.SunWritableRaster; 42 43 /** 44 * This class is used to generate native system input events 45 * for the purposes of test automation, self-running demos, and 46 * other applications where control of the mouse and keyboard 47 * is needed. The primary purpose of Robot is to facilitate 48 * automated testing of Java platform implementations. 49 * <p> 50 * Using the class to generate input events differs from posting 51 * events to the AWT event queue or AWT components in that the 52 * events are generated in the platform's native input 53 * queue. For example, {@code Robot.mouseMove} will actually move 54 * the mouse cursor instead of just generating mouse move events. 55 * <p> 56 * Note that some platforms require special privileges or extensions 57 * to access low-level input control. If the current platform configuration 58 * does not allow input control, an {@code AWTException} will be thrown 59 * when trying to construct Robot objects. For example, X-Window systems 60 * will throw the exception if the XTEST 2.2 standard extension is not supported 61 * (or not enabled) by the X server. 62 * <p> 63 * Applications that use Robot for purposes other than self-testing should 64 * handle these error conditions gracefully. 65 * 66 * @author Robi Khan 67 * @since 1.3 68 */ 69 public class Robot { 70 private static final int MAX_DELAY = 60000; 71 private RobotPeer peer; 72 private boolean isAutoWaitForIdle = false; 73 private int autoDelay = 0; 74 private static int LEGAL_BUTTON_MASK = 0; 75 76 private DirectColorModel screenCapCM = null; 77 78 /** 79 * Constructs a Robot object in the coordinate system of the primary screen. 80 * 81 * @throws AWTException if the platform configuration does not allow 82 * low-level input control. This exception is always thrown when 83 * GraphicsEnvironment.isHeadless() returns true 84 * @throws SecurityException if {@code createRobot} permission is not granted 85 * @see java.awt.GraphicsEnvironment#isHeadless 86 * @see SecurityManager#checkPermission 87 * @see AWTPermission 88 */ 89 public Robot() throws AWTException { 90 if (GraphicsEnvironment.isHeadless()) { 91 throw new AWTException("headless environment"); 92 } 93 init(GraphicsEnvironment.getLocalGraphicsEnvironment() 94 .getDefaultScreenDevice()); 95 } 96 97 /** 98 * Creates a Robot for the given screen device. Coordinates passed 99 * to Robot method calls like mouseMove and createScreenCapture will 100 * be interpreted as being in the same coordinate system as the 101 * specified screen. Note that depending on the platform configuration, 102 * multiple screens may either: 103 * <ul> 104 * <li>share the same coordinate system to form a combined virtual screen</li> 105 * <li>use different coordinate systems to act as independent screens</li> 106 * </ul> 107 * This constructor is meant for the latter case. 108 * <p> 109 * If screen devices are reconfigured such that the coordinate system is 110 * affected, the behavior of existing Robot objects is undefined. 111 * 112 * @param screen A screen GraphicsDevice indicating the coordinate 113 * system the Robot will operate in. 114 * @throws AWTException if the platform configuration does not allow 115 * low-level input control. This exception is always thrown when 116 * GraphicsEnvironment.isHeadless() returns true. 117 * @throws IllegalArgumentException if {@code screen} is not a screen 118 * GraphicsDevice. 119 * @throws SecurityException if {@code createRobot} permission is not granted 120 * @see java.awt.GraphicsEnvironment#isHeadless 121 * @see GraphicsDevice 122 * @see SecurityManager#checkPermission 123 * @see AWTPermission 124 */ 125 public Robot(GraphicsDevice screen) throws AWTException { 126 checkIsScreenDevice(screen); 127 init(screen); 128 } 129 130 private void init(GraphicsDevice screen) throws AWTException { 131 checkRobotAllowed(); 132 Toolkit toolkit = Toolkit.getDefaultToolkit(); 133 if (toolkit instanceof ComponentFactory) { 134 peer = ((ComponentFactory)toolkit).createRobot(this, screen); 135 disposer = new RobotDisposer(peer); 136 sun.java2d.Disposer.addRecord(anchor, disposer); 137 } 138 initLegalButtonMask(); 139 } 140 141 private static synchronized void initLegalButtonMask() { 142 if (LEGAL_BUTTON_MASK != 0) return; 143 144 int tmpMask = 0; 145 if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){ 146 if (Toolkit.getDefaultToolkit() instanceof SunToolkit) { 147 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons(); 148 for (int i = 0; i < buttonsNumber; i++){ 149 tmpMask |= InputEvent.getMaskForButton(i+1); 150 } 151 } 152 } 153 tmpMask |= InputEvent.BUTTON1_MASK| 154 InputEvent.BUTTON2_MASK| 155 InputEvent.BUTTON3_MASK| 156 InputEvent.BUTTON1_DOWN_MASK| 157 InputEvent.BUTTON2_DOWN_MASK| 158 InputEvent.BUTTON3_DOWN_MASK; 159 LEGAL_BUTTON_MASK = tmpMask; 160 } 161 162 /* determine if the security policy allows Robot's to be created */ 163 private void checkRobotAllowed() { 164 SecurityManager security = System.getSecurityManager(); 165 if (security != null) { 166 security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION); 167 } 168 } 169 170 /* check if the given device is a screen device */ 171 private void checkIsScreenDevice(GraphicsDevice device) { 172 if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) { 173 throw new IllegalArgumentException("not a valid screen device"); 174 } 175 } 176 177 private transient Object anchor = new Object(); 178 179 static class RobotDisposer implements sun.java2d.DisposerRecord { 180 private final RobotPeer peer; 181 public RobotDisposer(RobotPeer peer) { 182 this.peer = peer; 183 } 184 public void dispose() { 185 if (peer != null) { 186 peer.dispose(); 187 } 188 } 189 } 190 191 private transient RobotDisposer disposer; 192 193 /** 194 * Moves mouse pointer to given screen coordinates. 195 * @param x X position 196 * @param y Y position 197 */ 198 public synchronized void mouseMove(int x, int y) { 199 peer.mouseMove(x, y); 200 afterEvent(); 201 } 202 203 /** 204 * Presses one or more mouse buttons. The mouse buttons should 205 * be released using the {@link #mouseRelease(int)} method. 206 * 207 * @param buttons the Button mask; a combination of one or more 208 * mouse button masks. 209 * <p> 210 * It is allowed to use only a combination of valid values as a {@code buttons} parameter. 211 * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, 212 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} 213 * and values returned by the 214 * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. 215 * 216 * The valid combination also depends on a 217 * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: 218 * <ul> 219 * <li> If support for extended mouse buttons is 220 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 221 * then it is allowed to use only the following standard button masks: 222 * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK}, 223 * {@code InputEvent.BUTTON3_DOWN_MASK}. 224 * <li> If support for extended mouse buttons is 225 * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 226 * then it is allowed to use the standard button masks 227 * and masks for existing extended mouse buttons, if the mouse has more then three buttons. 228 * In that way, it is allowed to use the button masks corresponding to the buttons 229 * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}. 230 * <br> 231 * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} 232 * method to obtain the mask for any mouse button by its number. 233 * </ul> 234 * <p> 235 * The following standard button masks are also accepted: 236 * <ul> 237 * <li>{@code InputEvent.BUTTON1_MASK} 238 * <li>{@code InputEvent.BUTTON2_MASK} 239 * <li>{@code InputEvent.BUTTON3_MASK} 240 * </ul> 241 * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK}, 242 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} instead. 243 * Either extended {@code _DOWN_MASK} or old {@code _MASK} values 244 * should be used, but both those models should not be mixed. 245 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 246 * and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 247 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 248 * that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 249 * @see #mouseRelease(int) 250 * @see InputEvent#getMaskForButton(int) 251 * @see Toolkit#areExtraMouseButtonsEnabled() 252 * @see java.awt.MouseInfo#getNumberOfButtons() 253 * @see java.awt.event.MouseEvent 254 */ 255 public synchronized void mousePress(int buttons) { 256 checkButtonsArgument(buttons); 257 peer.mousePress(buttons); 258 afterEvent(); 259 } 260 261 /** 262 * Releases one or more mouse buttons. 263 * 264 * @param buttons the Button mask; a combination of one or more 265 * mouse button masks. 266 * <p> 267 * It is allowed to use only a combination of valid values as a {@code buttons} parameter. 268 * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, 269 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} 270 * and values returned by the 271 * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. 272 * 273 * The valid combination also depends on a 274 * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: 275 * <ul> 276 * <li> If the support for extended mouse buttons is 277 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 278 * then it is allowed to use only the following standard button masks: 279 * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK}, 280 * {@code InputEvent.BUTTON3_DOWN_MASK}. 281 * <li> If the support for extended mouse buttons is 282 * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 283 * then it is allowed to use the standard button masks 284 * and masks for existing extended mouse buttons, if the mouse has more then three buttons. 285 * In that way, it is allowed to use the button masks corresponding to the buttons 286 * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}. 287 * <br> 288 * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} 289 * method to obtain the mask for any mouse button by its number. 290 * </ul> 291 * <p> 292 * The following standard button masks are also accepted: 293 * <ul> 294 * <li>{@code InputEvent.BUTTON1_MASK} 295 * <li>{@code InputEvent.BUTTON2_MASK} 296 * <li>{@code InputEvent.BUTTON3_MASK} 297 * </ul> 298 * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK}, 299 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} instead. 300 * Either extended {@code _DOWN_MASK} or old {@code _MASK} values 301 * should be used, but both those models should not be mixed. 302 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 303 * and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 304 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 305 * that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 306 * @see #mousePress(int) 307 * @see InputEvent#getMaskForButton(int) 308 * @see Toolkit#areExtraMouseButtonsEnabled() 309 * @see java.awt.MouseInfo#getNumberOfButtons() 310 * @see java.awt.event.MouseEvent 311 */ 312 public synchronized void mouseRelease(int buttons) { 313 checkButtonsArgument(buttons); 314 peer.mouseRelease(buttons); 315 afterEvent(); 316 } 317 318 private void checkButtonsArgument(int buttons) { 319 if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) { 320 throw new IllegalArgumentException("Invalid combination of button flags"); 321 } 322 } 323 324 /** 325 * Rotates the scroll wheel on wheel-equipped mice. 326 * 327 * @param wheelAmt number of "notches" to move the mouse wheel 328 * Negative values indicate movement up/away from the user, 329 * positive values indicate movement down/towards the user. 330 * 331 * @since 1.4 332 */ 333 public synchronized void mouseWheel(int wheelAmt) { 334 peer.mouseWheel(wheelAmt); 335 afterEvent(); 336 } 337 338 /** 339 * Presses a given key. The key should be released using the 340 * {@code keyRelease} method. 341 * <p> 342 * Key codes that have more than one physical key associated with them 343 * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the 344 * left or right shift key) will map to the left key. 345 * 346 * @param keycode Key to press (e.g. {@code KeyEvent.VK_A}) 347 * @throws IllegalArgumentException if {@code keycode} is not 348 * a valid key 349 * @see #keyRelease(int) 350 * @see java.awt.event.KeyEvent 351 */ 352 public synchronized void keyPress(int keycode) { 353 checkKeycodeArgument(keycode); 354 peer.keyPress(keycode); 355 afterEvent(); 356 } 357 358 /** 359 * Releases a given key. 360 * <p> 361 * Key codes that have more than one physical key associated with them 362 * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the 363 * left or right shift key) will map to the left key. 364 * 365 * @param keycode Key to release (e.g. {@code KeyEvent.VK_A}) 366 * @throws IllegalArgumentException if {@code keycode} is not a 367 * valid key 368 * @see #keyPress(int) 369 * @see java.awt.event.KeyEvent 370 */ 371 public synchronized void keyRelease(int keycode) { 372 checkKeycodeArgument(keycode); 373 peer.keyRelease(keycode); 374 afterEvent(); 375 } 376 377 private void checkKeycodeArgument(int keycode) { 378 // rather than build a big table or switch statement here, we'll 379 // just check that the key isn't VK_UNDEFINED and assume that the 380 // peer implementations will throw an exception for other bogus 381 // values e.g. -1, 999999 382 if (keycode == KeyEvent.VK_UNDEFINED) { 383 throw new IllegalArgumentException("Invalid key code"); 384 } 385 } 386 387 /** 388 * Returns the color of a pixel at the given screen coordinates. 389 * @param x X position of pixel 390 * @param y Y position of pixel 391 * @return Color of the pixel 392 */ 393 public synchronized Color getPixelColor(int x, int y) { 394 Color color = new Color(peer.getRGBPixel(x, y)); 395 return color; 396 } 397 398 private static int interp(int pixels[], int x, int y, int w, int h, int fractx1, int fracty1) { 399 int fractx0 = 256 - fractx1; 400 int fracty0 = 256 - fracty1; 401 int i = y * w + x; 402 int rgb00 = (x < 0 || y < 0 || x >= w || y >= h) ? 0 : pixels[i]; 403 if (fracty1 == 0) { 404 // No interplation with pixels[y+1] 405 if (fractx1 == 0) { 406 // No interpolation with any neighbors 407 return rgb00; 408 } 409 int rgb10 = (y < 0 || x + 1 >= w || y >= h) ? 0 : pixels[i + 1]; 410 return interp(rgb00, rgb10, fractx0, fractx1); 411 } else if (fractx1 == 0) { 412 // No interpolation with pixels[x+1] 413 int rgb01 = (x < 0 || x >= w || y + 1 >= h) ? 0 : pixels[i + w]; 414 return interp(rgb00, rgb01, fracty0, fracty1); 415 } else { 416 // All 4 neighbors must be interpolated 417 int rgb10 = (y < 0 || x + 1 >= w || y >= h) ? 0 : pixels[i + 1]; 418 int rgb01 = (x < 0 || x >= w || y + 1 >= h) ? 0 : pixels[i + w]; 419 int rgb11 = (x + 1 >= w || y + 1 >= h) ? 0 : pixels[i + w + 1]; 420 return interp(interp(rgb00, rgb10, fractx0, fractx1), 421 interp(rgb01, rgb11, fractx0, fractx1), 422 fracty0, fracty1); 423 } 424 } 425 426 private static int interp(int rgb0, int rgb1, int fract0, int fract1) { 427 int a0 = (rgb0 >> 24) & 0xff; 428 int r0 = (rgb0 >> 16) & 0xff; 429 int g0 = (rgb0 >> 8) & 0xff; 430 int b0 = (rgb0) & 0xff; 431 int a1 = (rgb1 >> 24) & 0xff; 432 int r1 = (rgb1 >> 16) & 0xff; 433 int g1 = (rgb1 >> 8) & 0xff; 434 int b1 = (rgb1) & 0xff; 435 int a = (a0 * fract0 + a1 * fract1) >> 8; 436 int r = (r0 * fract0 + r1 * fract1) >> 8; 437 int g = (g0 * fract0 + g1 * fract1) >> 8; 438 int b = (b0 * fract0 + b1 * fract1) >> 8; 439 return (a << 24) | (r << 16) | (g << 8) | b; 440 } 441 442 /** 443 * Creates an image containing pixels read from the screen. This image does 444 * not include the mouse cursor. 445 * @param screenRect Rect to capture in screen coordinates 446 * @return The captured image 447 * @throws IllegalArgumentException if {@code screenRect} width and height are not greater than zero 448 * @throws SecurityException if {@code readDisplayPixels} permission is not granted 449 * @see SecurityManager#checkPermission 450 * @see AWTPermission 451 */ 452 public synchronized BufferedImage createScreenCapture(Rectangle screenRect) { 453 return createScreenCapture(screenRect, false); 454 } 455 456 /** 457 * Creates an image containing pixels read from the screen. This image does 458 * not include the mouse cursor. 459 * @param screenRect Rect to capture in screen coordinates 460 * @param isHiDPI Specifies if HiDPI 461 * @return The captured image 462 * @throws IllegalArgumentException if {@code screenRect} width and height are not greater than zero 463 * @throws SecurityException if {@code readDisplayPixels} permission is not granted 464 * @see SecurityManager#checkPermission 465 * @see AWTPermission 466 */ 467 public synchronized BufferedImage createScreenCapture(Rectangle screenRect, 468 boolean isHiDPI) { 469 checkScreenCaptureAllowed(); 470 471 checkValidRect(screenRect); 472 473 BufferedImage image; 474 DataBufferInt buffer; 475 WritableRaster raster; 476 477 if (screenCapCM == null) { 478 /* 479 * Fix for 4285201 480 * Create a DirectColorModel equivalent to the default RGB ColorModel, 481 * except with no Alpha component. 482 */ 483 484 screenCapCM = new DirectColorModel(24, 485 /* red mask */ 0x00FF0000, 486 /* green mask */ 0x0000FF00, 487 /* blue mask */ 0x000000FF); 488 } 489 490 // need to sync the toolkit prior to grabbing the pixels since in some 491 // cases rendering to the screen may be delayed 492 Toolkit.getDefaultToolkit().sync(); 493 AffineTransform tx = GraphicsEnvironment. 494 getLocalGraphicsEnvironment().getDefaultScreenDevice(). 495 getDefaultConfiguration().getDefaultTransform(); 496 double uiScaleX = tx.getScaleX(); 497 double uiScaleY = tx.getScaleY(); 498 int pixels[]; 499 int[] bandmasks = new int[3]; 500 501 if (uiScaleX == 1 && uiScaleY == 1) { 502 pixels = peer.getRGBPixels(screenRect); 503 } else { 504 int x = screenRect.x; 505 int y = screenRect.y; 506 int width = screenRect.width; 507 int height = screenRect.height; 508 int pminx = (int) Math.floor(x * uiScaleX); 509 int pminy = (int) Math.floor(y * uiScaleY); 510 int pmaxx = (int) Math.ceil((x + width) * uiScaleX); 511 int pmaxy = (int) Math.ceil((y + height) * uiScaleY); 512 int pwidth = pmaxx - pminx; 513 int pheight = pmaxy - pminy; 514 int temppixels[]; 515 Rectangle rect = new Rectangle(pminx, pminy, pwidth, pheight); 516 temppixels = peer.getRGBPixels(rect); 517 if (isHiDPI) { 518 pixels = temppixels; 519 screenRect.width = pwidth; 520 screenRect.height = pheight; 521 } else { 522 pixels = new int[width * height]; 523 int index = 0; 524 for (int iy = 0; iy < height; iy++) { 525 float rely = (float) (((y + iy + 0.5f) * uiScaleY) 526 - (pminy + 0.5f)); 527 int irely = (int) Math.floor(rely); 528 int fracty = (int) ((rely - irely) * 256); 529 for (int ix = 0; ix < width; ix++) { 530 float relx = (float) (((x + ix + 0.5f) * uiScaleX) 531 - (pminx + 0.5f)); 532 int irelx = (int) Math.floor(relx); 533 int fractx = (int) ((relx - irelx) * 256); 534 pixels[index++] 535 = interp(temppixels, irelx, irely, pwidth, 536 pheight, fractx, fracty); 537 } 538 } 539 screenRect.width = width; 540 screenRect.height = height; 541 } 542 } 543 544 buffer = new DataBufferInt(pixels, pixels.length); 545 546 bandmasks[0] = screenCapCM.getRedMask(); 547 bandmasks[1] = screenCapCM.getGreenMask(); 548 bandmasks[2] = screenCapCM.getBlueMask(); 549 550 raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null); 551 SunWritableRaster.makeTrackable(buffer); 552 553 image = new BufferedImage(screenCapCM, raster, false, null); 554 555 return image; 556 } 557 558 private static void checkValidRect(Rectangle rect) { 559 if (rect.width <= 0 || rect.height <= 0) { 560 throw new IllegalArgumentException("Rectangle width and height must be > 0"); 561 } 562 } 563 564 private static void checkScreenCaptureAllowed() { 565 SecurityManager security = System.getSecurityManager(); 566 if (security != null) { 567 security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION); 568 } 569 } 570 571 /* 572 * Called after an event is generated 573 */ 574 private void afterEvent() { 575 autoWaitForIdle(); 576 autoDelay(); 577 } 578 579 /** 580 * Returns whether this Robot automatically invokes {@code waitForIdle} 581 * after generating an event. 582 * @return Whether {@code waitForIdle} is automatically called 583 */ 584 public synchronized boolean isAutoWaitForIdle() { 585 return isAutoWaitForIdle; 586 } 587 588 /** 589 * Sets whether this Robot automatically invokes {@code waitForIdle} 590 * after generating an event. 591 * @param isOn Whether {@code waitForIdle} is automatically invoked 592 */ 593 public synchronized void setAutoWaitForIdle(boolean isOn) { 594 isAutoWaitForIdle = isOn; 595 } 596 597 /* 598 * Calls waitForIdle after every event if so desired. 599 */ 600 private void autoWaitForIdle() { 601 if (isAutoWaitForIdle) { 602 waitForIdle(); 603 } 604 } 605 606 /** 607 * Returns the number of milliseconds this Robot sleeps after generating an event. 608 * 609 * @return the delay duration in milliseconds 610 */ 611 public synchronized int getAutoDelay() { 612 return autoDelay; 613 } 614 615 /** 616 * Sets the number of milliseconds this Robot sleeps after generating an event. 617 * 618 * @param ms the delay duration in milliseconds 619 * @throws IllegalArgumentException If {@code ms} 620 * is not between 0 and 60,000 milliseconds inclusive 621 */ 622 public synchronized void setAutoDelay(int ms) { 623 checkDelayArgument(ms); 624 autoDelay = ms; 625 } 626 627 /* 628 * Automatically sleeps for the specified interval after event generated. 629 */ 630 private void autoDelay() { 631 delay(autoDelay); 632 } 633 634 /** 635 * Sleeps for the specified time. 636 * To catch any {@code InterruptedException}s that occur, 637 * {@code Thread.sleep()} may be used instead. 638 * 639 * @param ms time to sleep in milliseconds 640 * @throws IllegalArgumentException if {@code ms} 641 * is not between 0 and 60,000 milliseconds inclusive 642 * @see java.lang.Thread#sleep 643 */ 644 public synchronized void delay(int ms) { 645 checkDelayArgument(ms); 646 try { 647 Thread.sleep(ms); 648 } catch(InterruptedException ite) { 649 ite.printStackTrace(); 650 } 651 } 652 653 private void checkDelayArgument(int ms) { 654 if (ms < 0 || ms > MAX_DELAY) { 655 throw new IllegalArgumentException("Delay must be to 0 to 60,000ms"); 656 } 657 } 658 659 /** 660 * Waits until all events currently on the event queue have been processed. 661 * @throws IllegalThreadStateException if called on the AWT event dispatching thread 662 */ 663 public synchronized void waitForIdle() { 664 checkNotDispatchThread(); 665 SunToolkit.flushPendingEvents(); 666 ((SunToolkit) Toolkit.getDefaultToolkit()).realSync(); 667 } 668 669 private void checkNotDispatchThread() { 670 if (EventQueue.isDispatchThread()) { 671 throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread"); 672 } 673 } 674 675 /** 676 * Returns a string representation of this Robot. 677 * 678 * @return the string representation. 679 */ 680 @Override 681 public synchronized String toString() { 682 String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle(); 683 return getClass().getName() + "[ " + params + " ]"; 684 } 685 }