1. /*
  2. * @(#)Robot.java 1.27 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt;
  8. import java.awt.peer.*;
  9. import java.awt.image.*;
  10. import java.awt.event.*;
  11. import java.lang.reflect.InvocationTargetException;
  12. import sun.awt.ComponentFactory;
  13. import sun.awt.SunToolkit;
  14. import sun.security.util.SecurityConstants;
  15. /**
  16. * This class is used to generate native system input events
  17. * for the purposes of test automation, self-running demos, and
  18. * other applications where control of the mouse and keyboard
  19. * is needed. The primary purpose of Robot is to facilitate
  20. * automated testing of Java platform implementations.
  21. * <p>
  22. * Using the class to generate input events differs from posting
  23. * events to the AWT event queue or AWT components in that the
  24. * events are generated in the platform's native input
  25. * queue. For example, <code>Robot.mouseMove</code> will actually move
  26. * the mouse cursor instead of just generating mouse move events.
  27. * <p>
  28. * Note that some platforms require special privileges or extensions
  29. * to access low-level input control. If the current platform configuration
  30. * does not allow input control, an <code>AWTException</code> will be thrown
  31. * when trying to construct Robot objects. For example, X-Window systems
  32. * will throw the exception if the XTEST 2.2 standard extension is not supported
  33. * (or not enabled) by the X server.
  34. * <p>
  35. * Applications that use Robot for purposes other than self-testing should
  36. * handle these error conditions gracefully.
  37. *
  38. * @version 1.27, 12/19/03
  39. * @author Robi Khan
  40. * @since 1.3
  41. */
  42. public class Robot {
  43. private static final int MAX_DELAY = 60000;
  44. private RobotPeer peer;
  45. private boolean isAutoWaitForIdle = false;
  46. private int autoDelay = 0;
  47. private static final int LEGAL_BUTTON_MASK =
  48. InputEvent.BUTTON1_MASK|
  49. InputEvent.BUTTON2_MASK|
  50. InputEvent.BUTTON3_MASK;
  51. private DirectColorModel screenCapCM = null;
  52. /**
  53. * Constructs a Robot object in the coordinate system of the primary screen.
  54. * <p>
  55. *
  56. * @throws AWTException if the platform configuration does not allow
  57. * low-level input control. This exception is always thrown when
  58. * GraphicsEnvironment.isHeadless() returns true
  59. * @throws SecurityException if <code>createRobot</code> permission is not granted
  60. * @see java.awt.GraphicsEnvironment#isHeadless
  61. * @see SecurityManager#checkPermission
  62. * @see AWTPermission
  63. */
  64. public Robot() throws AWTException {
  65. if (GraphicsEnvironment.isHeadless()) {
  66. throw new AWTException("headless environment");
  67. }
  68. init(GraphicsEnvironment.getLocalGraphicsEnvironment()
  69. .getDefaultScreenDevice());
  70. }
  71. /**
  72. * Creates a Robot for the given screen device. Coordinates passed
  73. * to Robot method calls like mouseMove and createScreenCapture will
  74. * be interpreted as being in the same coordinate system as the
  75. * specified screen. Note that depending on the platform configuration,
  76. * multiple screens may either:
  77. * <ul>
  78. * <li>share the same coordinate system to form a combined virtual screen</li>
  79. * <li>use different coordinate systems to act as independent screens</li>
  80. * </ul>
  81. * This constructor is meant for the latter case.
  82. * <p>
  83. * If screen devices are reconfigured such that the coordinate system is
  84. * affected, the behavior of existing Robot objects is undefined.
  85. *
  86. * @param screen A screen GraphicsDevice indicating the coordinate
  87. * system the Robot will operate in.
  88. * @throws AWTException if the platform configuration does not allow
  89. * low-level input control. This exception is always thrown when
  90. * GraphicsEnvironment.isHeadless() returns true.
  91. * @throws IllegalArgumentException if <code>screen</code> is not a screen
  92. * GraphicsDevice.
  93. * @throws SecurityException if <code>createRobot</code> permission is not granted
  94. * @see java.awt.GraphicsEnvironment#isHeadless
  95. * @see GraphicsDevice
  96. * @see SecurityManager#checkPermission
  97. * @see AWTPermission
  98. */
  99. public Robot(GraphicsDevice screen) throws AWTException {
  100. checkIsScreenDevice(screen);
  101. init(screen);
  102. }
  103. private void init(GraphicsDevice screen) throws AWTException {
  104. checkRobotAllowed();
  105. Toolkit toolkit = Toolkit.getDefaultToolkit();
  106. if (toolkit instanceof ComponentFactory) {
  107. peer = ((ComponentFactory)toolkit).createRobot(this, screen);
  108. }
  109. }
  110. /* determine if the security policy allows Robot's to be created */
  111. private void checkRobotAllowed() {
  112. SecurityManager security = System.getSecurityManager();
  113. if (security != null) {
  114. security.checkPermission(SecurityConstants.CREATE_ROBOT_PERMISSION);
  115. }
  116. }
  117. /* check if the given device is a screen device */
  118. private void checkIsScreenDevice(GraphicsDevice device) {
  119. if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
  120. throw new IllegalArgumentException("not a valid screen device");
  121. }
  122. }
  123. /**
  124. * Moves mouse pointer to given screen coordinates.
  125. * @param x X position
  126. * @param y Y position
  127. */
  128. public synchronized void mouseMove(int x, int y) {
  129. peer.mouseMove(x,y);
  130. afterEvent();
  131. }
  132. /**
  133. * Presses one or more mouse buttons. The mouse buttons should
  134. * be released using the <code>mouseRelease</code> method.
  135. *
  136. * @param buttons the Button mask; a combination of one or more
  137. * of these flags:
  138. * <ul>
  139. * <li><code>InputEvent.BUTTON1_MASK</code>
  140. * <li><code>InputEvent.BUTTON2_MASK</code>
  141. * <li><code>InputEvent.BUTTON3_MASK</code>
  142. * </ul>
  143. * @throws IllegalArgumentException if the button mask is not a
  144. * valid combination
  145. * @see #mouseRelease(int)
  146. */
  147. public synchronized void mousePress(int buttons) {
  148. checkButtonsArgument(buttons);
  149. peer.mousePress(buttons);
  150. afterEvent();
  151. }
  152. /**
  153. * Releases one or more mouse buttons.
  154. *
  155. * @param buttons the Button mask; a combination of one or more
  156. * of these flags:
  157. * <ul>
  158. * <li><code>InputEvent.BUTTON1_MASK</code>
  159. * <li><code>InputEvent.BUTTON2_MASK</code>
  160. * <li><code>InputEvent.BUTTON3_MASK</code>
  161. * </ul>
  162. * @see #mousePress(int)
  163. * @throws IllegalArgumentException if the button mask is not a valid
  164. * combination
  165. */
  166. public synchronized void mouseRelease(int buttons) {
  167. checkButtonsArgument(buttons);
  168. peer.mouseRelease(buttons);
  169. afterEvent();
  170. }
  171. private void checkButtonsArgument(int buttons) {
  172. if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) {
  173. throw new IllegalArgumentException("Invalid combination of button flags");
  174. }
  175. }
  176. /**
  177. * Rotates the scroll wheel on wheel-equipped mice.
  178. *
  179. * @param wheelAmt number of "notches" to move the mouse wheel
  180. * Negative values indicate movement up/away from the user,
  181. * positive values indicate movement down/towards the user.
  182. *
  183. * @since 1.4
  184. */
  185. public synchronized void mouseWheel(int wheelAmt) {
  186. peer.mouseWheel(wheelAmt);
  187. afterEvent();
  188. }
  189. /**
  190. * Presses a given key. The key should be released using the
  191. * <code>keyRelease</code> method.
  192. * <p>
  193. * Key codes that have more than one physical key associated with them
  194. * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
  195. * left or right shift key) will map to the left key.
  196. *
  197. * @param keycode Key to press (e.g. <code>KeyEvent.VK_A</code>)
  198. * @throws IllegalArgumentException if <code>keycode</code> is not
  199. * a valid key
  200. * @see #keyRelease(int)
  201. * @see java.awt.event.KeyEvent
  202. */
  203. public synchronized void keyPress(int keycode) {
  204. checkKeycodeArgument(keycode);
  205. peer.keyPress(keycode);
  206. afterEvent();
  207. }
  208. /**
  209. * Releases a given key.
  210. * <p>
  211. * Key codes that have more than one physical key associated with them
  212. * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
  213. * left or right shift key) will map to the left key.
  214. *
  215. * @param keycode Key to release (e.g. <code>KeyEvent.VK_A</code>)
  216. * @throws IllegalArgumentException if <code>keycode</code> is not a
  217. * valid key
  218. * @see #keyPress(int)
  219. * @see java.awt.event.KeyEvent
  220. */
  221. public synchronized void keyRelease(int keycode) {
  222. checkKeycodeArgument(keycode);
  223. peer.keyRelease(keycode);
  224. afterEvent();
  225. }
  226. private void checkKeycodeArgument(int keycode) {
  227. // rather than build a big table or switch statement here, we'll
  228. // just check that the key isn't VK_UNDEFINED and assume that the
  229. // peer implementations will throw an exception for other bogus
  230. // values e.g. -1, 999999
  231. if (keycode == KeyEvent.VK_UNDEFINED) {
  232. throw new IllegalArgumentException("Invalid key code");
  233. }
  234. }
  235. /**
  236. * Returns the color of a pixel at the given screen coordinates.
  237. * @param x X position of pixel
  238. * @param y Y position of pixel
  239. * @return Color of the pixel
  240. */
  241. public synchronized Color getPixelColor(int x, int y) {
  242. Color color = new Color(peer.getRGBPixel(x,y));
  243. return color;
  244. }
  245. /**
  246. * Creates an image containing pixels read from the screen. This image does
  247. * not include the mouse cursor.
  248. * @param screenRect Rect to capture in screen coordinates
  249. * @return The captured image
  250. * @throws IllegalArgumentException if <code>screenRect</code> width and height are not greater than zero
  251. * @throws SecurityException if <code>readDisplayPixels</code> permission is not granted
  252. * @see SecurityManager#checkPermission
  253. * @see AWTPermission
  254. */
  255. public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
  256. checkScreenCaptureAllowed();
  257. checkValidRect(screenRect);
  258. BufferedImage image;
  259. DataBufferInt buffer;
  260. WritableRaster raster;
  261. if (screenCapCM == null) {
  262. /*
  263. * Fix for 4285201
  264. * Create a DirectColorModel equivalent to the default RGB ColorModel,
  265. * except with no Alpha component.
  266. */
  267. screenCapCM = new DirectColorModel(24,
  268. /* red mask */ 0x00FF0000,
  269. /* green mask */ 0x0000FF00,
  270. /* blue mask */ 0x000000FF);
  271. }
  272. int pixels[];
  273. int[] bandmasks = new int[3];
  274. pixels = peer.getRGBPixels(screenRect);
  275. buffer = new DataBufferInt(pixels, pixels.length);
  276. bandmasks[0] = screenCapCM.getRedMask();
  277. bandmasks[1] = screenCapCM.getGreenMask();
  278. bandmasks[2] = screenCapCM.getBlueMask();
  279. raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
  280. image = new BufferedImage(screenCapCM, raster, false, null);
  281. return image;
  282. }
  283. private static void checkValidRect(Rectangle rect) {
  284. if (rect.width <= 0 || rect.height <= 0) {
  285. throw new IllegalArgumentException("Rectangle width and height must be > 0");
  286. }
  287. }
  288. private static void checkScreenCaptureAllowed() {
  289. SecurityManager security = System.getSecurityManager();
  290. if (security != null) {
  291. security.checkPermission(
  292. SecurityConstants.READ_DISPLAY_PIXELS_PERMISSION);
  293. }
  294. }
  295. /*
  296. * Called after an event is generated
  297. */
  298. private void afterEvent() {
  299. autoWaitForIdle();
  300. autoDelay();
  301. }
  302. /**
  303. * Returns whether this Robot automatically invokes <code>waitForIdle</code>
  304. * after generating an event.
  305. * @return Whether <code>waitForIdle</code> is automatically called
  306. */
  307. public synchronized boolean isAutoWaitForIdle() {
  308. return isAutoWaitForIdle;
  309. }
  310. /**
  311. * Sets whether this Robot automatically invokes <code>waitForIdle</code>
  312. * after generating an event.
  313. * @param isOn Whether <code>waitForIdle</code> is automatically invoked
  314. */
  315. public synchronized void setAutoWaitForIdle(boolean isOn) {
  316. isAutoWaitForIdle = isOn;
  317. }
  318. /*
  319. * Calls waitForIdle after every event if so desired.
  320. */
  321. private void autoWaitForIdle() {
  322. if (isAutoWaitForIdle) {
  323. waitForIdle();
  324. }
  325. }
  326. /**
  327. * Returns the number of milliseconds this Robot sleeps after generating an event.
  328. */
  329. public synchronized int getAutoDelay() {
  330. return autoDelay;
  331. }
  332. /**
  333. * Sets the number of milliseconds this Robot sleeps after generating an event.
  334. * @throws IllegalArgumentException If <code>ms</code> is not between 0 and 60,000 milliseconds inclusive
  335. */
  336. public synchronized void setAutoDelay(int ms) {
  337. checkDelayArgument(ms);
  338. autoDelay = ms;
  339. }
  340. /*
  341. * Automatically sleeps for the specified interval after event generated.
  342. */
  343. private void autoDelay() {
  344. delay(autoDelay);
  345. }
  346. /**
  347. * Sleeps for the specified time.
  348. * To catch any <code>InterruptedException</code>s that occur,
  349. * <code>Thread.sleep()</code> may be used instead.
  350. * @param ms time to sleep in milliseconds
  351. * @throws IllegalArgumentException if <code>ms</code> is not between 0 and 60,000 milliseconds inclusive
  352. * @see java.lang.Thread#sleep
  353. */
  354. public synchronized void delay(int ms) {
  355. checkDelayArgument(ms);
  356. try {
  357. Thread.sleep(ms);
  358. } catch(InterruptedException ite) {
  359. ite.printStackTrace();
  360. }
  361. }
  362. private void checkDelayArgument(int ms) {
  363. if (ms < 0 || ms > MAX_DELAY) {
  364. throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
  365. }
  366. }
  367. /**
  368. * Waits until all events currently on the event queue have been processed.
  369. * @throws IllegalThreadStateException if called on the AWT event dispatching thread
  370. */
  371. public synchronized void waitForIdle() {
  372. checkNotDispatchThread();
  373. // post a dummy event to the queue so we know when
  374. // all the events before it have been processed
  375. try {
  376. SunToolkit.flushPendingEvents();
  377. EventQueue.invokeAndWait( new Runnable() {
  378. public void run() {
  379. // dummy implementation
  380. }
  381. } );
  382. } catch(InterruptedException ite) {
  383. System.err.println("Robot.waitForIdle, non-fatal exception caught:");
  384. ite.printStackTrace();
  385. } catch(InvocationTargetException ine) {
  386. System.err.println("Robot.waitForIdle, non-fatal exception caught:");
  387. ine.printStackTrace();
  388. }
  389. }
  390. private void checkNotDispatchThread() {
  391. if (EventQueue.isDispatchThread()) {
  392. throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
  393. }
  394. }
  395. /**
  396. * Returns a string representation of this Robot.
  397. *
  398. * @return the string representation.
  399. */
  400. public synchronized String toString() {
  401. String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
  402. return getClass().getName() + "[ " + params + " ]";
  403. }
  404. }