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 }
|