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