001: /*
002: * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package java.awt;
027:
028: import java.awt.peer.*;
029: import java.awt.image.*;
030: import java.awt.event.*;
031: import java.lang.reflect.InvocationTargetException;
032: import sun.awt.ComponentFactory;
033: import sun.awt.SunToolkit;
034: import sun.security.util.SecurityConstants;
035:
036: /**
037: * This class is used to generate native system input events
038: * for the purposes of test automation, self-running demos, and
039: * other applications where control of the mouse and keyboard
040: * is needed. The primary purpose of Robot is to facilitate
041: * automated testing of Java platform implementations.
042: * <p>
043: * Using the class to generate input events differs from posting
044: * events to the AWT event queue or AWT components in that the
045: * events are generated in the platform's native input
046: * queue. For example, <code>Robot.mouseMove</code> will actually move
047: * the mouse cursor instead of just generating mouse move events.
048: * <p>
049: * Note that some platforms require special privileges or extensions
050: * to access low-level input control. If the current platform configuration
051: * does not allow input control, an <code>AWTException</code> will be thrown
052: * when trying to construct Robot objects. For example, X-Window systems
053: * will throw the exception if the XTEST 2.2 standard extension is not supported
054: * (or not enabled) by the X server.
055: * <p>
056: * Applications that use Robot for purposes other than self-testing should
057: * handle these error conditions gracefully.
058: *
059: * @version 1.37, 05/05/07
060: * @author Robi Khan
061: * @since 1.3
062: */
063: public class Robot {
064: private static final int MAX_DELAY = 60000;
065: private RobotPeer peer;
066: private boolean isAutoWaitForIdle = false;
067: private int autoDelay = 0;
068: private static final int LEGAL_BUTTON_MASK = InputEvent.BUTTON1_MASK
069: | InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK;
070:
071: // location of robot's GC, used in mouseMove(), getPixelColor() and captureScreenImage()
072: private Point gdLoc;
073:
074: private DirectColorModel screenCapCM = null;
075:
076: /**
077: * Constructs a Robot object in the coordinate system of the primary screen.
078: * <p>
079: *
080: * @throws AWTException if the platform configuration does not allow
081: * low-level input control. This exception is always thrown when
082: * GraphicsEnvironment.isHeadless() returns true
083: * @throws SecurityException if <code>createRobot</code> permission is not granted
084: * @see java.awt.GraphicsEnvironment#isHeadless
085: * @see SecurityManager#checkPermission
086: * @see AWTPermission
087: */
088: public Robot() throws AWTException {
089: if (GraphicsEnvironment.isHeadless()) {
090: throw new AWTException("headless environment");
091: }
092: init(GraphicsEnvironment.getLocalGraphicsEnvironment()
093: .getDefaultScreenDevice());
094: }
095:
096: /**
097: * Creates a Robot for the given screen device. Coordinates passed
098: * to Robot method calls like mouseMove and createScreenCapture will
099: * 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: gdLoc = screen.getDefaultConfiguration().getBounds()
132: .getLocation();
133: Toolkit toolkit = Toolkit.getDefaultToolkit();
134: if (toolkit instanceof ComponentFactory) {
135: peer = ((ComponentFactory) toolkit).createRobot(this ,
136: screen);
137: disposer = new RobotDisposer(peer);
138: sun.java2d.Disposer.addRecord(anchor, disposer);
139: }
140: }
141:
142: /* determine if the security policy allows Robot's to be created */
143: private void checkRobotAllowed() {
144: SecurityManager security = System.getSecurityManager();
145: if (security != null) {
146: security
147: .checkPermission(SecurityConstants.CREATE_ROBOT_PERMISSION);
148: }
149: }
150:
151: /* check if the given device is a screen device */
152: private void checkIsScreenDevice(GraphicsDevice device) {
153: if (device == null
154: || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
155: throw new IllegalArgumentException(
156: "not a valid screen device");
157: }
158: }
159:
160: private transient Object anchor = new Object();
161:
162: static class RobotDisposer implements sun.java2d.DisposerRecord {
163: private final RobotPeer peer;
164:
165: public RobotDisposer(RobotPeer peer) {
166: this .peer = peer;
167: }
168:
169: public void dispose() {
170: if (peer != null) {
171: peer.dispose();
172: }
173: }
174: }
175:
176: private transient RobotDisposer disposer;
177:
178: /**
179: * Moves mouse pointer to given screen coordinates.
180: * @param x X position
181: * @param y Y position
182: */
183: public synchronized void mouseMove(int x, int y) {
184: peer.mouseMove(gdLoc.x + x, gdLoc.y + y);
185: afterEvent();
186: }
187:
188: /**
189: * Presses one or more mouse buttons. The mouse buttons should
190: * be released using the <code>mouseRelease</code> method.
191: *
192: * @param buttons the Button mask; a combination of one or more
193: * of these flags:
194: * <ul>
195: * <li><code>InputEvent.BUTTON1_MASK</code>
196: * <li><code>InputEvent.BUTTON2_MASK</code>
197: * <li><code>InputEvent.BUTTON3_MASK</code>
198: * </ul>
199: * @throws IllegalArgumentException if the button mask is not a
200: * valid combination
201: * @see #mouseRelease(int)
202: */
203: public synchronized void mousePress(int buttons) {
204: checkButtonsArgument(buttons);
205: peer.mousePress(buttons);
206: afterEvent();
207: }
208:
209: /**
210: * Releases one or more mouse buttons.
211: *
212: * @param buttons the Button mask; a combination of one or more
213: * of these flags:
214: * <ul>
215: * <li><code>InputEvent.BUTTON1_MASK</code>
216: * <li><code>InputEvent.BUTTON2_MASK</code>
217: * <li><code>InputEvent.BUTTON3_MASK</code>
218: * </ul>
219: * @see #mousePress(int)
220: * @throws IllegalArgumentException if the button mask is not a valid
221: * combination
222: */
223: public synchronized void mouseRelease(int buttons) {
224: checkButtonsArgument(buttons);
225: peer.mouseRelease(buttons);
226: afterEvent();
227: }
228:
229: private void checkButtonsArgument(int buttons) {
230: if ((buttons | LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK) {
231: throw new IllegalArgumentException(
232: "Invalid combination of button flags");
233: }
234: }
235:
236: /**
237: * Rotates the scroll wheel on wheel-equipped mice.
238: *
239: * @param wheelAmt number of "notches" to move the mouse wheel
240: * Negative values indicate movement up/away from the user,
241: * positive values indicate movement down/towards the user.
242: *
243: * @since 1.4
244: */
245: public synchronized void mouseWheel(int wheelAmt) {
246: peer.mouseWheel(wheelAmt);
247: afterEvent();
248: }
249:
250: /**
251: * Presses a given key. The key should be released using the
252: * <code>keyRelease</code> method.
253: * <p>
254: * Key codes that have more than one physical key associated with them
255: * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
256: * left or right shift key) will map to the left key.
257: *
258: * @param keycode Key to press (e.g. <code>KeyEvent.VK_A</code>)
259: * @throws IllegalArgumentException if <code>keycode</code> is not
260: * a valid key
261: * @see #keyRelease(int)
262: * @see java.awt.event.KeyEvent
263: */
264: public synchronized void keyPress(int keycode) {
265: checkKeycodeArgument(keycode);
266: peer.keyPress(keycode);
267: afterEvent();
268: }
269:
270: /**
271: * Releases a given key.
272: * <p>
273: * Key codes that have more than one physical key associated with them
274: * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
275: * left or right shift key) will map to the left key.
276: *
277: * @param keycode Key to release (e.g. <code>KeyEvent.VK_A</code>)
278: * @throws IllegalArgumentException if <code>keycode</code> is not a
279: * valid key
280: * @see #keyPress(int)
281: * @see java.awt.event.KeyEvent
282: */
283: public synchronized void keyRelease(int keycode) {
284: checkKeycodeArgument(keycode);
285: peer.keyRelease(keycode);
286: afterEvent();
287: }
288:
289: private void checkKeycodeArgument(int keycode) {
290: // rather than build a big table or switch statement here, we'll
291: // just check that the key isn't VK_UNDEFINED and assume that the
292: // peer implementations will throw an exception for other bogus
293: // values e.g. -1, 999999
294: if (keycode == KeyEvent.VK_UNDEFINED) {
295: throw new IllegalArgumentException("Invalid key code");
296: }
297: }
298:
299: /**
300: * Returns the color of a pixel at the given screen coordinates.
301: * @param x X position of pixel
302: * @param y Y position of pixel
303: * @return Color of the pixel
304: */
305: public synchronized Color getPixelColor(int x, int y) {
306: Color color = new Color(peer.getRGBPixel(gdLoc.x + x, gdLoc.y
307: + y));
308: return color;
309: }
310:
311: /**
312: * Creates an image containing pixels read from the screen. This image does
313: * not include the mouse cursor.
314: * @param screenRect Rect to capture in screen coordinates
315: * @return The captured image
316: * @throws IllegalArgumentException if <code>screenRect</code> width and height are not greater than zero
317: * @throws SecurityException if <code>readDisplayPixels</code> permission is not granted
318: * @see SecurityManager#checkPermission
319: * @see AWTPermission
320: */
321: public synchronized BufferedImage createScreenCapture(
322: Rectangle screenRect) {
323: checkScreenCaptureAllowed();
324:
325: // according to the spec, screenRect is relative to robot's GD
326: Rectangle translatedRect = new Rectangle(screenRect);
327: translatedRect.translate(gdLoc.x, gdLoc.y);
328: checkValidRect(translatedRect);
329:
330: BufferedImage image;
331: DataBufferInt buffer;
332: WritableRaster raster;
333:
334: if (screenCapCM == null) {
335: /*
336: * Fix for 4285201
337: * Create a DirectColorModel equivalent to the default RGB ColorModel,
338: * except with no Alpha component.
339: */
340:
341: screenCapCM = new DirectColorModel(24,
342: /* red mask */0x00FF0000,
343: /* green mask */0x0000FF00,
344: /* blue mask */0x000000FF);
345: }
346:
347: int pixels[];
348: int[] bandmasks = new int[3];
349:
350: pixels = peer.getRGBPixels(translatedRect);
351: buffer = new DataBufferInt(pixels, pixels.length);
352:
353: bandmasks[0] = screenCapCM.getRedMask();
354: bandmasks[1] = screenCapCM.getGreenMask();
355: bandmasks[2] = screenCapCM.getBlueMask();
356:
357: raster = Raster.createPackedRaster(buffer,
358: translatedRect.width, translatedRect.height,
359: translatedRect.width, bandmasks, null);
360:
361: image = new BufferedImage(screenCapCM, raster, false, null);
362:
363: return image;
364: }
365:
366: private static void checkValidRect(Rectangle rect) {
367: if (rect.width <= 0 || rect.height <= 0) {
368: throw new IllegalArgumentException(
369: "Rectangle width and height must be > 0");
370: }
371: }
372:
373: private static void checkScreenCaptureAllowed() {
374: SecurityManager security = System.getSecurityManager();
375: if (security != null) {
376: security
377: .checkPermission(SecurityConstants.READ_DISPLAY_PIXELS_PERMISSION);
378: }
379: }
380:
381: /*
382: * Called after an event is generated
383: */
384: private void afterEvent() {
385: autoWaitForIdle();
386: autoDelay();
387: }
388:
389: /**
390: * Returns whether this Robot automatically invokes <code>waitForIdle</code>
391: * after generating an event.
392: * @return Whether <code>waitForIdle</code> is automatically called
393: */
394: public synchronized boolean isAutoWaitForIdle() {
395: return isAutoWaitForIdle;
396: }
397:
398: /**
399: * Sets whether this Robot automatically invokes <code>waitForIdle</code>
400: * after generating an event.
401: * @param isOn Whether <code>waitForIdle</code> is automatically invoked
402: */
403: public synchronized void setAutoWaitForIdle(boolean isOn) {
404: isAutoWaitForIdle = isOn;
405: }
406:
407: /*
408: * Calls waitForIdle after every event if so desired.
409: */
410: private void autoWaitForIdle() {
411: if (isAutoWaitForIdle) {
412: waitForIdle();
413: }
414: }
415:
416: /**
417: * Returns the number of milliseconds this Robot sleeps after generating an event.
418: */
419: public synchronized int getAutoDelay() {
420: return autoDelay;
421: }
422:
423: /**
424: * Sets the number of milliseconds this Robot sleeps after generating an event.
425: * @throws IllegalArgumentException If <code>ms</code> is not between 0 and 60,000 milliseconds inclusive
426: */
427: public synchronized void setAutoDelay(int ms) {
428: checkDelayArgument(ms);
429: autoDelay = ms;
430: }
431:
432: /*
433: * Automatically sleeps for the specified interval after event generated.
434: */
435: private void autoDelay() {
436: delay(autoDelay);
437: }
438:
439: /**
440: * Sleeps for the specified time.
441: * To catch any <code>InterruptedException</code>s that occur,
442: * <code>Thread.sleep()</code> may be used instead.
443: * @param ms time to sleep in milliseconds
444: * @throws IllegalArgumentException if <code>ms</code> is not between 0 and 60,000 milliseconds inclusive
445: * @see java.lang.Thread#sleep
446: */
447: public synchronized void delay(int ms) {
448: checkDelayArgument(ms);
449: try {
450: Thread.sleep(ms);
451: } catch (InterruptedException ite) {
452: ite.printStackTrace();
453: }
454: }
455:
456: private void checkDelayArgument(int ms) {
457: if (ms < 0 || ms > MAX_DELAY) {
458: throw new IllegalArgumentException(
459: "Delay must be to 0 to 60,000ms");
460: }
461: }
462:
463: /**
464: * Waits until all events currently on the event queue have been processed.
465: * @throws IllegalThreadStateException if called on the AWT event dispatching thread
466: */
467: public synchronized void waitForIdle() {
468: checkNotDispatchThread();
469: // post a dummy event to the queue so we know when
470: // all the events before it have been processed
471: try {
472: SunToolkit.flushPendingEvents();
473: EventQueue.invokeAndWait(new Runnable() {
474: public void run() {
475: // dummy implementation
476: }
477: });
478: } catch (InterruptedException ite) {
479: System.err
480: .println("Robot.waitForIdle, non-fatal exception caught:");
481: ite.printStackTrace();
482: } catch (InvocationTargetException ine) {
483: System.err
484: .println("Robot.waitForIdle, non-fatal exception caught:");
485: ine.printStackTrace();
486: }
487: }
488:
489: private void checkNotDispatchThread() {
490: if (EventQueue.isDispatchThread()) {
491: throw new IllegalThreadStateException(
492: "Cannot call method from the event dispatcher thread");
493: }
494: }
495:
496: /**
497: * Returns a string representation of this Robot.
498: *
499: * @return the string representation.
500: */
501: public synchronized String toString() {
502: String params = "autoDelay = " + getAutoDelay() + ", "
503: + "autoWaitForIdle = " + isAutoWaitForIdle();
504: return getClass().getName() + "[ " + params + " ]";
505: }
506: }
|