1 /*
   2  * Copyright (c) 1999, 2014, 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.image.BufferedImage;
  31 import java.awt.image.DataBufferInt;
  32 import java.awt.image.DirectColorModel;
  33 import java.awt.image.Raster;
  34 import java.awt.image.WritableRaster;
  35 import java.awt.peer.RobotPeer;
  36 import java.security.AccessController;
  37 
  38 import sun.awt.AWTPermissions;
  39 import sun.awt.ComponentFactory;
  40 import sun.awt.ExtendedKeyCodes;
  41 import sun.awt.SunToolkit;
  42 import sun.awt.image.SunWritableRaster;
  43 import sun.security.action.GetIntegerAction;
  44 
  45 /**
  46  * This class is used to generate native system input events
  47  * for the purposes of test automation, self-running demos, and
  48  * other applications where control of the mouse and keyboard
  49  * is needed. The primary purpose of Robot is to facilitate
  50  * automated testing of Java platform implementations.
  51  * <p>
  52  * Using the class to generate input events differs from posting
  53  * events to the AWT event queue or AWT components in that the
  54  * events are generated in the platform's native input
  55  * queue. For example, <code>Robot.mouseMove</code> will actually move
  56  * the mouse cursor instead of just generating mouse move events.
  57  * <p>
  58  * Robot uses delay {@link #getSyncDelay()} to make syncing threads
  59  * with {@link #waitForIdle()} more stable. This delay can be set once
  60  * on creating object and could not be changed throughout object lifecycle.
  61  * Constructor reads vm integer property {@code java.awt.robotdelay} and
  62  * sets the delay value equal to the property value. If the property was
  63  * not set 500 milliseconds default value is used.
  64  * <p>
  65  * Note that some platforms require special privileges or extensions
  66  * to access low-level input control. If the current platform configuration
  67  * does not allow input control, an <code>AWTException</code> will be thrown
  68  * when trying to construct Robot objects. For example, X-Window systems
  69  * will throw the exception if the XTEST 2.2 standard extension is not supported
  70  * (or not enabled) by the X server.
  71  * <p>
  72  * Applications that use Robot for purposes other than self-testing should
  73  * handle these error conditions gracefully.
  74  *
  75  * @author      Robi Khan
  76  * @since       1.3
  77  */
  78 public class Robot {
  79     private static final int MAX_DELAY = 60000;
  80     private RobotPeer peer;
  81     private boolean isAutoWaitForIdle = false;
  82     private int autoDelay = 0;
  83     private static int LEGAL_BUTTON_MASK = 0;
  84     private static int DEFAULT_SPEED = 20;       // Speed for mouse glide and click
  85     private static int DEFAULT_SYNC_DELAY = 500; // Default Additional delay for waitForIdle()
  86     private static int DEFAULT_STEP_LENGTH = 2;  // Step length (in pixels) for mouse glide
  87 
  88     private final int syncDelay;
  89     {
  90         syncDelay = AccessController.doPrivileged(
  91                 new GetIntegerAction("java.awt.robotdelay", DEFAULT_SYNC_DELAY));
  92     }
  93 
  94 
  95     private DirectColorModel screenCapCM = null;
  96 
  97     /**
  98      * Constructs a Robot object in the coordinate system of the primary screen.
  99      *
 100      * @throws AWTException if the platform configuration does not allow
 101      * low-level input control.  This exception is always thrown when
 102      * GraphicsEnvironment.isHeadless() returns true
 103      * @throws  SecurityException if <code>createRobot</code> permission is not granted
 104      * @see     java.awt.GraphicsEnvironment#isHeadless
 105      * @see     SecurityManager#checkPermission
 106      * @see     AWTPermission
 107      */
 108     public Robot() throws AWTException {
 109         if (GraphicsEnvironment.isHeadless()) {
 110             throw new AWTException("headless environment");
 111         }
 112         init(GraphicsEnvironment.getLocalGraphicsEnvironment()
 113             .getDefaultScreenDevice());
 114     }
 115 
 116     /**
 117      * Creates a Robot for the given screen device. Coordinates passed
 118      * to Robot method calls like mouseMove and createScreenCapture will
 119      * be interpreted as being in the same coordinate system as the
 120      * specified screen. Note that depending on the platform configuration,
 121      * multiple screens may either:
 122      * <ul>
 123      * <li>share the same coordinate system to form a combined virtual screen</li>
 124      * <li>use different coordinate systems to act as independent screens</li>
 125      * </ul>
 126      * This constructor is meant for the latter case.
 127      * <p>
 128      * If screen devices are reconfigured such that the coordinate system is
 129      * affected, the behavior of existing Robot objects is undefined.
 130      *
 131      * @param screen    A screen GraphicsDevice indicating the coordinate
 132      *                  system the Robot will operate in.
 133      * @throws  AWTException if the platform configuration does not allow
 134      * low-level input control.  This exception is always thrown when
 135      * GraphicsEnvironment.isHeadless() returns true.
 136      * @throws  IllegalArgumentException if <code>screen</code> is not a screen
 137      *          GraphicsDevice.
 138      * @throws  SecurityException if <code>createRobot</code> permission is not granted
 139      * @see     java.awt.GraphicsEnvironment#isHeadless
 140      * @see     GraphicsDevice
 141      * @see     SecurityManager#checkPermission
 142      * @see     AWTPermission
 143      */
 144     public Robot(GraphicsDevice screen) throws AWTException {
 145         checkIsScreenDevice(screen);
 146         init(screen);
 147     }
 148 
 149     private void init(GraphicsDevice screen) throws AWTException {
 150         checkRobotAllowed();
 151         Toolkit toolkit = Toolkit.getDefaultToolkit();
 152         if (toolkit instanceof ComponentFactory) {
 153             peer = ((ComponentFactory)toolkit).createRobot(this, screen);
 154             disposer = new RobotDisposer(peer);
 155             sun.java2d.Disposer.addRecord(anchor, disposer);
 156         }
 157         initLegalButtonMask();
 158     }
 159 
 160     private static synchronized void initLegalButtonMask() {
 161         if (LEGAL_BUTTON_MASK != 0) return;
 162 
 163         int tmpMask = 0;
 164         if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){
 165             if (Toolkit.getDefaultToolkit() instanceof SunToolkit) {
 166                 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons();
 167                 for (int i = 0; i < buttonsNumber; i++){
 168                     tmpMask |= InputEvent.getMaskForButton(i+1);
 169                 }
 170             }
 171         }
 172         tmpMask |= InputEvent.BUTTON1_MASK|
 173             InputEvent.BUTTON2_MASK|
 174             InputEvent.BUTTON3_MASK|
 175             InputEvent.BUTTON1_DOWN_MASK|
 176             InputEvent.BUTTON2_DOWN_MASK|
 177             InputEvent.BUTTON3_DOWN_MASK;
 178         LEGAL_BUTTON_MASK = tmpMask;
 179     }
 180 
 181     /* determine if the security policy allows Robot's to be created */
 182     private void checkRobotAllowed() {
 183         SecurityManager security = System.getSecurityManager();
 184         if (security != null) {
 185             security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION);
 186         }
 187     }
 188 
 189     /* check if the given device is a screen device */
 190     private void checkIsScreenDevice(GraphicsDevice device) {
 191         if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
 192             throw new IllegalArgumentException("not a valid screen device");
 193         }
 194     }
 195 
 196     private transient Object anchor = new Object();
 197 
 198     static class RobotDisposer implements sun.java2d.DisposerRecord {
 199         private final RobotPeer peer;
 200         public RobotDisposer(RobotPeer peer) {
 201             this.peer = peer;
 202         }
 203         public void dispose() {
 204             if (peer != null) {
 205                 peer.dispose();
 206             }
 207         }
 208     }
 209 
 210     private transient RobotDisposer disposer;
 211 
 212     /**
 213      * Moves mouse pointer to given screen coordinates.
 214      * @param x         X position
 215      * @param y         Y position
 216      */
 217     public synchronized void mouseMove(int x, int y) {
 218         peer.mouseMove(x, y);
 219         afterEvent();
 220     }
 221 
 222     /**
 223      * Moves mouse pointer to given screen coordinates.
 224      *
 225      * @param   position    Target position
 226      *
 227      * @see     #mouseMove(int, int)
 228      */
 229     public synchronized void mouseMove(Point position) {
 230         mouseMove(position.x, position.y);
 231     }
 232 
 233     /**
 234      * Move mouse cursor to the center of the Component.
 235      *
 236      * @param   c   Component the mouse is placed over
 237      *
 238      * @see     #mouseMove(int, int)
 239      */
 240     public synchronized void mouseMove(Component c) {
 241         Point p = c.getLocationOnScreen();
 242         Dimension size = c.getSize();
 243         p.x += size.width / 2;
 244         p.y += size.height / 2;
 245         mouseMove(p.x, p.y);
 246     }
 247 
 248     /**
 249      * Move the mouse in multiple steps from where it is
 250      * now to the destination coordinates.
 251      *
 252      * @param   x   Destination point x coordinate
 253      * @param   y   Destination point y coordinate
 254      *
 255      * @see     #glide(int, int, int, int)
 256      */
 257     public void glide(int x, int y) {
 258         Point p = MouseInfo.getPointerInfo().getLocation();
 259         glide(p.x, p.y, x, y);
 260     }
 261 
 262     /**
 263      * Move the mouse in multiple steps from where it is
 264      * now to the destination point.
 265      *
 266      * @param   dest    Destination point
 267      *
 268      * @see     #glide(int, int)
 269      */
 270     public void glide(Point dest) {
 271         glide(dest.x, dest.y);
 272     }
 273 
 274     /**
 275      * Move the mouse in multiple steps from source coordinates
 276      * to the destination coordinates.
 277      *
 278      * @param   fromX   Source point x coordinate
 279      * @param   fromY   Source point y coordinate
 280      * @param   toX     Destination point x coordinate
 281      * @param   toY     Destination point y coordinate
 282      *
 283      * @see     #glide(int, int, int, int, int, int)
 284      */
 285     public void glide(int fromX, int fromY, int toX, int toY) {
 286         glide(fromX, fromY, toX, toY, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
 287     }
 288 
 289     /**
 290      * Move the mouse in multiple steps from source point to the
 291      * destination point with default speed and step length.
 292      *
 293      * @param   src     Source point
 294      * @param   dest    Destination point
 295      *
 296      * @see     #glide(int, int, int, int, int, int)
 297      */
 298     public void glide(Point src, Point dest) {
 299         glide(src.x, src.y, dest.x, dest.y, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
 300     }
 301 
 302     /**
 303      * Move the mouse in multiple steps from source point to the
 304      * destination point with given speed and step length.
 305      *
 306      * @param   srcX        Source point x cordinate
 307      * @param   srcY        Source point y cordinate
 308      * @param   destX       Destination point x cordinate
 309      * @param   destY       Destination point y cordinate
 310      * @param   stepLength  Approximate length of one step
 311      * @param   speed       Delay between steps.
 312      *
 313      * @see     #mouseMove(int, int)
 314      * @see     #delay(int)
 315      */
 316     public void glide(int srcX, int srcY, int destX, int destY, int stepLength, int speed) {
 317         int stepNum;
 318         double tDx, tDy;
 319         double dx, dy, ds;
 320         double x, y;
 321 
 322         dx = (destX - srcX);
 323         dy = (destY - srcY);
 324         ds = Math.sqrt(dx*dx + dy*dy);
 325 
 326         tDx = dx / ds * stepLength;
 327         tDy = dy / ds * stepLength;
 328 
 329         int stepsCount = (int) ds / stepLength;
 330 
 331         // Walk the mouse to the destination one step at a time
 332         mouseMove(srcX, srcY);
 333 
 334         for (x = srcX, y = srcY, stepNum = 0;
 335              stepNum < stepsCount;
 336              stepNum++) {
 337             x += tDx;
 338             y += tDy;
 339             mouseMove((int)x, (int)y);
 340             delay(speed);
 341         }
 342 
 343         // Ensure the mouse moves to the right destination.
 344         // The steps may have led the mouse to a slightly wrong place.
 345         mouseMove(destX, destY);
 346     }
 347 
 348     /**
 349      * Presses one or more mouse buttons.  The mouse buttons should
 350      * be released using the {@link #mouseRelease(int)} method.
 351      *
 352      * @param buttons the Button mask; a combination of one or more
 353      * mouse button masks.
 354      * <p>
 355      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 356      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 357      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 358      * and values returned by the
 359      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 360      *
 361      * The valid combination also depends on a
 362      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 363      * <ul>
 364      * <li> If support for extended mouse buttons is
 365      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 366      * then it is allowed to use only the following standard button masks:
 367      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 368      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 369      * <li> If support for extended mouse buttons is
 370      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 371      * then it is allowed to use the standard button masks
 372      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 373      * In that way, it is allowed to use the button masks corresponding to the buttons
 374      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 375      * <br>
 376      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 377      * method to obtain the mask for any mouse button by its number.
 378      * </ul>
 379      * <p>
 380      * The following standard button masks are also accepted:
 381      * <ul>
 382      * <li>{@code InputEvent.BUTTON1_MASK}
 383      * <li>{@code InputEvent.BUTTON2_MASK}
 384      * <li>{@code InputEvent.BUTTON3_MASK}
 385      * </ul>
 386      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 387      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 388      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 389      * should be used, but both those models should not be mixed.
 390      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 391      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 392      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 393      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 394      * @see #mouseRelease(int)
 395      * @see InputEvent#getMaskForButton(int)
 396      * @see Toolkit#areExtraMouseButtonsEnabled()
 397      * @see java.awt.MouseInfo#getNumberOfButtons()
 398      * @see java.awt.event.MouseEvent
 399      */
 400     public synchronized void mousePress(int buttons) {
 401         checkButtonsArgument(buttons);
 402         peer.mousePress(buttons);
 403         afterEvent();
 404     }
 405 
 406     /**
 407      * Releases one or more mouse buttons.
 408      *
 409      * @param buttons the Button mask; a combination of one or more
 410      * mouse button masks.
 411      * <p>
 412      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 413      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 414      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 415      * and values returned by the
 416      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 417      *
 418      * The valid combination also depends on a
 419      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 420      * <ul>
 421      * <li> If the support for extended mouse buttons is
 422      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 423      * then it is allowed to use only the following standard button masks:
 424      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 425      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 426      * <li> If the support for extended mouse buttons is
 427      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 428      * then it is allowed to use the standard button masks
 429      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 430      * In that way, it is allowed to use the button masks corresponding to the buttons
 431      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 432      * <br>
 433      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 434      * method to obtain the mask for any mouse button by its number.
 435      * </ul>
 436      * <p>
 437      * The following standard button masks are also accepted:
 438      * <ul>
 439      * <li>{@code InputEvent.BUTTON1_MASK}
 440      * <li>{@code InputEvent.BUTTON2_MASK}
 441      * <li>{@code InputEvent.BUTTON3_MASK}
 442      * </ul>
 443      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 444      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 445      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 446      * should be used, but both those models should not be mixed.
 447      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 448      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 449      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 450      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 451      * @see #mousePress(int)
 452      * @see InputEvent#getMaskForButton(int)
 453      * @see Toolkit#areExtraMouseButtonsEnabled()
 454      * @see java.awt.MouseInfo#getNumberOfButtons()
 455      * @see java.awt.event.MouseEvent
 456      */
 457     public synchronized void mouseRelease(int buttons) {
 458         checkButtonsArgument(buttons);
 459         peer.mouseRelease(buttons);
 460         afterEvent();
 461     }
 462 
 463     /**
 464      * Clicks mouse button(s) by calling {@link #mousePress(int)} and
 465      * {@link #mouseRelease(int)} methods
 466      *
 467      *
 468      * @param   buttons The button mask; a combination of one or more mouse button masks.
 469      * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
 470      *          extra mouse button and support for extended mouse buttons is
 471      *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 472      * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
 473      *          extra mouse button that does not exist on the mouse and support for extended
 474      *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
 475      *          by Java
 476      *
 477      * @see     #mousePress(int)
 478      * @see     #mouseRelease(int)
 479      * @see     InputEvent#getMaskForButton(int)
 480      * @see     Toolkit#areExtraMouseButtonsEnabled()
 481      * @see     java.awt.event.MouseEvent
 482      */
 483     public void click(int buttons) {
 484         mousePress(buttons);
 485         waitForIdle(DEFAULT_SPEED);
 486         mouseRelease(buttons);
 487         waitForIdle();
 488     }
 489 
 490     /**
 491      * Clicks mouse button 1
 492      *
 493      * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
 494      *          extra mouse button and support for extended mouse buttons is
 495      *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 496      * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
 497      *          extra mouse button that does not exist on the mouse and support for extended
 498      *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
 499      *          by Java
 500      *
 501      * @see     #click(int)
 502      */
 503     public void click() {
 504         click(InputEvent.BUTTON1_DOWN_MASK);
 505     }
 506 
 507     /**
 508      * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}.
 509      * The method successively moves mouse cursor to point with coordinates
 510      * ({@code fromX}, {@code fromY}), presses mouse button 1, drag mouse to
 511      * point with coordinates ({@code toX}, {@code toY}) and releases mouse
 512      * button 1 at last.
 513      *
 514      * @param   fromX   Source point x coordinate
 515      * @param   fromY   Source point y coordinate
 516      * @param   toX     Destination point x coordinate
 517      * @param   toY     Destination point y coordinate
 518      *
 519      * @see     #mousePress(int)
 520      * @see     #glide(int, int, int, int)
 521      */
 522     public void dragAndDrop(int fromX, int fromY, int toX, int toY) {
 523         mouseMove(fromX, fromY);
 524         mousePress(InputEvent.BUTTON1_DOWN_MASK);
 525         waitForIdle();
 526         glide(toX, toY);
 527         mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
 528         waitForIdle();
 529     }
 530 
 531     /**
 532      * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}.
 533      * The method successively moves mouse cursor to point {@code from},
 534      * presses mouse button 1, drag mouse to point {@code to} and releases
 535      * mouse button 1 at last.
 536      *
 537      * @param   from    Source point
 538      * @param   to      Destination point
 539      *
 540      * @see     #mousePress(int)
 541      * @see     #glide(int, int, int, int)
 542      * @see     #dragAndDrop(int, int, int, int)
 543      */
 544     public void dragAndDrop(Point from, Point to) {
 545         dragAndDrop(from.x, from.y, to.x, to.y);
 546     }
 547 
 548     private void checkButtonsArgument(int buttons) {
 549         if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) {
 550             throw new IllegalArgumentException("Invalid combination of button flags");
 551         }
 552     }
 553 
 554     /**
 555      * Rotates the scroll wheel on wheel-equipped mice.
 556      *
 557      * @param wheelAmt  number of "notches" to move the mouse wheel
 558      *                  Negative values indicate movement up/away from the user,
 559      *                  positive values indicate movement down/towards the user.
 560      *
 561      * @since 1.4
 562      */
 563     public synchronized void mouseWheel(int wheelAmt) {
 564         peer.mouseWheel(wheelAmt);
 565         afterEvent();
 566     }
 567 
 568     /**
 569      * Presses a given key.  The key should be released using the
 570      * <code>keyRelease</code> method.
 571      * <p>
 572      * Key codes that have more than one physical key associated with them
 573      * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
 574      * left or right shift key) will map to the left key.
 575      *
 576      * @param   keycode Key to press (e.g. <code>KeyEvent.VK_A</code>)
 577      * @throws  IllegalArgumentException if <code>keycode</code> is not
 578      *          a valid key
 579      * @see     #keyRelease(int)
 580      * @see     java.awt.event.KeyEvent
 581      */
 582     public synchronized void keyPress(int keycode) {
 583         checkKeycodeArgument(keycode);
 584         peer.keyPress(keycode);
 585         afterEvent();
 586     }
 587 
 588     /**
 589      * Releases a given key.
 590      * <p>
 591      * Key codes that have more than one physical key associated with them
 592      * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
 593      * left or right shift key) will map to the left key.
 594      *
 595      * @param   keycode Key to release (e.g. <code>KeyEvent.VK_A</code>)
 596      * @throws  IllegalArgumentException if <code>keycode</code> is not a
 597      *          valid key
 598      * @see  #keyPress(int)
 599      * @see     java.awt.event.KeyEvent
 600      */
 601     public synchronized void keyRelease(int keycode) {
 602         checkKeycodeArgument(keycode);
 603         peer.keyRelease(keycode);
 604         afterEvent();
 605     }
 606 
 607     /**
 608      * Successively presses and releases a given key.
 609      * <p>
 610      * Key codes that have more than one physical key associated with them
 611      * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
 612      * left or right shift key) will map to the left key.
 613      *
 614      * @param   keycode Key to press (e.g. {@code KeyEvent.VK_A})
 615      * @throws  IllegalArgumentException if {@code keycode} is not
 616      *          a valid key
 617      *
 618      * @see     #keyPress(int)
 619      * @see     #keyRelease(int)
 620      * @see     java.awt.event.KeyEvent
 621      */
 622     public void type(int keycode) {
 623         keyPress(keycode);
 624         waitForIdle(DEFAULT_SPEED);
 625         keyRelease(keycode);
 626         waitForIdle();
 627     }
 628 
 629     /**
 630      * Types given character
 631      *
 632      * @param   c   Character to be typed (e.g. {@code 'a'})
 633      *
 634      * @see     #type(int)
 635      * @see     java.awt.event.KeyEvent
 636      */
 637     public void type(char c) {
 638         type(ExtendedKeyCodes.getExtendedKeyCodeForChar(c));
 639     }
 640 
 641     /**
 642      * Types given array of characters one by one
 643      *
 644      * @param   symbols Array of characters to be typed
 645      *
 646      * @see     #type(char)
 647      */
 648     public void type(char[] symbols) {
 649         for (int i = 0; i < symbols.length; i++) {
 650             type(symbols[i]);
 651         }
 652     }
 653 
 654     /**
 655      * Types given string
 656      *
 657      * @param   s   String to be typed
 658      *
 659      * @see     #type(char[])
 660      */
 661     public void type(String s) {
 662         type(s.toCharArray());
 663     }
 664 
 665     private void checkKeycodeArgument(int keycode) {
 666         // rather than build a big table or switch statement here, we'll
 667         // just check that the key isn't VK_UNDEFINED and assume that the
 668         // peer implementations will throw an exception for other bogus
 669         // values e.g. -1, 999999
 670         if (keycode == KeyEvent.VK_UNDEFINED) {
 671             throw new IllegalArgumentException("Invalid key code");
 672         }
 673     }
 674 
 675     /**
 676      * Returns the color of a pixel at the given screen coordinates.
 677      * @param   x       X position of pixel
 678      * @param   y       Y position of pixel
 679      * @return  Color of the pixel
 680      */
 681     public synchronized Color getPixelColor(int x, int y) {
 682         Color color = new Color(peer.getRGBPixel(x, y));
 683         return color;
 684     }
 685 
 686     /**
 687      * Creates an image containing pixels read from the screen.  This image does
 688      * not include the mouse cursor.
 689      * @param   screenRect      Rect to capture in screen coordinates
 690      * @return  The captured image
 691      * @throws  IllegalArgumentException if <code>screenRect</code> width and height are not greater than zero
 692      * @throws  SecurityException if <code>readDisplayPixels</code> permission is not granted
 693      * @see     SecurityManager#checkPermission
 694      * @see     AWTPermission
 695      */
 696     public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
 697         checkScreenCaptureAllowed();
 698 
 699         checkValidRect(screenRect);
 700 
 701         BufferedImage image;
 702         DataBufferInt buffer;
 703         WritableRaster raster;
 704 
 705         if (screenCapCM == null) {
 706             /*
 707              * Fix for 4285201
 708              * Create a DirectColorModel equivalent to the default RGB ColorModel,
 709              * except with no Alpha component.
 710              */
 711 
 712             screenCapCM = new DirectColorModel(24,
 713                                                /* red mask */    0x00FF0000,
 714                                                /* green mask */  0x0000FF00,
 715                                                /* blue mask */   0x000000FF);
 716         }
 717 
 718         // need to sync the toolkit prior to grabbing the pixels since in some
 719         // cases rendering to the screen may be delayed
 720         Toolkit.getDefaultToolkit().sync();
 721 
 722         int pixels[];
 723         int[] bandmasks = new int[3];
 724 
 725         pixels = peer.getRGBPixels(screenRect);
 726         buffer = new DataBufferInt(pixels, pixels.length);
 727 
 728         bandmasks[0] = screenCapCM.getRedMask();
 729         bandmasks[1] = screenCapCM.getGreenMask();
 730         bandmasks[2] = screenCapCM.getBlueMask();
 731 
 732         raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
 733         SunWritableRaster.makeTrackable(buffer);
 734 
 735         image = new BufferedImage(screenCapCM, raster, false, null);
 736 
 737         return image;
 738     }
 739 
 740     private static void checkValidRect(Rectangle rect) {
 741         if (rect.width <= 0 || rect.height <= 0) {
 742             throw new IllegalArgumentException("Rectangle width and height must be > 0");
 743         }
 744     }
 745 
 746     private static void checkScreenCaptureAllowed() {
 747         SecurityManager security = System.getSecurityManager();
 748         if (security != null) {
 749             security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION);
 750         }
 751     }
 752 
 753     /*
 754      * Called after an event is generated
 755      */
 756     private void afterEvent() {
 757         autoWaitForIdle();
 758         autoDelay();
 759     }
 760 
 761     /**
 762      * Returns whether this Robot automatically invokes <code>waitForIdle</code>
 763      * after generating an event.
 764      * @return Whether <code>waitForIdle</code> is automatically called
 765      */
 766     public synchronized boolean isAutoWaitForIdle() {
 767         return isAutoWaitForIdle;
 768     }
 769 
 770     /**
 771      * Sets whether this Robot automatically invokes <code>waitForIdle</code>
 772      * after generating an event.
 773      * @param   isOn    Whether <code>waitForIdle</code> is automatically invoked
 774      */
 775     public synchronized void setAutoWaitForIdle(boolean isOn) {
 776         isAutoWaitForIdle = isOn;
 777     }
 778 
 779     /*
 780      * Calls waitForIdle after every event if so desired.
 781      */
 782     private void autoWaitForIdle() {
 783         if (isAutoWaitForIdle) {
 784             waitForIdle();
 785         }
 786     }
 787 
 788     /**
 789      * Returns delay length for {@link #waitForIdle()} method
 790      *
 791      * @return  Current delay value
 792      *
 793      * @see     #waitForIdle()
 794      */
 795     public int getSyncDelay() {
 796         return this.syncDelay;
 797     }
 798 
 799     /**
 800      * Returns the number of milliseconds this Robot sleeps after generating an event.
 801      *
 802      * @return the delay duration in milliseconds
 803      */
 804     public synchronized int getAutoDelay() {
 805         return autoDelay;
 806     }
 807 
 808     /**
 809      * Sets the number of milliseconds this Robot sleeps after generating an event.
 810      *
 811      * @param  ms the delay duration in milliseconds
 812      * @throws IllegalArgumentException If {@code ms}
 813      *         is not between 0 and 60,000 milliseconds inclusive
 814      */
 815     public synchronized void setAutoDelay(int ms) {
 816         checkDelayArgument(ms);
 817         autoDelay = ms;
 818     }
 819 
 820     /*
 821      * Automatically sleeps for the specified interval after event generated.
 822      */
 823     private void autoDelay() {
 824         delay(autoDelay);
 825     }
 826 
 827     /**
 828      * Sleeps for the specified time.
 829      * To catch any <code>InterruptedException</code>s that occur,
 830      * <code>Thread.sleep()</code> may be used instead.
 831      *
 832      * @param  ms time to sleep in milliseconds
 833      * @throws IllegalArgumentException if {@code ms}
 834      *         is not between 0 and 60,000 milliseconds inclusive
 835      * @see java.lang.Thread#sleep
 836      */
 837     public synchronized void delay(int ms) {
 838         checkDelayArgument(ms);
 839         try {
 840             Thread.sleep(ms);
 841         } catch(InterruptedException ite) {
 842             ite.printStackTrace();
 843         }
 844     }
 845 
 846     private void checkDelayArgument(int ms) {
 847         if (ms < 0 || ms > MAX_DELAY) {
 848             throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
 849         }
 850     }
 851 
 852     /**
 853      * Waits until all events currently on the event queue have been processed with given
 854      * delay after syncing threads.
 855      *
 856      * @param   delayValue  Additional delay length in milliseconds to wait until thread
 857      *                      sync been completed
 858      * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
 859      *          dispatching thread
 860      */
 861     public synchronized void waitForIdle(int delayValue) {
 862         SunToolkit.flushPendingEvents();
 863         ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
 864         delay(delayValue);
 865     }
 866 
 867     /**
 868      * Static method for synchronizing event queue with current thread. Waits until
 869      * all events currently on the event queue have been processed with a small delay
 870      * of 500 ms after syncing threads.
 871      *
 872      * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
 873      *          dispatching thread
 874      */
 875     public static synchronized void syncEventQueue() {
 876         SunToolkit.flushPendingEvents();
 877         ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
 878         try {
 879             Thread.sleep(DEFAULT_SYNC_DELAY);
 880         } catch (InterruptedException ite) {
 881             ite.printStackTrace();
 882         }
 883     }
 884 
 885     /**
 886      * Waits until all events currently on the event queue have been processed with delay
 887      * {@link #getSyncDelay()} after syncing threads.
 888      *
 889      * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
 890      *          dispatching thread
 891      *
 892      * @see     #waitForIdle(int)
 893      */
 894     public synchronized void waitForIdle() {
 895         waitForIdle(syncDelay);
 896     }
 897 
 898     private void checkNotDispatchThread() {
 899         if (EventQueue.isDispatchThread()) {
 900             throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
 901         }
 902     }
 903 
 904     /**
 905      * Returns a string representation of this Robot.
 906      *
 907      * @return  the string representation.
 908      */
 909     public synchronized String toString() {
 910         String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
 911         return getClass().getName() + "[ " + params + " ]";
 912     }
 913 }