001: /*
002: * @(#)Robot.java 1.11 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package sun.awt;
028:
029: import java.awt.*;
030: import java.awt.image.*;
031: import java.awt.event.*;
032: import java.lang.reflect.InvocationTargetException;
033:
034: /**
035: * This class is used to generate native system input events
036: * for the purposes of test automation, self-running demos, and
037: * other applications where control of the mouse and keyboard
038: * is needed. The primary purpose of Robot is to facilitate
039: * automated testing of Java platform implementations.
040: * <p>
041: * Using the class to generate input events differs from posting
042: * events to the AWT event queue or AWT components in that the
043: * events are generated in the platform's native input
044: * queue. For example, <code>Robot.mouseMove</code> will actually move
045: * the mouse cursor instead of just generating mouse move events.
046: * <p>
047: * Note that some platforms require special privileges or extensions
048: * to access low-level input control. If the current platform configuration
049: * does not allow input control, an <code>AWTException</code> will be thrown
050: * when trying to construct Robot objects. For example, X-Window systems
051: * will throw the exception if the XTEST 2.2 standard extension is not supported
052: * (or not enabled) by the X server.
053: * <p>
054: * Applications that use Robot for purposes other than self-testing should
055: * handle these error conditions gracefully.
056: *
057: * @version 1.3, 11/21/02
058: * @author Nicholas Allen
059: */
060: public class Robot {
061:
062: private static final int MAX_DELAY = 60000;
063: private boolean isAutoWaitForIdle = false;
064: private int autoDelay = 0;
065: private static final int LEGAL_BUTTON_MASK = InputEvent.BUTTON1_MASK
066: | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK;
067:
068: /** The modifers to use for the next events generated by this robot. */
069:
070: /** The location where the mouse was last moved to. As Microwindows does not support
071: generating the low level events in native drivers, events are posted to the Java
072: event queue. When mouseMoved is called the coordinates are remembered in these
073: fields (The mouse may be physically in a different place though). */
074:
075: private int mouseX, mouseY;
076:
077: /** The graphics device useed for screen capture. */
078:
079: private GraphicsDevice screen;
080: private RobotHelper robotHelper;
081:
082: /**
083: * Constructs a Robot object in the coordinate system of the primary screen.
084: * <p>
085: *
086: * @throws AWTException if the platform configuration does not allow
087: * low-level input control. This exception is always thrown when
088: * GraphicsEnvironment.isHeadless() returns true
089: * @throws SecurityException if <code>createRobot</code> permission is not granted
090: * @see java.awt.GraphicsEnvironment#isHeadless
091: * @see SecurityManager#checkPermission
092: * @see AWTPermission
093: */
094: public Robot() throws AWTException {
095: this (GraphicsEnvironment.getLocalGraphicsEnvironment()
096: .getDefaultScreenDevice());
097: }
098:
099: /**
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: robotHelper = RobotHelper.getRobotHelper(screen);
129: if (robotHelper == null) {
130: throw new IllegalArgumentException(
131: "not a valid screen device");
132: }
133: this .screen = screen;
134: }
135:
136: /**
137: * Moves mouse pointer to given screen coordinates.
138: * @param x X position
139: * @param y Y position
140: */
141: public synchronized void mouseMove(int x, int y) {
142: mouseX = x;
143: mouseY = y;
144: robotHelper.doMouseAction(x, y, 0, false);
145: afterEvent();
146: }
147:
148: /**
149: * Presses one or more mouse buttons.
150: *
151: * @param buttons the Button mask; a combination of one or more
152: * of these flags:
153: * <ul>
154: * <li><code>InputEvent.BUTTON1_MASK</code>
155: * <li><code>InputEvent.BUTTON2_MASK</code>
156: * <li><code>InputEvent.BUTTON3_MASK</code>
157: * </ul>
158: * @throws IllegalArgumentException if the button mask is not a
159: * valid combination
160: */
161: public synchronized void mousePress(int buttons) {
162: checkButtonsArgument(buttons);
163: robotHelper.doMouseAction(mouseX, mouseY, buttons, true);
164: afterEvent();
165: }
166:
167: /**
168: * Releases one or more mouse buttons.
169: *
170: * @param buttons the Button mask; a combination of one or more
171: * of these flags:
172: * <ul>
173: * <li><code>InputEvent.BUTTON1_MASK</code>
174: * <li><code>InputEvent.BUTTON2_MASK</code>
175: * <li><code>InputEvent.BUTTON3_MASK</code>
176: * </ul>
177: * @throws IllegalArgumentException if the button mask is not a valid
178: * combination
179: */
180: public synchronized void mouseRelease(int buttons) {
181: checkButtonsArgument(buttons);
182: robotHelper.doMouseAction(mouseX, mouseY, buttons, false);
183: afterEvent();
184: }
185:
186: private void checkButtonsArgument(int buttons) {
187: if ((buttons | LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK) {
188: throw new IllegalArgumentException(
189: "Invalid combination of button flags");
190: }
191: }
192:
193: /**
194: * Presses a given key.
195: * <p>
196: * Key codes that have more than one physical key associated with them
197: * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
198: * left or right shift key) will map to the left key.
199: *
200: * @param keyCode Key to press (e.g. <code>KeyEvent.VK_A</code>)
201: * @throws IllegalArgumentException if <code>keycode</code> is not a valid key
202: * @see java.awt.event.KeyEvent
203: */
204: public synchronized void keyPress(int keycode) {
205: checkKeycodeArgument(keycode);
206: robotHelper.doKeyAction(keycode, true);
207: afterEvent();
208: }
209:
210: /**
211: * Releases a given key.
212: * <p>
213: * Key codes that have more than one physical key associated with them
214: * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
215: * left or right shift key) will map to the left key.
216: *
217: * @param keyCode Key to release (e.g. <code>KeyEvent.VK_A</code>)
218: * @throws IllegalArgumentException if <code>keycode</code> is not a valid key
219: * @see java.awt.event.KeyEvent
220: */
221: public synchronized void keyRelease(int keycode) {
222: checkKeycodeArgument(keycode);
223: robotHelper.doKeyAction(keycode, false);
224: afterEvent();
225: }
226:
227: private void checkKeycodeArgument(int keycode) {
228: if (keycode == KeyEvent.VK_UNDEFINED) {
229: throw new IllegalArgumentException("Invalid key code");
230: }
231: }
232:
233: /**
234: * Returns the color of a pixel at the given screen coordinates.
235: * @param x X position of pixel
236: * @param y Y position of pixel
237: * @return Color of the pixel
238: */
239: public synchronized Color getPixelColor(int x, int y) {
240: return robotHelper.getPixelColor(x, y);
241: }
242:
243: /**
244: * Creates an image containing pixels read from the screen.
245: * @param screenRect Rect to capture in screen coordinates
246: * @return The captured image
247: * @throws IllegalArgumentException if <code>screenRect</code> width and height are not greater than zero
248: * @throws SecurityException if <code>readDisplayPixels</code> permission is not granted
249: * @see SecurityManager#checkPermission
250: * @see AWTPermission
251: */
252: public synchronized BufferedImage createScreenCapture(
253: Rectangle screenRect) {
254: if (screenRect.width <= 0 || screenRect.height <= 0) {
255: throw new IllegalArgumentException(
256: "Rectangle width and height must be > 0");
257: }
258: return robotHelper.getScreenImage(screenRect);
259:
260: }
261:
262: /*
263: * Called after an event is generated
264: */
265: private void afterEvent() {
266: autoWaitForIdle();
267: autoDelay();
268: }
269:
270: /**
271: * Returns whether this Robot automatically invokes <code>waitForIdle</code>
272: * after generating an event.
273: * @return Whether <code>waitForIdle</code> is automatically called
274: */
275: public synchronized boolean isAutoWaitForIdle() {
276: return isAutoWaitForIdle;
277: }
278:
279: /**
280: * Sets whether this Robot automatically invokes <code>waitForIdle</code>
281: * after generating an event.
282: * @param isOn Whether <code>waitForIdle</code> is automatically invoked
283: */
284: public synchronized void setAutoWaitForIdle(boolean isOn) {
285: isAutoWaitForIdle = isOn;
286: }
287:
288: /*
289: * Calls waitForIdle after every event if so desired.
290: */
291: private void autoWaitForIdle() {
292: if (isAutoWaitForIdle) {
293: waitForIdle();
294: }
295: }
296:
297: /**
298: * Returns the number of milliseconds this Robot sleeps after generating an event.
299: */
300: public synchronized int getAutoDelay() {
301: return autoDelay;
302: }
303:
304: /**
305: * Sets the number of milliseconds this Robot sleeps after generating an event.
306: * @throws IllegalArgumentException If <code>ms</code> is not between 0 and 60,000 milliseconds inclusive
307: */
308: public synchronized void setAutoDelay(int ms) {
309: checkDelayArgument(ms);
310: autoDelay = ms;
311: }
312:
313: /*
314: * Automatically sleeps for the specified interval after event generated.
315: */
316: private void autoDelay() {
317: delay(autoDelay);
318: }
319:
320: /**
321: * Sleeps for the specified time.
322: * To catch any <code>InterruptedException</code>s that occur,
323: * <code>Thread.sleep()</code> may be used instead.
324: * @param ms time to sleep in milliseconds
325: * @throws IllegalArgumentException if <code>ms</code> is not between 0 and 60,000 milliseconds inclusive
326: * @see java.lang.Thread#sleep()
327: */
328: public synchronized void delay(int ms) {
329: checkDelayArgument(ms);
330: try {
331: Thread.sleep(ms);
332: } catch (InterruptedException ite) {
333: ite.printStackTrace();
334: }
335: }
336:
337: private void checkDelayArgument(int ms) {
338: if (ms < 0 || ms > MAX_DELAY) {
339: throw new IllegalArgumentException(
340: "Delay must be to 0 to 60,000ms");
341: }
342: }
343:
344: /**
345: * Waits until all events currently on the event queue have been processed.
346: * @throws IllegalThreadStateException if called on the AWT event dispatching thread
347: */
348: public synchronized void waitForIdle() {
349: checkNotDispatchThread();
350: // post a dummy event to the queue so we know when
351: // all the events before it have been processed
352: try {
353: EventQueue.invokeAndWait(new Runnable() {
354: public void run() {
355: // dummy implementation
356: }
357: });
358: } catch (InterruptedException ite) {
359: System.err
360: .println("Robot.waitForIdle, non-fatal exception caught:");
361: ite.printStackTrace();
362: } catch (InvocationTargetException ine) {
363: System.err
364: .println("Robot.waitForIdle, non-fatal exception caught:");
365: ine.printStackTrace();
366: }
367: }
368:
369: private void checkNotDispatchThread() {
370: if (EventQueue.isDispatchThread()) {
371: throw new IllegalThreadStateException(
372: "Cannot call method from the event dispatcher thread");
373: }
374: }
375:
376: /**
377: * Returns a string representation of this Robot.
378: *
379: * @return the string representation.
380: */
381: public synchronized String toString() {
382: String params = "autoDelay = " + getAutoDelay() + ", "
383: + "autoWaitForIdle = " + isAutoWaitForIdle();
384: return getClass().getName() + "[ " + params + " ]";
385: }
386: }
|