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