001/* Robot.java -- a native input event generator
002   Copyright (C) 2004, 2005  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.awt;
040
041import gnu.java.awt.ClasspathToolkit;
042
043import java.lang.reflect.InvocationTargetException;
044import java.awt.event.InputEvent;
045import java.awt.image.BufferedImage;
046import java.awt.peer.RobotPeer;
047
048/**
049 * The Robot class is used to simulate user interaction with graphical
050 * programs.  It can generate native windowing system input events and
051 * retrieve image data from the current screen.  Robot is used to test
052 * the AWT and Swing library implementations; it can also be used to
053 * create self-running demo programs.
054 *
055 * Since Robot generates native windowing system events, rather than
056 * simply inserting {@link AWTEvent}s on the AWT event queue, its use
057 * is not restricted to Java programs.  It can be used to
058 * programatically drive any graphical application.
059 *
060 * This implementation requires an X server that supports the XTest
061 * extension.
062 *
063 * @author Thomas Fitzsimmons (fitzsim@redhat.com)
064 *
065 * @since 1.3
066 */
067public class Robot
068{
069  private boolean waitForIdle;
070  private int autoDelay;
071  private RobotPeer peer;
072
073  /**
074   * Construct a Robot object that operates on the default screen.
075   *
076   * @exception AWTException if GraphicsEnvironment.isHeadless()
077   * returns true or if the X server does not support the XTest
078   * extension
079   * @exception SecurityException if createRobot permission is not
080   * granted
081   */
082  public Robot () throws AWTException
083  {
084    if (GraphicsEnvironment.isHeadless ())
085      throw new AWTException ("Robot: headless graphics environment");
086
087    SecurityManager sm = System.getSecurityManager ();
088    if (sm != null)
089      sm.checkPermission (new AWTPermission ("createRobot"));
090
091    ClasspathToolkit tk = (ClasspathToolkit) Toolkit.getDefaultToolkit ();
092
093    // createRobot will throw AWTException if XTest is not supported.
094    peer = tk.createRobot (GraphicsEnvironment.getLocalGraphicsEnvironment ()
095                           .getDefaultScreenDevice ());
096  }
097
098  /**
099   * Construct a Robot object that operates on the specified screen.
100   *
101   * @exception AWTException if GraphicsEnvironment.isHeadless()
102   * returns true or if the X server does not support the XTest
103   * extension
104   * @exception IllegalArgumentException if screen is not a screen
105   * GraphicsDevice
106   * @exception SecurityException if createRobot permission is not
107   * granted
108   */
109  public Robot (GraphicsDevice screen) throws AWTException
110  {
111    if (GraphicsEnvironment.isHeadless ())
112      throw new AWTException ("Robot: headless graphics environment");
113
114    if (screen.getType () != GraphicsDevice.TYPE_RASTER_SCREEN)
115      throw new IllegalArgumentException ("Robot: graphics"
116                                          + " device is not a screen");
117
118    SecurityManager sm = System.getSecurityManager ();
119    if (sm != null)
120      sm.checkPermission (new AWTPermission ("createRobot"));
121
122    ClasspathToolkit tk = (ClasspathToolkit) Toolkit.getDefaultToolkit ();
123
124    // createRobot will throw AWTException if XTest is not supported.
125    peer = tk.createRobot (screen);
126  }
127
128  /**
129   * Move the mouse pointer to absolute coordinates (x, y).
130   *
131   * @param x the destination x coordinate
132   * @param y the destination y coordinate
133   */
134  public void mouseMove(int x, int y)
135  {
136    peer.mouseMove (x, y);
137
138    if (waitForIdle)
139      waitForIdle ();
140
141    if (autoDelay > 0)
142      delay (autoDelay);
143  }
144
145  /**
146   * Press one or more mouse buttons.
147   *
148   * @param buttons the buttons to press; a bitmask of one or more of
149   * these {@link InputEvent} fields:
150   *
151   * <ul>
152   *   <li>BUTTON1_MASK</li>
153   *   <li>BUTTON2_MASK</li>
154   *   <li>BUTTON3_MASK</li>
155   * </ul>
156   *
157   * @exception IllegalArgumentException if the button mask is invalid
158   */
159  public void mousePress (int buttons)
160  {
161    if ((buttons & InputEvent.BUTTON1_MASK) == 0
162        && (buttons & InputEvent.BUTTON2_MASK) == 0
163        && (buttons & InputEvent.BUTTON3_MASK) == 0)
164      throw new IllegalArgumentException ("Robot: mousePress:"
165                                          + " invalid button mask");
166
167    peer.mousePress (buttons);
168
169    if (waitForIdle)
170      waitForIdle ();
171
172    if (autoDelay > 0)
173      delay (autoDelay);
174  }
175
176  /**
177   * Release one or more mouse buttons.
178   *
179   * @param buttons the buttons to release; a bitmask of one or more
180   * of these {@link InputEvent} fields:
181   *
182   * <ul>
183   *   <li>BUTTON1_MASK</li>
184   *   <li>BUTTON2_MASK</li>
185   *   <li>BUTTON3_MASK</li>
186   * </ul>
187   *
188   * @exception IllegalArgumentException if the button mask is invalid
189   */
190  public void mouseRelease(int buttons)
191  {
192    if ((buttons & InputEvent.BUTTON1_MASK) == 0
193        && (buttons & InputEvent.BUTTON2_MASK) == 0
194        && (buttons & InputEvent.BUTTON3_MASK) == 0)
195      throw new IllegalArgumentException ("Robot: mouseRelease:"
196                                          + " invalid button mask");
197
198    peer.mouseRelease (buttons);
199
200    if (waitForIdle)
201      waitForIdle ();
202
203    if (autoDelay > 0)
204      delay (autoDelay);
205  }
206
207  /**
208   * Rotate the mouse scroll wheel.
209   *
210   * @param wheelAmt number of steps to rotate mouse wheel.  negative
211   * to rotate wheel up (away from the user), positive to rotate wheel
212   * down (toward the user).
213   *
214   * @since 1.4
215   */
216  public void mouseWheel (int wheelAmt)
217  {
218    peer.mouseWheel (wheelAmt);
219
220    if (waitForIdle)
221      waitForIdle ();
222
223    if (autoDelay > 0)
224      delay (autoDelay);
225  }
226
227  /**
228   * Press a key.
229   *
230   * @param keycode key to press, a {@link java.awt.event.KeyEvent} VK_ constant
231   *
232   * @exception IllegalArgumentException if keycode is not a valid key
233   */
234  public void keyPress (int keycode)
235  {
236    peer.keyPress (keycode);
237
238    if (waitForIdle)
239      waitForIdle ();
240
241    if (autoDelay > 0)
242      delay (autoDelay);
243  }
244
245  /**
246   * Release a key.
247   *
248   * @param keycode key to release, a {@link java.awt.event.KeyEvent} VK_
249   *                constant
250   *
251   * @exception IllegalArgumentException if keycode is not a valid key
252   */
253  public void keyRelease (int keycode)
254  {
255    peer.keyRelease (keycode);
256
257    if (waitForIdle)
258      waitForIdle ();
259
260    if (autoDelay > 0)
261      delay (autoDelay);
262  }
263
264  /**
265   * Return the color of the pixel at the given screen coordinates.
266   *
267   * @param x the x coordinate of the pixel
268   * @param y the y coordinate of the pixel
269   *
270   * @return the Color of the pixel at screen coodinates <code>(x, y)</code>
271   */
272  public Color getPixelColor (int x, int y)
273  {
274    return new Color (peer.getRGBPixel (x, y));
275  }
276
277  /**
278   * Create an image containing pixels read from the screen.  The
279   * image does not include the mouse pointer.
280   *
281   * @param screenRect the rectangle of pixels to capture, in screen
282   * coordinates
283   *
284   * @return a BufferedImage containing the requested pixels
285   *
286   * @exception IllegalArgumentException if requested width and height
287   * are not both greater than zero
288   * @exception SecurityException if readDisplayPixels permission is
289   * not granted
290   */
291  public BufferedImage createScreenCapture (Rectangle screenRect)
292  {
293    if (screenRect.width <= 0)
294      throw new IllegalArgumentException ("Robot: capture width is <= 0");
295
296    if (screenRect.height <= 0)
297      throw new IllegalArgumentException ("Robot: capture height is <= 0");
298
299    SecurityManager sm = System.getSecurityManager ();
300    if (sm != null)
301      sm.checkPermission (new AWTPermission ("readDisplayPixels"));
302
303    int[] pixels = peer.getRGBPixels (screenRect);
304
305    BufferedImage bufferedImage =
306      new BufferedImage (screenRect.width, screenRect.height,
307                         BufferedImage.TYPE_INT_ARGB);
308
309    bufferedImage.setRGB (0, 0, screenRect.width, screenRect.height,
310                          pixels, 0, screenRect.width);
311
312    return bufferedImage;
313  }
314
315  /**
316   * Check if this Robot automatically calls {@link #waitForIdle()} after
317   * generating an event.
318   *
319   * @return true if waitForIdle is automatically called
320   */
321  public boolean isAutoWaitForIdle ()
322  {
323    return waitForIdle;
324  }
325
326  /**
327   * Set whether or not this Robot automatically calls {@link
328   * #waitForIdle()} after generating an event.
329   *
330   * @param isOn true if waitForIdle should be called automatically
331   */
332  public void setAutoWaitForIdle (boolean isOn)
333  {
334    waitForIdle = isOn;
335  }
336
337  /**
338   * Retrieve the length of time this Robot sleeps after generating an
339   * event.
340   *
341   * @return the length of time in milliseconds
342   */
343  public int getAutoDelay ()
344  {
345    return autoDelay;
346  }
347
348  /**
349   * Set the length of time this Robot sleeps after generating an
350   * event.
351   *
352   * @param ms the length of time in milliseconds
353   *
354   * @exception IllegalArgumentException if ms is not between 0 and
355   * 60,000 milliseconds inclusive
356   */
357  public void setAutoDelay (int ms)
358  {
359    if (ms <= 0 || ms >= 60000)
360      throw new IllegalArgumentException ("Robot: delay length out-of-bounds");
361
362    autoDelay = ms;
363  }
364
365  /**
366   * Sleep for a specified length of time.
367   *
368   * @param ms the length of time in milliseconds
369   *
370   * @exception IllegalArgumentException if ms is not between 0 and
371   * 60,000 milliseconds inclusive
372   */
373  public void delay (int ms)
374  {
375    if (ms < 0 || ms > 60000)
376      throw new IllegalArgumentException ("Robot: delay length out-of-bounds");
377
378    try
379      {
380        Thread.sleep (ms);
381      }
382    catch (InterruptedException e)
383      {
384        System.err.println ("Robot: delay interrupted");
385      }
386  }
387
388  /**
389   * Wait until all events currently on the event queue have been
390   * dispatched.
391   */
392  public void waitForIdle ()
393  {
394    if (EventQueue.isDispatchThread ())
395      throw new IllegalThreadStateException ("Robot: waitForIdle called from "
396                                             + "the event dispatch thread");
397
398    try
399      {
400        EventQueue.invokeAndWait (new Runnable () { public void run () { } });
401      }
402    catch (InterruptedException e)
403      {
404        System.err.println ("Robot: waitForIdle interrupted");
405      }
406    catch (InvocationTargetException e)
407      {
408        System.err.println ("Robot: waitForIdle cannot invoke target");
409      }
410  }
411
412  /**
413   * Return a string representation of this Robot.
414   *
415   * @return a string representation
416   */
417  public String toString ()
418  {
419    return getClass ().getName ()
420        + "[ autoDelay = " + autoDelay + ", autoWaitForIdle = "
421        + waitForIdle + " ]";
422  }
423}