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