1. /*
  2. * @(#)Robot.java 1.24 03/01/23
  3. *
  4. * Copyright 2003 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.24, 01/23/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.
  134. *
  135. * @param buttons the Button mask; a combination of one or more
  136. * of these flags:
  137. * <ul>
  138. * <li><code>InputEvent.BUTTON1_MASK</code>
  139. * <li><code>InputEvent.BUTTON2_MASK</code>
  140. * <li><code>InputEvent.BUTTON3_MASK</code>
  141. * </ul>
  142. * @throws IllegalArgumentException if the button mask is not a
  143. * valid combination
  144. */
  145. public synchronized void mousePress(int buttons) {
  146. checkButtonsArgument(buttons);
  147. peer.mousePress(buttons);
  148. afterEvent();
  149. }
  150. /**
  151. * Releases one or more mouse buttons.
  152. *
  153. * @param buttons the Button mask; a combination of one or more
  154. * of these flags:
  155. * <ul>
  156. * <li><code>InputEvent.BUTTON1_MASK</code>
  157. * <li><code>InputEvent.BUTTON2_MASK</code>
  158. * <li><code>InputEvent.BUTTON3_MASK</code>
  159. * </ul>
  160. * @throws IllegalArgumentException if the button mask is not a valid
  161. * combination
  162. */
  163. public synchronized void mouseRelease(int buttons) {
  164. checkButtonsArgument(buttons);
  165. peer.mouseRelease(buttons);
  166. afterEvent();
  167. }
  168. private void checkButtonsArgument(int buttons) {
  169. if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) {
  170. throw new IllegalArgumentException("Invalid combination of button flags");
  171. }
  172. }
  173. /**
  174. * Rotates the scroll wheel on wheel-equipped mice.
  175. *
  176. * @param wheelAmt number of "notches" to move the mouse wheel
  177. * Negative values indicate movement up/away from the user,
  178. * positive values indicate movement down/towards the user.
  179. *
  180. * @since 1.4
  181. */
  182. public synchronized void mouseWheel(int wheelAmt) {
  183. peer.mouseWheel(wheelAmt);
  184. afterEvent();
  185. }
  186. /**
  187. * Presses a given key.
  188. * <p>
  189. * Key codes that have more than one physical key associated with them
  190. * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
  191. * left or right shift key) will map to the left key.
  192. *
  193. * @param keycode Key to press (e.g. <code>KeyEvent.VK_A</code>)
  194. * @throws IllegalArgumentException if <code>keycode</code> is not a valid key
  195. * @see java.awt.event.KeyEvent
  196. */
  197. public synchronized void keyPress(int keycode) {
  198. checkKeycodeArgument(keycode);
  199. peer.keyPress(keycode);
  200. afterEvent();
  201. }
  202. /**
  203. * Releases a given key.
  204. * <p>
  205. * Key codes that have more than one physical key associated with them
  206. * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the
  207. * left or right shift key) will map to the left key.
  208. *
  209. * @param keycode Key to release (e.g. <code>KeyEvent.VK_A</code>)
  210. * @throws IllegalArgumentException if <code>keycode</code> is not a valid key
  211. * @see java.awt.event.KeyEvent
  212. */
  213. public synchronized void keyRelease(int keycode) {
  214. checkKeycodeArgument(keycode);
  215. peer.keyRelease(keycode);
  216. afterEvent();
  217. }
  218. private void checkKeycodeArgument(int keycode) {
  219. // rather than build a big table or switch statement here, we'll
  220. // just check that the key isn't VK_UNDEFINED and assume that the
  221. // peer implementations will throw an exception for other bogus
  222. // values e.g. -1, 999999
  223. if (keycode == KeyEvent.VK_UNDEFINED) {
  224. throw new IllegalArgumentException("Invalid key code");
  225. }
  226. }
  227. /**
  228. * Returns the color of a pixel at the given screen coordinates.
  229. * @param x X position of pixel
  230. * @param y Y position of pixel
  231. * @return Color of the pixel
  232. */
  233. public synchronized Color getPixelColor(int x, int y) {
  234. Color color = new Color(peer.getRGBPixel(x,y));
  235. return color;
  236. }
  237. /**
  238. * Creates an image containing pixels read from the screen.
  239. * @param screenRect Rect to capture in screen coordinates
  240. * @return The captured image
  241. * @throws IllegalArgumentException if <code>screenRect</code> width and height are not greater than zero
  242. * @throws SecurityException if <code>readDisplayPixels</code> permission is not granted
  243. * @see SecurityManager#checkPermission
  244. * @see AWTPermission
  245. */
  246. public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
  247. checkScreenCaptureAllowed();
  248. checkValidRect(screenRect);
  249. BufferedImage image;
  250. DataBufferInt buffer;
  251. WritableRaster raster;
  252. if (screenCapCM == null) {
  253. /*
  254. * Fix for 4285201
  255. * Create a DirectColorModel equivalent to the default RGB ColorModel,
  256. * except with no Alpha component.
  257. */
  258. screenCapCM = new DirectColorModel(24,
  259. /* red mask */ 0x00FF0000,
  260. /* green mask */ 0x0000FF00,
  261. /* blue mask */ 0x000000FF);
  262. }
  263. int pixels[];
  264. int[] bandmasks = new int[3];
  265. pixels = peer.getRGBPixels(screenRect);
  266. buffer = new DataBufferInt(pixels, pixels.length);
  267. bandmasks[0] = screenCapCM.getRedMask();
  268. bandmasks[1] = screenCapCM.getGreenMask();
  269. bandmasks[2] = screenCapCM.getBlueMask();
  270. raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
  271. image = new BufferedImage(screenCapCM, raster, false, null);
  272. return image;
  273. }
  274. private static void checkValidRect(Rectangle rect) {
  275. if (rect.width <= 0 || rect.height <= 0) {
  276. throw new IllegalArgumentException("Rectangle width and height must be > 0");
  277. }
  278. }
  279. private static void checkScreenCaptureAllowed() {
  280. SecurityManager security = System.getSecurityManager();
  281. if (security != null) {
  282. security.checkPermission(
  283. SecurityConstants.READ_DISPLAY_PIXELS_PERMISSION);
  284. }
  285. }
  286. /*
  287. * Called after an event is generated
  288. */
  289. private void afterEvent() {
  290. autoWaitForIdle();
  291. autoDelay();
  292. }
  293. /**
  294. * Returns whether this Robot automatically invokes <code>waitForIdle</code>
  295. * after generating an event.
  296. * @return Whether <code>waitForIdle</code> is automatically called
  297. */
  298. public synchronized boolean isAutoWaitForIdle() {
  299. return isAutoWaitForIdle;
  300. }
  301. /**
  302. * Sets whether this Robot automatically invokes <code>waitForIdle</code>
  303. * after generating an event.
  304. * @param isOn Whether <code>waitForIdle</code> is automatically invoked
  305. */
  306. public synchronized void setAutoWaitForIdle(boolean isOn) {
  307. isAutoWaitForIdle = isOn;
  308. }
  309. /*
  310. * Calls waitForIdle after every event if so desired.
  311. */
  312. private void autoWaitForIdle() {
  313. if (isAutoWaitForIdle) {
  314. waitForIdle();
  315. }
  316. }
  317. /**
  318. * Returns the number of milliseconds this Robot sleeps after generating an event.
  319. */
  320. public synchronized int getAutoDelay() {
  321. return autoDelay;
  322. }
  323. /**
  324. * Sets the number of milliseconds this Robot sleeps after generating an event.
  325. * @throws IllegalArgumentException If <code>ms</code> is not between 0 and 60,000 milliseconds inclusive
  326. */
  327. public synchronized void setAutoDelay(int ms) {
  328. checkDelayArgument(ms);
  329. autoDelay = ms;
  330. }
  331. /*
  332. * Automatically sleeps for the specified interval after event generated.
  333. */
  334. private void autoDelay() {
  335. delay(autoDelay);
  336. }
  337. /**
  338. * Sleeps for the specified time.
  339. * To catch any <code>InterruptedException</code>s that occur,
  340. * <code>Thread.sleep()</code> may be used instead.
  341. * @param ms time to sleep in milliseconds
  342. * @throws IllegalArgumentException if <code>ms</code> is not between 0 and 60,000 milliseconds inclusive
  343. * @see java.lang.Thread#sleep
  344. */
  345. public synchronized void delay(int ms) {
  346. checkDelayArgument(ms);
  347. try {
  348. Thread.sleep(ms);
  349. } catch(InterruptedException ite) {
  350. ite.printStackTrace();
  351. }
  352. }
  353. private void checkDelayArgument(int ms) {
  354. if (ms < 0 || ms > MAX_DELAY) {
  355. throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
  356. }
  357. }
  358. /**
  359. * Waits until all events currently on the event queue have been processed.
  360. * @throws IllegalThreadStateException if called on the AWT event dispatching thread
  361. */
  362. public synchronized void waitForIdle() {
  363. checkNotDispatchThread();
  364. // post a dummy event to the queue so we know when
  365. // all the events before it have been processed
  366. try {
  367. SunToolkit.flushPendingEvents();
  368. EventQueue.invokeAndWait( new Runnable() {
  369. public void run() {
  370. // dummy implementation
  371. }
  372. } );
  373. } catch(InterruptedException ite) {
  374. System.err.println("Robot.waitForIdle, non-fatal exception caught:");
  375. ite.printStackTrace();
  376. } catch(InvocationTargetException ine) {
  377. System.err.println("Robot.waitForIdle, non-fatal exception caught:");
  378. ine.printStackTrace();
  379. }
  380. }
  381. private void checkNotDispatchThread() {
  382. if (EventQueue.isDispatchThread()) {
  383. throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
  384. }
  385. }
  386. /**
  387. * Returns a string representation of this Robot.
  388. *
  389. * @return the string representation.
  390. */
  391. public synchronized String toString() {
  392. String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
  393. return getClass().getName() + "[ " + params + " ]";
  394. }
  395. }