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