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      * To catch any {@code InterruptedException}s that occur,
 646      * {@code Thread.sleep()} may be used instead.
 647      *
 648      * @param  ms time to sleep in milliseconds
 649      * @throws IllegalArgumentException if {@code ms}
 650      *         is not between 0 and 60,000 milliseconds inclusive
 651      * @see java.lang.Thread#sleep
 652      */
 653     public synchronized void delay(int ms) {
 654         checkDelayArgument(ms);
 655         try {
 656             Thread.sleep(ms);
 657         } catch(InterruptedException ite) {
 658             ite.printStackTrace();
 659         }
 660     }
 661 
 662     private void checkDelayArgument(int ms) {
 663         if (ms < 0 || ms > MAX_DELAY) {
 664             throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
 665         }
 666     }
 667 
 668     /**
 669      * Waits until all events currently on the event queue have been processed.
 670      * @throws  IllegalThreadStateException if called on the AWT event dispatching thread
 671      */
 672     public synchronized void waitForIdle() {
 673         checkNotDispatchThread();
 674         SunToolkit.flushPendingEvents();
 675         ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
 676     }
 677 
 678     private void checkNotDispatchThread() {
 679         if (EventQueue.isDispatchThread()) {
 680             throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
 681         }
 682     }
 683 
 684     /**
 685      * Returns a string representation of this Robot.
 686      *
 687      * @return  the string representation.
 688      */
 689     @Override
 690     public synchronized String toString() {
 691         String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
 692         return getClass().getName() + "[ " + params + " ]";
 693     }
 694 }