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