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.lang.reflect.InvocationTargetException;
  37 import java.security.AccessController;
  38 
  39 import sun.awt.AWTPermissions;
  40 import sun.awt.ComponentFactory;
  41 import sun.awt.SunToolkit;
  42 import sun.awt.OSInfo;
  43 import sun.awt.image.SunWritableRaster;
  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  * Note that some platforms require special privileges or extensions
  59  * to access low-level input control. If the current platform configuration
  60  * does not allow input control, an <code>AWTException</code> will be thrown
  61  * when trying to construct Robot objects. For example, X-Window systems
  62  * will throw the exception if the XTEST 2.2 standard extension is not supported
  63  * (or not enabled) by the X server.
  64  * <p>
  65  * Applications that use Robot for purposes other than self-testing should
  66  * handle these error conditions gracefully.
  67  *
  68  * @author      Robi Khan
  69  * @since       1.3
  70  */
  71 public class Robot {
  72     private static final int MAX_DELAY = 60000;
  73     private RobotPeer peer;
  74     private boolean isAutoWaitForIdle = false;
  75     private int autoDelay = 0;
  76     private static int LEGAL_BUTTON_MASK = 0;
  77 
  78     private DirectColorModel screenCapCM = null;
  79 
  80     /**
  81      * Constructs a Robot object in the coordinate system of the primary screen.
  82      *
  83      * @throws  AWTException if the platform configuration does not allow
  84      * low-level input control.  This exception is always thrown when
  85      * GraphicsEnvironment.isHeadless() returns true
  86      * @throws  SecurityException if <code>createRobot</code> permission is not granted
  87      * @see     java.awt.GraphicsEnvironment#isHeadless
  88      * @see     SecurityManager#checkPermission
  89      * @see     AWTPermission
  90      */
  91     public Robot() throws AWTException {
  92         if (GraphicsEnvironment.isHeadless()) {
  93             throw new AWTException("headless environment");
  94         }
  95         init(GraphicsEnvironment.getLocalGraphicsEnvironment()
  96             .getDefaultScreenDevice());
  97     }
  98 
  99     /**
 100      * Creates a Robot for the given screen device. Coordinates passed
 101      * to Robot method calls like mouseMove and createScreenCapture will
 102      * be interpreted as being in the same coordinate system as the
 103      * specified screen. Note that depending on the platform configuration,
 104      * multiple screens may either:
 105      * <ul>
 106      * <li>share the same coordinate system to form a combined virtual screen</li>
 107      * <li>use different coordinate systems to act as independent screens</li>
 108      * </ul>
 109      * This constructor is meant for the latter case.
 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</code> is not a screen
 120      *          GraphicsDevice.
 121      * @throws  SecurityException if <code>createRobot</code> permission is not granted
 122      * @see     java.awt.GraphicsEnvironment#isHeadless
 123      * @see     GraphicsDevice
 124      * @see     SecurityManager#checkPermission
 125      * @see     AWTPermission
 126      */
 127     public Robot(GraphicsDevice screen) throws AWTException {
 128         checkIsScreenDevice(screen);
 129         init(screen);
 130     }
 131 
 132     private void init(GraphicsDevice screen) throws AWTException {
 133         checkRobotAllowed();
 134         Toolkit toolkit = Toolkit.getDefaultToolkit();
 135         if (toolkit instanceof ComponentFactory) {
 136             peer = ((ComponentFactory)toolkit).createRobot(this, screen);
 137             disposer = new RobotDisposer(peer);
 138             sun.java2d.Disposer.addRecord(anchor, disposer);
 139         }
 140         initLegalButtonMask();
 141     }
 142 
 143     private static synchronized void initLegalButtonMask() {
 144         if (LEGAL_BUTTON_MASK != 0) return;
 145 
 146         int tmpMask = 0;
 147         if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){
 148             if (Toolkit.getDefaultToolkit() instanceof SunToolkit) {
 149                 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons();
 150                 for (int i = 0; i < buttonsNumber; i++){
 151                     tmpMask |= InputEvent.getMaskForButton(i+1);
 152                 }
 153             }
 154         }
 155         tmpMask |= InputEvent.BUTTON1_MASK|
 156             InputEvent.BUTTON2_MASK|
 157             InputEvent.BUTTON3_MASK|
 158             InputEvent.BUTTON1_DOWN_MASK|
 159             InputEvent.BUTTON2_DOWN_MASK|
 160             InputEvent.BUTTON3_DOWN_MASK;
 161         LEGAL_BUTTON_MASK = tmpMask;
 162     }
 163 
 164     /* determine if the security policy allows Robot's to be created */
 165     private void checkRobotAllowed() {
 166         SecurityManager security = System.getSecurityManager();
 167         if (security != null) {
 168             security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION);
 169         }
 170     }
 171 
 172     /* check if the given device is a screen device */
 173     private void checkIsScreenDevice(GraphicsDevice device) {
 174         if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
 175             throw new IllegalArgumentException("not a valid screen device");
 176         }
 177     }
 178 
 179     private transient Object anchor = new Object();
 180 
 181     static class RobotDisposer implements sun.java2d.DisposerRecord {
 182         private final RobotPeer peer;
 183         public RobotDisposer(RobotPeer peer) {
 184             this.peer = peer;
 185         }
 186         public void dispose() {
 187             if (peer != null) {
 188                 peer.dispose();
 189             }
 190         }
 191     }
 192 
 193     private transient RobotDisposer disposer;
 194 
 195     /**
 196      * Moves mouse pointer to given screen coordinates.
 197      * @param x         X position
 198      * @param y         Y position
 199      */
 200     public synchronized void mouseMove(int x, int y) {
 201         peer.mouseMove(x, y);
 202         afterEvent();
 203     }
 204 
 205     /**
 206      * Presses one or more mouse buttons.  The mouse buttons should
 207      * be released using the {@link #mouseRelease(int)} method.
 208      *
 209      * @param buttons the Button mask; a combination of one or more
 210      * mouse button masks.
 211      * <p>
 212      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 213      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 214      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 215      * and values returned by the
 216      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 217      *
 218      * The valid combination also depends on a
 219      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 220      * <ul>
 221      * <li> If support for extended mouse buttons is
 222      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 223      * then it is allowed to use only the following standard button masks:
 224      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 225      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 226      * <li> If support for extended mouse buttons is
 227      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 228      * then it is allowed to use the standard button masks
 229      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 230      * In that way, it is allowed to use the button masks corresponding to the buttons
 231      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 232      * <br>
 233      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 234      * method to obtain the mask for any mouse button by its number.
 235      * </ul>
 236      * <p>
 237      * The following standard button masks are also accepted:
 238      * <ul>
 239      * <li>{@code InputEvent.BUTTON1_MASK}
 240      * <li>{@code InputEvent.BUTTON2_MASK}
 241      * <li>{@code InputEvent.BUTTON3_MASK}
 242      * </ul>
 243      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 244      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 245      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 246      * should be used, but both those models should not be mixed.
 247      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 248      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 249      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 250      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 251      * @see #mouseRelease(int)
 252      * @see InputEvent#getMaskForButton(int)
 253      * @see Toolkit#areExtraMouseButtonsEnabled()
 254      * @see java.awt.MouseInfo#getNumberOfButtons()
 255      * @see java.awt.event.MouseEvent
 256      */
 257     public synchronized void mousePress(int buttons) {
 258         checkButtonsArgument(buttons);
 259         peer.mousePress(buttons);
 260         afterEvent();
 261     }
 262 
 263     /**
 264      * Releases one or more mouse buttons.
 265      *
 266      * @param buttons the Button mask; a combination of one or more
 267      * mouse button masks.
 268      * <p>
 269      * It is allowed to use only a combination of valid values as a {@code buttons} parameter.
 270      * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK},
 271      * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK}
 272      * and values returned by the
 273      * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method.
 274      *
 275      * The valid combination also depends on a
 276      * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows:
 277      * <ul>
 278      * <li> If the support for extended mouse buttons is
 279      * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 280      * then it is allowed to use only the following standard button masks:
 281      * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK},
 282      * {@code InputEvent.BUTTON3_DOWN_MASK}.
 283      * <li> If the support for extended mouse buttons is
 284      * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 285      * then it is allowed to use the standard button masks
 286      * and masks for existing extended mouse buttons, if the mouse has more then three buttons.
 287      * In that way, it is allowed to use the button masks corresponding to the buttons
 288      * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}.
 289      * <br>
 290      * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)}
 291      * method to obtain the mask for any mouse button by its number.
 292      * </ul>
 293      * <p>
 294      * The following standard button masks are also accepted:
 295      * <ul>
 296      * <li>{@code InputEvent.BUTTON1_MASK}
 297      * <li>{@code InputEvent.BUTTON2_MASK}
 298      * <li>{@code InputEvent.BUTTON3_MASK}
 299      * </ul>
 300      * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK},
 301      * {@code InputEvent.BUTTON2_DOWN_MASK},  {@code InputEvent.BUTTON3_DOWN_MASK} instead.
 302      * Either extended {@code _DOWN_MASK} or old {@code _MASK} values
 303      * should be used, but both those models should not be mixed.
 304      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 305      *         and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
 306      * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button
 307      *         that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
 308      * @see #mousePress(int)
 309      * @see InputEvent#getMaskForButton(int)
 310      * @see Toolkit#areExtraMouseButtonsEnabled()
 311      * @see java.awt.MouseInfo#getNumberOfButtons()
 312      * @see java.awt.event.MouseEvent
 313      */
 314     public synchronized void mouseRelease(int buttons) {
 315         checkButtonsArgument(buttons);
 316         peer.mouseRelease(buttons);
 317         afterEvent();
 318     }
 319 
 320     private void checkButtonsArgument(int buttons) {
 321         if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) {
 322             throw new IllegalArgumentException("Invalid combination of button flags");
 323         }
 324     }
 325 
 326     /**
 327      * Rotates the scroll wheel on wheel-equipped mice.
 328      *
 329      * @param wheelAmt  number of "notches" to move the mouse wheel
 330      *                  Negative values indicate movement up/away from the user,
 331      *                  positive values indicate movement down/towards the user.
 332      *
 333      * @since 1.4
 334      */
 335     public synchronized void mouseWheel(int wheelAmt) {
 336         peer.mouseWheel(wheelAmt);
 337         afterEvent();
 338     }
 339 
 340     /**
 341      * Presses a given key.  The key should be released using the
 342      * <code>keyRelease</code> method.
 343      * <p>
 344      * Key codes that have more than one physical key associated with them
 345      * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
 346      * left or right shift key) will map to the left key.
 347      *
 348      * @param   keycode Key to press (e.g. <code>KeyEvent.VK_A</code>)
 349      * @throws  IllegalArgumentException if <code>keycode</code> is not
 350      *          a valid key
 351      * @see     #keyRelease(int)
 352      * @see     java.awt.event.KeyEvent
 353      */
 354     public synchronized void keyPress(int keycode) {
 355         checkKeycodeArgument(keycode);
 356         peer.keyPress(keycode);
 357         afterEvent();
 358     }
 359 
 360     /**
 361      * Releases a given key.
 362      * <p>
 363      * Key codes that have more than one physical key associated with them
 364      * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
 365      * left or right shift key) will map to the left key.
 366      *
 367      * @param   keycode Key to release (e.g. <code>KeyEvent.VK_A</code>)
 368      * @throws  IllegalArgumentException if <code>keycode</code> is not a
 369      *          valid key
 370      * @see  #keyPress(int)
 371      * @see     java.awt.event.KeyEvent
 372      */
 373     public synchronized void keyRelease(int keycode) {
 374         checkKeycodeArgument(keycode);
 375         peer.keyRelease(keycode);
 376         afterEvent();
 377     }
 378 
 379     private void checkKeycodeArgument(int keycode) {
 380         // rather than build a big table or switch statement here, we'll
 381         // just check that the key isn't VK_UNDEFINED and assume that the
 382         // peer implementations will throw an exception for other bogus
 383         // values e.g. -1, 999999
 384         if (keycode == KeyEvent.VK_UNDEFINED) {
 385             throw new IllegalArgumentException("Invalid key code");
 386         }
 387     }
 388 
 389     /**
 390      * Returns the color of a pixel at the given screen coordinates.
 391      * @param   x       X position of pixel
 392      * @param   y       Y position of pixel
 393      * @return  Color of the pixel
 394      */
 395     public synchronized Color getPixelColor(int x, int y) {
 396         Color color = new Color(peer.getRGBPixel(x, y));
 397         return color;
 398     }
 399 
 400     /**
 401      * Creates an image containing pixels read from the screen.  This image does
 402      * not include the mouse cursor.
 403      * @param   screenRect      Rect to capture in screen coordinates
 404      * @return  The captured image
 405      * @throws  IllegalArgumentException if <code>screenRect</code> width and height are not greater than zero
 406      * @throws  SecurityException if <code>readDisplayPixels</code> permission is not granted
 407      * @see     SecurityManager#checkPermission
 408      * @see     AWTPermission
 409      */
 410     public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
 411         checkScreenCaptureAllowed();
 412 
 413         checkValidRect(screenRect);
 414 
 415         BufferedImage image;
 416         DataBufferInt buffer;
 417         WritableRaster raster;
 418 
 419         if (screenCapCM == null) {
 420             /*
 421              * Fix for 4285201
 422              * Create a DirectColorModel equivalent to the default RGB ColorModel,
 423              * except with no Alpha component.
 424              */
 425 
 426             screenCapCM = new DirectColorModel(24,
 427                                                /* red mask */    0x00FF0000,
 428                                                /* green mask */  0x0000FF00,
 429                                                /* blue mask */   0x000000FF);
 430         }
 431 
 432         // need to sync the toolkit prior to grabbing the pixels since in some
 433         // cases rendering to the screen may be delayed
 434         Toolkit.getDefaultToolkit().sync();
 435 
 436         int pixels[];
 437         int[] bandmasks = new int[3];
 438 
 439         pixels = peer.getRGBPixels(screenRect);
 440         buffer = new DataBufferInt(pixels, pixels.length);
 441 
 442         bandmasks[0] = screenCapCM.getRedMask();
 443         bandmasks[1] = screenCapCM.getGreenMask();
 444         bandmasks[2] = screenCapCM.getBlueMask();
 445 
 446         raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
 447         SunWritableRaster.makeTrackable(buffer);
 448 
 449         image = new BufferedImage(screenCapCM, raster, false, null);
 450 
 451         return image;
 452     }
 453 
 454     private static void checkValidRect(Rectangle rect) {
 455         if (rect.width <= 0 || rect.height <= 0) {
 456             throw new IllegalArgumentException("Rectangle width and height must be > 0");
 457         }
 458     }
 459 
 460     private static void checkScreenCaptureAllowed() {
 461         SecurityManager security = System.getSecurityManager();
 462         if (security != null) {
 463             security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION);
 464         }
 465     }
 466 
 467     /*
 468      * Called after an event is generated
 469      */
 470     private void afterEvent() {
 471         autoWaitForIdle();
 472         autoDelay();
 473     }
 474 
 475     /**
 476      * Returns whether this Robot automatically invokes <code>waitForIdle</code>
 477      * after generating an event.
 478      * @return Whether <code>waitForIdle</code> is automatically called
 479      */
 480     public synchronized boolean isAutoWaitForIdle() {
 481         return isAutoWaitForIdle;
 482     }
 483 
 484     /**
 485      * Sets whether this Robot automatically invokes <code>waitForIdle</code>
 486      * after generating an event.
 487      * @param   isOn    Whether <code>waitForIdle</code> is automatically invoked
 488      */
 489     public synchronized void setAutoWaitForIdle(boolean isOn) {
 490         isAutoWaitForIdle = isOn;
 491     }
 492 
 493     /*
 494      * Calls waitForIdle after every event if so desired.
 495      */
 496     private void autoWaitForIdle() {
 497         if (isAutoWaitForIdle) {
 498             waitForIdle();
 499         }
 500     }
 501 
 502     /**
 503      * Returns the number of milliseconds this Robot sleeps after generating an event.
 504      *
 505      * @return the delay duration in milliseconds
 506      */
 507     public synchronized int getAutoDelay() {
 508         return autoDelay;
 509     }
 510 
 511     /**
 512      * Sets the number of milliseconds this Robot sleeps after generating an event.
 513      *
 514      * @param  ms the delay duration in milliseconds
 515      * @throws IllegalArgumentException If {@code ms}
 516      *         is not between 0 and 60,000 milliseconds inclusive
 517      */
 518     public synchronized void setAutoDelay(int ms) {
 519         checkDelayArgument(ms);
 520         autoDelay = ms;
 521     }
 522 
 523     /*
 524      * Automatically sleeps for the specified interval after event generated.
 525      */
 526     private void autoDelay() {
 527         delay(autoDelay);
 528     }
 529 
 530     /**
 531      * Sleeps for the specified time.
 532      * To catch any <code>InterruptedException</code>s that occur,
 533      * <code>Thread.sleep()</code> may be used instead.
 534      *
 535      * @param  ms time to sleep in milliseconds
 536      * @throws IllegalArgumentException if {@code ms}
 537      *         is not between 0 and 60,000 milliseconds inclusive
 538      * @see java.lang.Thread#sleep
 539      */
 540     public synchronized void delay(int ms) {
 541         checkDelayArgument(ms);
 542         try {
 543             Thread.sleep(ms);
 544         } catch(InterruptedException ite) {
 545             ite.printStackTrace();
 546         }
 547     }
 548 
 549     private void checkDelayArgument(int ms) {
 550         if (ms < 0 || ms > MAX_DELAY) {
 551             throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
 552         }
 553     }
 554 
 555     /**
 556      * Waits until all events currently on the event queue have been processed.
 557      * @throws  IllegalThreadStateException if called on the AWT event dispatching thread
 558      */
 559     public synchronized void waitForIdle() {
 560         checkNotDispatchThread();
 561 
 562         try {
 563             SunToolkit.flushPendingEvents();
 564             // 7185258: realSync() call blocks all DnD tests on OS X
 565             if (AccessController.doPrivileged(OSInfo.getOSTypeAction()) == OSInfo.OSType.MACOSX) {
 566                 // post a dummy event to the queue so we know when
 567                 // all the events before it have been processed
 568                 EventQueue.invokeAndWait( new Runnable() {
 569                                                 public void run() {
 570                                                     // dummy implementation
 571                                                 }
 572                                             } );
 573             } else {
 574                 ((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
 575             }
 576         } catch(InterruptedException ite) {
 577             System.err.println("Robot.waitForIdle, non-fatal exception caught:");
 578             ite.printStackTrace();
 579         } catch(InvocationTargetException ine) {
 580             System.err.println("Robot.waitForIdle, non-fatal exception caught:");
 581             ine.printStackTrace();
 582         }
 583     }
 584 
 585     private void checkNotDispatchThread() {
 586         if (EventQueue.isDispatchThread()) {
 587             throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
 588         }
 589     }
 590 
 591     /**
 592      * Returns a string representation of this Robot.
 593      *
 594      * @return  the string representation.
 595      */
 596     public synchronized String toString() {
 597         String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
 598         return getClass().getName() + "[ " + params + " ]";
 599     }
 600 }