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