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