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 /** 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 */ Robot()92 public Robot() throws AWTException { 93 checkHeadless(); 94 init(GraphicsEnvironment.getLocalGraphicsEnvironment() 95 .getDefaultScreenDevice()); 96 } 97 98 /** 99 * Creates a Robot for the given screen device. Coordinates passed 100 * to Robot method calls like mouseMove, getPixelColor and 101 * createScreenCapture will be interpreted as being in the same coordinate 102 * system as the specified screen. Note that depending on the platform 103 * configuration, multiple screens may either: 104 * <ul> 105 * <li>share the same coordinate system to form a combined virtual screen</li> 106 * <li>use different coordinate systems to act as independent screens</li> 107 * </ul> 108 * <p> 109 * If screen devices are reconfigured such that the coordinate system is 110 * affected, the behavior of existing Robot objects is undefined. 111 * 112 * @param screen A screen GraphicsDevice indicating the coordinate 113 * system the Robot will operate in. 114 * @throws AWTException if the platform configuration does not allow 115 * low-level input control. This exception is always thrown when 116 * GraphicsEnvironment.isHeadless() returns true. 117 * @throws IllegalArgumentException if {@code screen} is not a screen 118 * GraphicsDevice. 119 * @throws SecurityException if {@code createRobot} permission is not granted 120 * @see java.awt.GraphicsEnvironment#isHeadless 121 * @see GraphicsDevice 122 * @see SecurityManager#checkPermission 123 * @see AWTPermission 124 */ Robot(GraphicsDevice screen)125 public Robot(GraphicsDevice screen) throws AWTException { 126 checkHeadless(); 127 checkIsScreenDevice(screen); 128 init(screen); 129 } 130 init(GraphicsDevice screen)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(screen); 136 } 137 initLegalButtonMask(); 138 } 139 140 @SuppressWarnings("deprecation") initLegalButtonMask()141 private static synchronized void initLegalButtonMask() { 142 if (LEGAL_BUTTON_MASK != 0) return; 143 144 int tmpMask = 0; 145 if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){ 146 if (Toolkit.getDefaultToolkit() instanceof SunToolkit) { 147 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons(); 148 for (int i = 0; i < buttonsNumber; i++){ 149 tmpMask |= InputEvent.getMaskForButton(i+1); 150 } 151 } 152 } 153 tmpMask |= InputEvent.BUTTON1_MASK| 154 InputEvent.BUTTON2_MASK| 155 InputEvent.BUTTON3_MASK| 156 InputEvent.BUTTON1_DOWN_MASK| 157 InputEvent.BUTTON2_DOWN_MASK| 158 InputEvent.BUTTON3_DOWN_MASK; 159 LEGAL_BUTTON_MASK = tmpMask; 160 } 161 162 /* determine if the security policy allows Robot's to be created */ checkRobotAllowed()163 private static void checkRobotAllowed() { 164 SecurityManager security = System.getSecurityManager(); 165 if (security != null) { 166 security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION); 167 } 168 } 169 170 /** 171 * Check for headless state and throw {@code AWTException} if headless. 172 */ checkHeadless()173 private static void checkHeadless() throws AWTException { 174 if (GraphicsEnvironment.isHeadless()) { 175 throw new AWTException("headless environment"); 176 } 177 } 178 179 /* check if the given device is a screen device */ checkIsScreenDevice(GraphicsDevice device)180 private static void checkIsScreenDevice(GraphicsDevice device) { 181 if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) { 182 throw new IllegalArgumentException("not a valid screen device"); 183 } 184 } 185 186 /** 187 * Moves mouse pointer to given screen coordinates. 188 * @param x X position 189 * @param y Y position 190 */ mouseMove(int x, int y)191 public synchronized void mouseMove(int x, int y) { 192 peer.mouseMove(x, y); 193 afterEvent(); 194 } 195 196 /** 197 * Presses one or more mouse buttons. The mouse buttons should 198 * be released using the {@link #mouseRelease(int)} method. 199 * 200 * @param buttons the Button mask; a combination of one or more 201 * mouse button masks. 202 * <p> 203 * It is allowed to use only a combination of valid values as a {@code buttons} parameter. 204 * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, 205 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} 206 * and values returned by the 207 * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. 208 * 209 * The valid combination also depends on a 210 * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: 211 * <ul> 212 * <li> If support for extended mouse buttons is 213 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 214 * then it is allowed to use only the following standard button masks: 215 * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK}, 216 * {@code InputEvent.BUTTON3_DOWN_MASK}. 217 * <li> If support for extended mouse buttons is 218 * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 219 * then it is allowed to use the standard button masks 220 * and masks for existing extended mouse buttons, if the mouse has more then three buttons. 221 * In that way, it is allowed to use the button masks corresponding to the buttons 222 * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}. 223 * <br> 224 * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} 225 * method to obtain the mask for any mouse button by its number. 226 * </ul> 227 * <p> 228 * The following standard button masks are also accepted: 229 * <ul> 230 * <li>{@code InputEvent.BUTTON1_MASK} 231 * <li>{@code InputEvent.BUTTON2_MASK} 232 * <li>{@code InputEvent.BUTTON3_MASK} 233 * </ul> 234 * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK}, 235 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} instead. 236 * Either extended {@code _DOWN_MASK} or old {@code _MASK} values 237 * should be used, but both those models should not be mixed. 238 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 239 * and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 240 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 241 * that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 242 * @see #mouseRelease(int) 243 * @see InputEvent#getMaskForButton(int) 244 * @see Toolkit#areExtraMouseButtonsEnabled() 245 * @see java.awt.MouseInfo#getNumberOfButtons() 246 * @see java.awt.event.MouseEvent 247 */ mousePress(int buttons)248 public synchronized void mousePress(int buttons) { 249 checkButtonsArgument(buttons); 250 peer.mousePress(buttons); 251 afterEvent(); 252 } 253 254 /** 255 * Releases one or more mouse buttons. 256 * 257 * @param buttons the Button mask; a combination of one or more 258 * mouse button masks. 259 * <p> 260 * It is allowed to use only a combination of valid values as a {@code buttons} parameter. 261 * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, 262 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} 263 * and values returned by the 264 * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. 265 * 266 * The valid combination also depends on a 267 * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: 268 * <ul> 269 * <li> If the support for extended mouse buttons is 270 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 271 * then it is allowed to use only the following standard button masks: 272 * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK}, 273 * {@code InputEvent.BUTTON3_DOWN_MASK}. 274 * <li> If the support for extended mouse buttons is 275 * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 276 * then it is allowed to use the standard button masks 277 * and masks for existing extended mouse buttons, if the mouse has more then three buttons. 278 * In that way, it is allowed to use the button masks corresponding to the buttons 279 * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}. 280 * <br> 281 * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} 282 * method to obtain the mask for any mouse button by its number. 283 * </ul> 284 * <p> 285 * The following standard button masks are also accepted: 286 * <ul> 287 * <li>{@code InputEvent.BUTTON1_MASK} 288 * <li>{@code InputEvent.BUTTON2_MASK} 289 * <li>{@code InputEvent.BUTTON3_MASK} 290 * </ul> 291 * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK}, 292 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} instead. 293 * Either extended {@code _DOWN_MASK} or old {@code _MASK} values 294 * should be used, but both those models should not be mixed. 295 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 296 * and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 297 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 298 * that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 299 * @see #mousePress(int) 300 * @see InputEvent#getMaskForButton(int) 301 * @see Toolkit#areExtraMouseButtonsEnabled() 302 * @see java.awt.MouseInfo#getNumberOfButtons() 303 * @see java.awt.event.MouseEvent 304 */ mouseRelease(int buttons)305 public synchronized void mouseRelease(int buttons) { 306 checkButtonsArgument(buttons); 307 peer.mouseRelease(buttons); 308 afterEvent(); 309 } 310 checkButtonsArgument(int buttons)311 private static void checkButtonsArgument(int buttons) { 312 if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) { 313 throw new IllegalArgumentException("Invalid combination of button flags"); 314 } 315 } 316 317 /** 318 * Rotates the scroll wheel on wheel-equipped mice. 319 * 320 * @param wheelAmt number of "notches" to move the mouse wheel 321 * Negative values indicate movement up/away from the user, 322 * positive values indicate movement down/towards the user. 323 * 324 * @since 1.4 325 */ mouseWheel(int wheelAmt)326 public synchronized void mouseWheel(int wheelAmt) { 327 peer.mouseWheel(wheelAmt); 328 afterEvent(); 329 } 330 331 /** 332 * Presses a given key. The key should be released using the 333 * {@code keyRelease} method. 334 * <p> 335 * Key codes that have more than one physical key associated with them 336 * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the 337 * left or right shift key) will map to the left key. 338 * 339 * @param keycode Key to press (e.g. {@code KeyEvent.VK_A}) 340 * @throws IllegalArgumentException if {@code keycode} is not 341 * a valid key 342 * @see #keyRelease(int) 343 * @see java.awt.event.KeyEvent 344 */ keyPress(int keycode)345 public synchronized void keyPress(int keycode) { 346 checkKeycodeArgument(keycode); 347 peer.keyPress(keycode); 348 afterEvent(); 349 } 350 351 /** 352 * Releases a given key. 353 * <p> 354 * Key codes that have more than one physical key associated with them 355 * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the 356 * left or right shift key) will map to the left key. 357 * 358 * @param keycode Key to release (e.g. {@code KeyEvent.VK_A}) 359 * @throws IllegalArgumentException if {@code keycode} is not a 360 * valid key 361 * @see #keyPress(int) 362 * @see java.awt.event.KeyEvent 363 */ keyRelease(int keycode)364 public synchronized void keyRelease(int keycode) { 365 checkKeycodeArgument(keycode); 366 peer.keyRelease(keycode); 367 afterEvent(); 368 } 369 checkKeycodeArgument(int keycode)370 private static void checkKeycodeArgument(int keycode) { 371 // rather than build a big table or switch statement here, we'll 372 // just check that the key isn't VK_UNDEFINED and assume that the 373 // peer implementations will throw an exception for other bogus 374 // values e.g. -1, 999999 375 if (keycode == KeyEvent.VK_UNDEFINED) { 376 throw new IllegalArgumentException("Invalid key code"); 377 } 378 } 379 380 /** 381 * Returns the color of a pixel at the given screen coordinates. 382 * @param x X position of pixel 383 * @param y Y position of pixel 384 * @return Color of the pixel 385 */ getPixelColor(int x, int y)386 public synchronized Color getPixelColor(int x, int y) { 387 checkScreenCaptureAllowed(); 388 AffineTransform tx = GraphicsEnvironment. 389 getLocalGraphicsEnvironment().getDefaultScreenDevice(). 390 getDefaultConfiguration().getDefaultTransform(); 391 x = (int) (x * tx.getScaleX()); 392 y = (int) (y * tx.getScaleY()); 393 Color color = new Color(peer.getRGBPixel(x, y)); 394 return color; 395 } 396 397 /** 398 * Creates an image containing pixels read from the screen. This image does 399 * not include the mouse cursor. 400 * @param screenRect Rect to capture in screen coordinates 401 * @return The captured image 402 * @throws IllegalArgumentException if {@code screenRect} width and height are not greater than zero 403 * @throws SecurityException if {@code readDisplayPixels} permission is not granted 404 * @see SecurityManager#checkPermission 405 * @see AWTPermission 406 */ createScreenCapture(Rectangle screenRect)407 public synchronized BufferedImage createScreenCapture(Rectangle screenRect) { 408 return createCompatibleImage(screenRect, false)[0]; 409 } 410 411 /** 412 * Creates an image containing pixels read from the screen. 413 * This image does not include the mouse cursor. 414 * This method can be used in case there is a scaling transform 415 * from user space to screen (device) space. 416 * Typically this means that the display is a high resolution screen, 417 * although strictly it means any case in which there is such a transform. 418 * Returns a {@link java.awt.image.MultiResolutionImage}. 419 * <p> 420 * For a non-scaled display, the {@code MultiResolutionImage} 421 * will have one image variant: 422 * <ul> 423 * <li> Base Image with user specified size. 424 * </ul> 425 * <p> 426 * For a high resolution display where there is a scaling transform, 427 * the {@code MultiResolutionImage} will have two image variants: 428 * <ul> 429 * <li> Base Image with user specified size. This is scaled from the screen. 430 * <li> Native device resolution image with device size pixels. 431 * </ul> 432 * <p> 433 * Example: 434 * <pre>{@code 435 * Image nativeResImage; 436 * MultiResolutionImage mrImage = robot.createMultiResolutionScreenCapture(frame.getBounds()); 437 * List<Image> resolutionVariants = mrImage.getResolutionVariants(); 438 * if (resolutionVariants.size() > 1) { 439 * nativeResImage = resolutionVariants.get(1); 440 * } else { 441 * nativeResImage = resolutionVariants.get(0); 442 * } 443 * }</pre> 444 * @param screenRect Rect to capture in screen coordinates 445 * @return The captured image 446 * @throws IllegalArgumentException if {@code screenRect} width and height are not greater than zero 447 * @throws SecurityException if {@code readDisplayPixels} permission is not granted 448 * @see SecurityManager#checkPermission 449 * @see AWTPermission 450 * 451 * @since 9 452 */ 453 public synchronized MultiResolutionImage createMultiResolutionScreenCapture(Rectangle screenRect)454 createMultiResolutionScreenCapture(Rectangle screenRect) { 455 456 return new BaseMultiResolutionImage( 457 createCompatibleImage(screenRect, true)); 458 } 459 460 private synchronized BufferedImage[] createCompatibleImage(Rectangle screenRect, boolean isHiDPI)461 createCompatibleImage(Rectangle screenRect, boolean isHiDPI) { 462 463 checkScreenCaptureAllowed(); 464 465 checkValidRect(screenRect); 466 467 BufferedImage lowResolutionImage; 468 BufferedImage highResolutionImage; 469 DataBufferInt buffer; 470 WritableRaster raster; 471 BufferedImage[] imageArray; 472 473 if (screenCapCM == null) { 474 /* 475 * Fix for 4285201 476 * Create a DirectColorModel equivalent to the default RGB ColorModel, 477 * except with no Alpha component. 478 */ 479 480 screenCapCM = new DirectColorModel(24, 481 /* red mask */ 0x00FF0000, 482 /* green mask */ 0x0000FF00, 483 /* blue mask */ 0x000000FF); 484 } 485 486 int[] bandmasks = new int[3]; 487 bandmasks[0] = screenCapCM.getRedMask(); 488 bandmasks[1] = screenCapCM.getGreenMask(); 489 bandmasks[2] = screenCapCM.getBlueMask(); 490 491 // need to sync the toolkit prior to grabbing the pixels since in some 492 // cases rendering to the screen may be delayed 493 Toolkit.getDefaultToolkit().sync(); 494 495 GraphicsConfiguration gc = GraphicsEnvironment 496 .getLocalGraphicsEnvironment() 497 .getDefaultScreenDevice(). 498 getDefaultConfiguration(); 499 gc = SunGraphicsEnvironment.getGraphicsConfigurationAtPoint( 500 gc, screenRect.getCenterX(), screenRect.getCenterY()); 501 502 AffineTransform tx = gc.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 checkValidRect(Rectangle rect)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 checkScreenCaptureAllowed()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 */ afterEvent()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 */ isAutoWaitForIdle()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 */ setAutoWaitForIdle(boolean isOn)610 public synchronized void setAutoWaitForIdle(boolean isOn) { 611 isAutoWaitForIdle = isOn; 612 } 613 614 /* 615 * Calls waitForIdle after every event if so desired. 616 */ autoWaitForIdle()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 */ getAutoDelay()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 */ setAutoDelay(int ms)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 */ autoDelay()647 private void autoDelay() { 648 delay(autoDelay); 649 } 650 651 /** 652 * Sleeps for the specified time. 653 * <p> 654 * If the invoking thread is interrupted while waiting, then it will return 655 * immediately with the interrupt status set. If the interrupted status is 656 * already set, this method returns immediately with the interrupt status 657 * set. 658 * 659 * @param ms time to sleep in milliseconds 660 * @throws IllegalArgumentException if {@code ms} is not between {@code 0} 661 * and {@code 60,000} milliseconds inclusive 662 */ delay(int ms)663 public void delay(int ms) { 664 checkDelayArgument(ms); 665 Thread thread = Thread.currentThread(); 666 if (!thread.isInterrupted()) { 667 try { 668 Thread.sleep(ms); 669 } catch (final InterruptedException ignored) { 670 thread.interrupt(); // Preserve interrupt status 671 } 672 } 673 } 674 checkDelayArgument(int ms)675 private static void checkDelayArgument(int ms) { 676 if (ms < 0 || ms > MAX_DELAY) { 677 throw new IllegalArgumentException("Delay must be to 0 to 60,000ms"); 678 } 679 } 680 681 /** 682 * Waits until all events currently on the event queue have been processed. 683 * @throws IllegalThreadStateException if called on the AWT event dispatching thread 684 */ waitForIdle()685 public synchronized void waitForIdle() { 686 checkNotDispatchThread(); 687 SunToolkit.flushPendingEvents(); 688 ((SunToolkit) Toolkit.getDefaultToolkit()).realSync(); 689 } 690 checkNotDispatchThread()691 private static void checkNotDispatchThread() { 692 if (EventQueue.isDispatchThread()) { 693 throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread"); 694 } 695 } 696 697 /** 698 * Returns a string representation of this Robot. 699 * 700 * @return the string representation. 701 */ 702 @Override toString()703 public synchronized String toString() { 704 String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle(); 705 return getClass().getName() + "[ " + params + " ]"; 706 } 707 } 708