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, getPixelColor and 102 * createScreenCapture will be interpreted as being in the same coordinate 103 * system as the specified screen. Note that depending on the platform 104 * configuration, 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 * <p> 110 * If screen devices are reconfigured such that the coordinate system is 111 * affected, the behavior of existing Robot objects is undefined. 112 * 113 * @param screen A screen GraphicsDevice indicating the coordinate 114 * system the Robot will operate in. 115 * @throws AWTException if the platform configuration does not allow 116 * low-level input control. This exception is always thrown when 117 * GraphicsEnvironment.isHeadless() returns true. 118 * @throws IllegalArgumentException if {@code screen} is not a screen 119 * GraphicsDevice. 120 * @throws SecurityException if {@code createRobot} permission is not granted 121 * @see java.awt.GraphicsEnvironment#isHeadless 122 * @see GraphicsDevice 123 * @see SecurityManager#checkPermission 124 * @see AWTPermission 125 */ 126 public Robot(GraphicsDevice screen) throws AWTException { 127 checkIsScreenDevice(screen); 128 init(screen); 129 } 130 131 private void init(GraphicsDevice screen) throws AWTException { 132 checkRobotAllowed(); 133 Toolkit toolkit = Toolkit.getDefaultToolkit(); 134 if (toolkit instanceof ComponentFactory) { 135 peer = ((ComponentFactory)toolkit).createRobot(this, screen); 136 disposer = new RobotDisposer(peer); 137 sun.java2d.Disposer.addRecord(anchor, disposer); 138 } 139 initLegalButtonMask(); 140 } 141 142 @SuppressWarnings("deprecation") 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 can be used in case 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 a 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 * <p> 441 * Example: 442 * <pre>{@code 443 * Image nativeResImage; 444 * MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(frame.getBounds()); 445 * List<Image> resolutionVariants = mrImage.getResolutionVariants(); 446 * if (resolutionVariants.size() > 1) { 447 * nativeResImage = resolutionVariants.get(1); 448 * } else { 449 * nativeResImage = resolutionVariants.get(0); 450 * } 451 * }</pre> 452 * @param screenRect Rect to capture in screen coordinates 453 * @return The captured image 454 * @throws IllegalArgumentException if {@code screenRect} width and height are not greater than zero 455 * @throws SecurityException if {@code readDisplayPixels} permission is not granted 456 * @see SecurityManager#checkPermission 457 * @see AWTPermission 458 * 459 * @since 9 460 */ 461 public synchronized MultiResolutionImage 462 createMultiResolutionScreenCapture(Rectangle screenRect) { 463 464 return new BaseMultiResolutionImage( 465 createCompatibleImage(screenRect, true)); 466 } 467 468 private synchronized BufferedImage[] 469 createCompatibleImage(Rectangle screenRect, boolean isHiDPI) { 470 471 checkScreenCaptureAllowed(); 472 473 checkValidRect(screenRect); 474 475 BufferedImage lowResolutionImage; 476 BufferedImage highResolutionImage; 477 DataBufferInt buffer; 478 WritableRaster raster; 479 BufferedImage[] imageArray; 480 481 if (screenCapCM == null) { 482 /* 483 * Fix for 4285201 484 * Create a DirectColorModel equivalent to the default RGB ColorModel, 485 * except with no Alpha component. 486 */ 487 488 screenCapCM = new DirectColorModel(24, 489 /* red mask */ 0x00FF0000, 490 /* green mask */ 0x0000FF00, 491 /* blue mask */ 0x000000FF); 492 } 493 494 int[] bandmasks = new int[3]; 495 bandmasks[0] = screenCapCM.getRedMask(); 496 bandmasks[1] = screenCapCM.getGreenMask(); 497 bandmasks[2] = screenCapCM.getBlueMask(); 498 499 // need to sync the toolkit prior to grabbing the pixels since in some 500 // cases rendering to the screen may be delayed 501 Toolkit.getDefaultToolkit().sync(); 502 AffineTransform tx = GraphicsEnvironment. 503 getLocalGraphicsEnvironment().getDefaultScreenDevice(). 504 getDefaultConfiguration().getDefaultTransform(); 505 double uiScaleX = tx.getScaleX(); 506 double uiScaleY = tx.getScaleY(); 507 int pixels[]; 508 509 if (uiScaleX == 1 && uiScaleY == 1) { 510 511 pixels = peer.getRGBPixels(screenRect); 512 buffer = new DataBufferInt(pixels, pixels.length); 513 514 bandmasks[0] = screenCapCM.getRedMask(); 515 bandmasks[1] = screenCapCM.getGreenMask(); 516 bandmasks[2] = screenCapCM.getBlueMask(); 517 518 raster = Raster.createPackedRaster(buffer, screenRect.width, 519 screenRect.height, screenRect.width, bandmasks, null); 520 SunWritableRaster.makeTrackable(buffer); 521 522 highResolutionImage = new BufferedImage(screenCapCM, raster, 523 false, null); 524 imageArray = new BufferedImage[1]; 525 imageArray[0] = highResolutionImage; 526 527 } else { 528 529 int sX = (int) Math.floor(screenRect.x * uiScaleX); 530 int sY = (int) Math.floor(screenRect.y * uiScaleY); 531 int sWidth = (int) Math.ceil(screenRect.width * uiScaleX); 532 int sHeight = (int) Math.ceil(screenRect.height * uiScaleY); 533 int temppixels[]; 534 Rectangle scaledRect = new Rectangle(sX, sY, sWidth, sHeight); 535 temppixels = peer.getRGBPixels(scaledRect); 536 537 // HighResolutionImage 538 pixels = temppixels; 539 buffer = new DataBufferInt(pixels, pixels.length); 540 raster = Raster.createPackedRaster(buffer, scaledRect.width, 541 scaledRect.height, scaledRect.width, bandmasks, null); 542 SunWritableRaster.makeTrackable(buffer); 543 544 highResolutionImage = new BufferedImage(screenCapCM, raster, 545 false, null); 546 547 548 // LowResolutionImage 549 lowResolutionImage = new BufferedImage(screenRect.width, 550 screenRect.height, highResolutionImage.getType()); 551 Graphics2D g = lowResolutionImage.createGraphics(); 552 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 553 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 554 g.setRenderingHint(RenderingHints.KEY_RENDERING, 555 RenderingHints.VALUE_RENDER_QUALITY); 556 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 557 RenderingHints.VALUE_ANTIALIAS_ON); 558 g.drawImage(highResolutionImage, 0, 0, 559 screenRect.width, screenRect.height, 560 0, 0, scaledRect.width, scaledRect.height, null); 561 g.dispose(); 562 563 if(!isHiDPI) { 564 imageArray = new BufferedImage[1]; 565 imageArray[0] = lowResolutionImage; 566 } else { 567 imageArray = new BufferedImage[2]; 568 imageArray[0] = lowResolutionImage; 569 imageArray[1] = highResolutionImage; 570 } 571 572 } 573 574 return imageArray; 575 } 576 577 private static void checkValidRect(Rectangle rect) { 578 if (rect.width <= 0 || rect.height <= 0) { 579 throw new IllegalArgumentException("Rectangle width and height must be > 0"); 580 } 581 } 582 583 private static void checkScreenCaptureAllowed() { 584 SecurityManager security = System.getSecurityManager(); 585 if (security != null) { 586 security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION); 587 } 588 } 589 590 /* 591 * Called after an event is generated 592 */ 593 private void afterEvent() { 594 autoWaitForIdle(); 595 autoDelay(); 596 } 597 598 /** 599 * Returns whether this Robot automatically invokes {@code waitForIdle} 600 * after generating an event. 601 * @return Whether {@code waitForIdle} is automatically called 602 */ 603 public synchronized boolean isAutoWaitForIdle() { 604 return isAutoWaitForIdle; 605 } 606 607 /** 608 * Sets whether this Robot automatically invokes {@code waitForIdle} 609 * after generating an event. 610 * @param isOn Whether {@code waitForIdle} is automatically invoked 611 */ 612 public synchronized void setAutoWaitForIdle(boolean isOn) { 613 isAutoWaitForIdle = isOn; 614 } 615 616 /* 617 * Calls waitForIdle after every event if so desired. 618 */ 619 private void autoWaitForIdle() { 620 if (isAutoWaitForIdle) { 621 waitForIdle(); 622 } 623 } 624 625 /** 626 * Returns the number of milliseconds this Robot sleeps after generating an event. 627 * 628 * @return the delay duration in milliseconds 629 */ 630 public synchronized int getAutoDelay() { 631 return autoDelay; 632 } 633 634 /** 635 * Sets the number of milliseconds this Robot sleeps after generating an event. 636 * 637 * @param ms the delay duration in milliseconds 638 * @throws IllegalArgumentException If {@code ms} 639 * is not between 0 and 60,000 milliseconds inclusive 640 */ 641 public synchronized void setAutoDelay(int ms) { 642 checkDelayArgument(ms); 643 autoDelay = ms; 644 } 645 646 /* 647 * Automatically sleeps for the specified interval after event generated. 648 */ 649 private void autoDelay() { 650 delay(autoDelay); 651 } 652 653 /** 654 * Sleeps for the specified time. 655 * To catch any {@code InterruptedException}s that occur, 656 * {@code Thread.sleep()} may be used instead. 657 * 658 * @param ms time to sleep in milliseconds 659 * @throws IllegalArgumentException if {@code ms} 660 * is not between 0 and 60,000 milliseconds inclusive 661 * @see java.lang.Thread#sleep 662 */ 663 public synchronized void delay(int ms) { 664 checkDelayArgument(ms); 665 try { 666 Thread.sleep(ms); 667 } catch(InterruptedException ite) { 668 ite.printStackTrace(); 669 } 670 } 671 672 private void checkDelayArgument(int ms) { 673 if (ms < 0 || ms > MAX_DELAY) { 674 throw new IllegalArgumentException("Delay must be to 0 to 60,000ms"); 675 } 676 } 677 678 /** 679 * Waits until all events currently on the event queue have been processed. 680 * @throws IllegalThreadStateException if called on the AWT event dispatching thread 681 */ 682 public synchronized void waitForIdle() { 683 checkNotDispatchThread(); 684 SunToolkit.flushPendingEvents(); 685 ((SunToolkit) Toolkit.getDefaultToolkit()).realSync(); 686 } 687 688 private void checkNotDispatchThread() { 689 if (EventQueue.isDispatchThread()) { 690 throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread"); 691 } 692 } 693 694 /** 695 * Returns a string representation of this Robot. 696 * 697 * @return the string representation. 698 */ 699 @Override 700 public synchronized String toString() { 701 String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle(); 702 return getClass().getName() + "[ " + params + " ]"; 703 } 704 }