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