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