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