1 /* 2 * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.awt; 27 28 import java.awt.event.InputEvent; 29 import java.awt.event.KeyEvent; 30 import java.awt.image.BufferedImage; 31 import java.awt.image.DataBufferInt; 32 import java.awt.image.DirectColorModel; 33 import java.awt.image.Raster; 34 import java.awt.image.WritableRaster; 35 import java.awt.peer.RobotPeer; 36 import java.security.AccessController; 37 38 import sun.awt.AWTPermissions; 39 import sun.awt.ComponentFactory; 40 import sun.awt.ExtendedKeyCodes; 41 import sun.awt.SunToolkit; 42 import sun.awt.image.SunWritableRaster; 43 import sun.security.action.GetIntegerAction; 44 45 /** 46 * This class is used to generate native system input events 47 * for the purposes of test automation, self-running demos, and 48 * other applications where control of the mouse and keyboard 49 * is needed. The primary purpose of Robot is to facilitate 50 * automated testing of Java platform implementations. 51 * <p> 52 * Using the class to generate input events differs from posting 53 * events to the AWT event queue or AWT components in that the 54 * events are generated in the platform's native input 55 * queue. For example, <code>Robot.mouseMove</code> will actually move 56 * the mouse cursor instead of just generating mouse move events. 57 * <p> 58 * Robot uses delay {@link #getSyncDelay()} to make syncing threads 59 * with {@link #waitForIdle()} more stable. This delay can be set once 60 * on creating object and could not be changed throughout object lifecycle. 61 * Constructor reads vm integer property {@code java.awt.robotdelay} and 62 * sets the delay value equal to the property value. If the property was 63 * not set 500 milliseconds default value is used. 64 * <p> 65 * Note that some platforms require special privileges or extensions 66 * to access low-level input control. If the current platform configuration 67 * does not allow input control, an <code>AWTException</code> will be thrown 68 * when trying to construct Robot objects. For example, X-Window systems 69 * will throw the exception if the XTEST 2.2 standard extension is not supported 70 * (or not enabled) by the X server. 71 * <p> 72 * Applications that use Robot for purposes other than self-testing should 73 * handle these error conditions gracefully. 74 * 75 * @author Robi Khan 76 * @since 1.3 77 */ 78 public class Robot { 79 private static final int MAX_DELAY = 60000; 80 private RobotPeer peer; 81 private boolean isAutoWaitForIdle = false; 82 private int autoDelay = 0; 83 private static int LEGAL_BUTTON_MASK = 0; 84 private static int DEFAULT_SPEED = 20; // Speed for mouse glide and click 85 private static int DEFAULT_SYNC_DELAY = 500; // Default Additional delay for waitForIdle() 86 private static int DEFAULT_STEP_LENGTH = 2; // Step length (in pixels) for mouse glide 87 88 private final int syncDelay; 89 { 90 syncDelay = AccessController.doPrivileged( 91 new GetIntegerAction("java.awt.robotdelay", DEFAULT_SYNC_DELAY)); 92 } 93 94 95 private DirectColorModel screenCapCM = null; 96 97 /** 98 * Constructs a Robot object in the coordinate system of the primary screen. 99 * 100 * @throws AWTException if the platform configuration does not allow 101 * low-level input control. This exception is always thrown when 102 * GraphicsEnvironment.isHeadless() returns true 103 * @throws SecurityException if <code>createRobot</code> permission is not granted 104 * @see java.awt.GraphicsEnvironment#isHeadless 105 * @see SecurityManager#checkPermission 106 * @see AWTPermission 107 */ 108 public Robot() throws AWTException { 109 if (GraphicsEnvironment.isHeadless()) { 110 throw new AWTException("headless environment"); 111 } 112 init(GraphicsEnvironment.getLocalGraphicsEnvironment() 113 .getDefaultScreenDevice()); 114 } 115 116 /** 117 * Creates a Robot for the given screen device. Coordinates passed 118 * to Robot method calls like mouseMove and createScreenCapture will 119 * be interpreted as being in the same coordinate system as the 120 * specified screen. Note that depending on the platform configuration, 121 * multiple screens may either: 122 * <ul> 123 * <li>share the same coordinate system to form a combined virtual screen</li> 124 * <li>use different coordinate systems to act as independent screens</li> 125 * </ul> 126 * This constructor is meant for the latter case. 127 * <p> 128 * If screen devices are reconfigured such that the coordinate system is 129 * affected, the behavior of existing Robot objects is undefined. 130 * 131 * @param screen A screen GraphicsDevice indicating the coordinate 132 * system the Robot will operate in. 133 * @throws AWTException if the platform configuration does not allow 134 * low-level input control. This exception is always thrown when 135 * GraphicsEnvironment.isHeadless() returns true. 136 * @throws IllegalArgumentException if <code>screen</code> is not a screen 137 * GraphicsDevice. 138 * @throws SecurityException if <code>createRobot</code> permission is not granted 139 * @see java.awt.GraphicsEnvironment#isHeadless 140 * @see GraphicsDevice 141 * @see SecurityManager#checkPermission 142 * @see AWTPermission 143 */ 144 public Robot(GraphicsDevice screen) throws AWTException { 145 checkIsScreenDevice(screen); 146 init(screen); 147 } 148 149 private void init(GraphicsDevice screen) throws AWTException { 150 checkRobotAllowed(); 151 Toolkit toolkit = Toolkit.getDefaultToolkit(); 152 if (toolkit instanceof ComponentFactory) { 153 peer = ((ComponentFactory)toolkit).createRobot(this, screen); 154 disposer = new RobotDisposer(peer); 155 sun.java2d.Disposer.addRecord(anchor, disposer); 156 } 157 initLegalButtonMask(); 158 } 159 160 private static synchronized void initLegalButtonMask() { 161 if (LEGAL_BUTTON_MASK != 0) return; 162 163 int tmpMask = 0; 164 if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){ 165 if (Toolkit.getDefaultToolkit() instanceof SunToolkit) { 166 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons(); 167 for (int i = 0; i < buttonsNumber; i++){ 168 tmpMask |= InputEvent.getMaskForButton(i+1); 169 } 170 } 171 } 172 tmpMask |= InputEvent.BUTTON1_MASK| 173 InputEvent.BUTTON2_MASK| 174 InputEvent.BUTTON3_MASK| 175 InputEvent.BUTTON1_DOWN_MASK| 176 InputEvent.BUTTON2_DOWN_MASK| 177 InputEvent.BUTTON3_DOWN_MASK; 178 LEGAL_BUTTON_MASK = tmpMask; 179 } 180 181 /* determine if the security policy allows Robot's to be created */ 182 private void checkRobotAllowed() { 183 SecurityManager security = System.getSecurityManager(); 184 if (security != null) { 185 security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION); 186 } 187 } 188 189 /* check if the given device is a screen device */ 190 private void checkIsScreenDevice(GraphicsDevice device) { 191 if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) { 192 throw new IllegalArgumentException("not a valid screen device"); 193 } 194 } 195 196 private transient Object anchor = new Object(); 197 198 static class RobotDisposer implements sun.java2d.DisposerRecord { 199 private final RobotPeer peer; 200 public RobotDisposer(RobotPeer peer) { 201 this.peer = peer; 202 } 203 public void dispose() { 204 if (peer != null) { 205 peer.dispose(); 206 } 207 } 208 } 209 210 private transient RobotDisposer disposer; 211 212 /** 213 * Moves mouse pointer to given screen coordinates. 214 * @param x X position 215 * @param y Y position 216 */ 217 public synchronized void mouseMove(int x, int y) { 218 peer.mouseMove(x, y); 219 afterEvent(); 220 } 221 222 /** 223 * Moves mouse pointer to given screen coordinates. 224 * 225 * @param position Target position 226 * 227 * @see #mouseMove(int, int) 228 */ 229 public synchronized void mouseMove(Point position) { 230 mouseMove(position.x, position.y); 231 } 232 233 /** 234 * Move mouse cursor to the center of the Component. 235 * 236 * @param c Component the mouse is placed over 237 * 238 * @see #mouseMove(int, int) 239 */ 240 public synchronized void mouseMove(Component c) { 241 Point p = c.getLocationOnScreen(); 242 Dimension size = c.getSize(); 243 p.x += size.width / 2; 244 p.y += size.height / 2; 245 mouseMove(p.x, p.y); 246 } 247 248 /** 249 * Move the mouse in multiple steps from where it is 250 * now to the destination coordinates. 251 * 252 * @param x Destination point x coordinate 253 * @param y Destination point y coordinate 254 * 255 * @see #glide(int, int, int, int) 256 */ 257 public void glide(int x, int y) { 258 Point p = MouseInfo.getPointerInfo().getLocation(); 259 glide(p.x, p.y, x, y); 260 } 261 262 /** 263 * Move the mouse in multiple steps from where it is 264 * now to the destination point. 265 * 266 * @param dest Destination point 267 * 268 * @see #glide(int, int) 269 */ 270 public void glide(Point dest) { 271 glide(dest.x, dest.y); 272 } 273 274 /** 275 * Move the mouse in multiple steps from source coordinates 276 * to the destination coordinates. 277 * 278 * @param fromX Source point x coordinate 279 * @param fromY Source point y coordinate 280 * @param toX Destination point x coordinate 281 * @param toY Destination point y coordinate 282 * 283 * @see #glide(int, int, int, int, int, int) 284 */ 285 public void glide(int fromX, int fromY, int toX, int toY) { 286 glide(fromX, fromY, toX, toY, DEFAULT_STEP_LENGTH, DEFAULT_SPEED); 287 } 288 289 /** 290 * Move the mouse in multiple steps from source point to the 291 * destination point with default speed and step length. 292 * 293 * @param src Source point 294 * @param dest Destination point 295 * 296 * @see #glide(int, int, int, int, int, int) 297 */ 298 public void glide(Point src, Point dest) { 299 glide(src.x, src.y, dest.x, dest.y, DEFAULT_STEP_LENGTH, DEFAULT_SPEED); 300 } 301 302 /** 303 * Move the mouse in multiple steps from source point to the 304 * destination point with given speed and step length. 305 * 306 * @param srcX Source point x cordinate 307 * @param srcY Source point y cordinate 308 * @param destX Destination point x cordinate 309 * @param destY Destination point y cordinate 310 * @param stepLength Approximate length of one step 311 * @param speed Delay between steps. 312 * 313 * @see #mouseMove(int, int) 314 * @see #delay(int) 315 */ 316 public void glide(int srcX, int srcY, int destX, int destY, int stepLength, int speed) { 317 int stepNum; 318 double tDx, tDy; 319 double dx, dy, ds; 320 double x, y; 321 322 dx = (destX - srcX); 323 dy = (destY - srcY); 324 ds = Math.sqrt(dx*dx + dy*dy); 325 326 tDx = dx / ds * stepLength; 327 tDy = dy / ds * stepLength; 328 329 int stepsCount = (int) ds / stepLength; 330 331 // Walk the mouse to the destination one step at a time 332 mouseMove(srcX, srcY); 333 334 for (x = srcX, y = srcY, stepNum = 0; 335 stepNum < stepsCount; 336 stepNum++) { 337 x += tDx; 338 y += tDy; 339 mouseMove((int)x, (int)y); 340 delay(speed); 341 } 342 343 // Ensure the mouse moves to the right destination. 344 // The steps may have led the mouse to a slightly wrong place. 345 mouseMove(destX, destY); 346 } 347 348 /** 349 * Presses one or more mouse buttons. The mouse buttons should 350 * be released using the {@link #mouseRelease(int)} method. 351 * 352 * @param buttons the Button mask; a combination of one or more 353 * mouse button masks. 354 * <p> 355 * It is allowed to use only a combination of valid values as a {@code buttons} parameter. 356 * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, 357 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} 358 * and values returned by the 359 * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. 360 * 361 * The valid combination also depends on a 362 * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: 363 * <ul> 364 * <li> If support for extended mouse buttons is 365 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 366 * then it is allowed to use only the following standard button masks: 367 * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK}, 368 * {@code InputEvent.BUTTON3_DOWN_MASK}. 369 * <li> If support for extended mouse buttons is 370 * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 371 * then it is allowed to use the standard button masks 372 * and masks for existing extended mouse buttons, if the mouse has more then three buttons. 373 * In that way, it is allowed to use the button masks corresponding to the buttons 374 * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}. 375 * <br> 376 * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} 377 * method to obtain the mask for any mouse button by its number. 378 * </ul> 379 * <p> 380 * The following standard button masks are also accepted: 381 * <ul> 382 * <li>{@code InputEvent.BUTTON1_MASK} 383 * <li>{@code InputEvent.BUTTON2_MASK} 384 * <li>{@code InputEvent.BUTTON3_MASK} 385 * </ul> 386 * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK}, 387 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} instead. 388 * Either extended {@code _DOWN_MASK} or old {@code _MASK} values 389 * should be used, but both those models should not be mixed. 390 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 391 * and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 392 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 393 * that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 394 * @see #mouseRelease(int) 395 * @see InputEvent#getMaskForButton(int) 396 * @see Toolkit#areExtraMouseButtonsEnabled() 397 * @see java.awt.MouseInfo#getNumberOfButtons() 398 * @see java.awt.event.MouseEvent 399 */ 400 public synchronized void mousePress(int buttons) { 401 checkButtonsArgument(buttons); 402 peer.mousePress(buttons); 403 afterEvent(); 404 } 405 406 /** 407 * Releases one or more mouse buttons. 408 * 409 * @param buttons the Button mask; a combination of one or more 410 * mouse button masks. 411 * <p> 412 * It is allowed to use only a combination of valid values as a {@code buttons} parameter. 413 * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, 414 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} 415 * and values returned by the 416 * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. 417 * 418 * The valid combination also depends on a 419 * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: 420 * <ul> 421 * <li> If the support for extended mouse buttons is 422 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 423 * then it is allowed to use only the following standard button masks: 424 * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK}, 425 * {@code InputEvent.BUTTON3_DOWN_MASK}. 426 * <li> If the support for extended mouse buttons is 427 * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 428 * then it is allowed to use the standard button masks 429 * and masks for existing extended mouse buttons, if the mouse has more then three buttons. 430 * In that way, it is allowed to use the button masks corresponding to the buttons 431 * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}. 432 * <br> 433 * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} 434 * method to obtain the mask for any mouse button by its number. 435 * </ul> 436 * <p> 437 * The following standard button masks are also accepted: 438 * <ul> 439 * <li>{@code InputEvent.BUTTON1_MASK} 440 * <li>{@code InputEvent.BUTTON2_MASK} 441 * <li>{@code InputEvent.BUTTON3_MASK} 442 * </ul> 443 * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK}, 444 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} instead. 445 * Either extended {@code _DOWN_MASK} or old {@code _MASK} values 446 * should be used, but both those models should not be mixed. 447 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 448 * and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 449 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 450 * that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 451 * @see #mousePress(int) 452 * @see InputEvent#getMaskForButton(int) 453 * @see Toolkit#areExtraMouseButtonsEnabled() 454 * @see java.awt.MouseInfo#getNumberOfButtons() 455 * @see java.awt.event.MouseEvent 456 */ 457 public synchronized void mouseRelease(int buttons) { 458 checkButtonsArgument(buttons); 459 peer.mouseRelease(buttons); 460 afterEvent(); 461 } 462 463 /** 464 * Clicks mouse button(s) by calling {@link #mousePress(int)} and 465 * {@link #mouseRelease(int)} methods 466 * 467 * 468 * @param buttons The button mask; a combination of one or more mouse button masks. 469 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for 470 * extra mouse button and support for extended mouse buttons is 471 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 472 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for 473 * extra mouse button that does not exist on the mouse and support for extended 474 * mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} 475 * by Java 476 * 477 * @see #mousePress(int) 478 * @see #mouseRelease(int) 479 * @see InputEvent#getMaskForButton(int) 480 * @see Toolkit#areExtraMouseButtonsEnabled() 481 * @see java.awt.event.MouseEvent 482 */ 483 public void click(int buttons) { 484 mousePress(buttons); 485 waitForIdle(DEFAULT_SPEED); 486 mouseRelease(buttons); 487 waitForIdle(); 488 } 489 490 /** 491 * Clicks mouse button 1 492 * 493 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for 494 * extra mouse button and support for extended mouse buttons is 495 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 496 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for 497 * extra mouse button that does not exist on the mouse and support for extended 498 * mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} 499 * by Java 500 * 501 * @see #click(int) 502 */ 503 public void click() { 504 click(InputEvent.BUTTON1_DOWN_MASK); 505 } 506 507 /** 508 * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}. 509 * The method successively moves mouse cursor to point with coordinates 510 * ({@code fromX}, {@code fromY}), presses mouse button 1, drag mouse to 511 * point with coordinates ({@code toX}, {@code toY}) and releases mouse 512 * button 1 at last. 513 * 514 * @param fromX Source point x coordinate 515 * @param fromY Source point y coordinate 516 * @param toX Destination point x coordinate 517 * @param toY Destination point y coordinate 518 * 519 * @see #mousePress(int) 520 * @see #glide(int, int, int, int) 521 */ 522 public void dragAndDrop(int fromX, int fromY, int toX, int toY) { 523 mouseMove(fromX, fromY); 524 mousePress(InputEvent.BUTTON1_DOWN_MASK); 525 waitForIdle(); 526 glide(toX, toY); 527 mouseRelease(InputEvent.BUTTON1_DOWN_MASK); 528 waitForIdle(); 529 } 530 531 /** 532 * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}. 533 * The method successively moves mouse cursor to point {@code from}, 534 * presses mouse button 1, drag mouse to point {@code to} and releases 535 * mouse button 1 at last. 536 * 537 * @param from Source point 538 * @param to Destination point 539 * 540 * @see #mousePress(int) 541 * @see #glide(int, int, int, int) 542 * @see #dragAndDrop(int, int, int, int) 543 */ 544 public void dragAndDrop(Point from, Point to) { 545 dragAndDrop(from.x, from.y, to.x, to.y); 546 } 547 548 private void checkButtonsArgument(int buttons) { 549 if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) { 550 throw new IllegalArgumentException("Invalid combination of button flags"); 551 } 552 } 553 554 /** 555 * Rotates the scroll wheel on wheel-equipped mice. 556 * 557 * @param wheelAmt number of "notches" to move the mouse wheel 558 * Negative values indicate movement up/away from the user, 559 * positive values indicate movement down/towards the user. 560 * 561 * @since 1.4 562 */ 563 public synchronized void mouseWheel(int wheelAmt) { 564 peer.mouseWheel(wheelAmt); 565 afterEvent(); 566 } 567 568 /** 569 * Presses a given key. The key should be released using the 570 * <code>keyRelease</code> method. 571 * <p> 572 * Key codes that have more than one physical key associated with them 573 * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the 574 * left or right shift key) will map to the left key. 575 * 576 * @param keycode Key to press (e.g. <code>KeyEvent.VK_A</code>) 577 * @throws IllegalArgumentException if <code>keycode</code> is not 578 * a valid key 579 * @see #keyRelease(int) 580 * @see java.awt.event.KeyEvent 581 */ 582 public synchronized void keyPress(int keycode) { 583 checkKeycodeArgument(keycode); 584 peer.keyPress(keycode); 585 afterEvent(); 586 } 587 588 /** 589 * Releases a given key. 590 * <p> 591 * Key codes that have more than one physical key associated with them 592 * (e.g. <code>KeyEvent.VK_SHIFT</code> could mean either the 593 * left or right shift key) will map to the left key. 594 * 595 * @param keycode Key to release (e.g. <code>KeyEvent.VK_A</code>) 596 * @throws IllegalArgumentException if <code>keycode</code> is not a 597 * valid key 598 * @see #keyPress(int) 599 * @see java.awt.event.KeyEvent 600 */ 601 public synchronized void keyRelease(int keycode) { 602 checkKeycodeArgument(keycode); 603 peer.keyRelease(keycode); 604 afterEvent(); 605 } 606 607 /** 608 * Successively presses and releases a given key. 609 * <p> 610 * Key codes that have more than one physical key associated with them 611 * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the 612 * left or right shift key) will map to the left key. 613 * 614 * @param keycode Key to press (e.g. {@code KeyEvent.VK_A}) 615 * @throws IllegalArgumentException if {@code keycode} is not 616 * a valid key 617 * 618 * @see #keyPress(int) 619 * @see #keyRelease(int) 620 * @see java.awt.event.KeyEvent 621 */ 622 public void type(int keycode) { 623 keyPress(keycode); 624 waitForIdle(DEFAULT_SPEED); 625 keyRelease(keycode); 626 waitForIdle(); 627 } 628 629 /** 630 * Types given character 631 * 632 * @param c Character to be typed (e.g. {@code 'a'}) 633 * 634 * @see #type(int) 635 * @see java.awt.event.KeyEvent 636 */ 637 public void type(char c) { 638 type(ExtendedKeyCodes.getExtendedKeyCodeForChar(c)); 639 } 640 641 /** 642 * Types given array of characters one by one 643 * 644 * @param symbols Array of characters to be typed 645 * 646 * @see #type(char) 647 */ 648 public void type(char[] symbols) { 649 for (int i = 0; i < symbols.length; i++) { 650 type(symbols[i]); 651 } 652 } 653 654 /** 655 * Types given string 656 * 657 * @param s String to be typed 658 * 659 * @see #type(char[]) 660 */ 661 public void type(String s) { 662 type(s.toCharArray()); 663 } 664 665 private void checkKeycodeArgument(int keycode) { 666 // rather than build a big table or switch statement here, we'll 667 // just check that the key isn't VK_UNDEFINED and assume that the 668 // peer implementations will throw an exception for other bogus 669 // values e.g. -1, 999999 670 if (keycode == KeyEvent.VK_UNDEFINED) { 671 throw new IllegalArgumentException("Invalid key code"); 672 } 673 } 674 675 /** 676 * Returns the color of a pixel at the given screen coordinates. 677 * @param x X position of pixel 678 * @param y Y position of pixel 679 * @return Color of the pixel 680 */ 681 public synchronized Color getPixelColor(int x, int y) { 682 Color color = new Color(peer.getRGBPixel(x, y)); 683 return color; 684 } 685 686 /** 687 * Creates an image containing pixels read from the screen. This image does 688 * not include the mouse cursor. 689 * @param screenRect Rect to capture in screen coordinates 690 * @return The captured image 691 * @throws IllegalArgumentException if <code>screenRect</code> width and height are not greater than zero 692 * @throws SecurityException if <code>readDisplayPixels</code> permission is not granted 693 * @see SecurityManager#checkPermission 694 * @see AWTPermission 695 */ 696 public synchronized BufferedImage createScreenCapture(Rectangle screenRect) { 697 checkScreenCaptureAllowed(); 698 699 checkValidRect(screenRect); 700 701 BufferedImage image; 702 DataBufferInt buffer; 703 WritableRaster raster; 704 705 if (screenCapCM == null) { 706 /* 707 * Fix for 4285201 708 * Create a DirectColorModel equivalent to the default RGB ColorModel, 709 * except with no Alpha component. 710 */ 711 712 screenCapCM = new DirectColorModel(24, 713 /* red mask */ 0x00FF0000, 714 /* green mask */ 0x0000FF00, 715 /* blue mask */ 0x000000FF); 716 } 717 718 // need to sync the toolkit prior to grabbing the pixels since in some 719 // cases rendering to the screen may be delayed 720 Toolkit.getDefaultToolkit().sync(); 721 722 int pixels[]; 723 int[] bandmasks = new int[3]; 724 725 pixels = peer.getRGBPixels(screenRect); 726 buffer = new DataBufferInt(pixels, pixels.length); 727 728 bandmasks[0] = screenCapCM.getRedMask(); 729 bandmasks[1] = screenCapCM.getGreenMask(); 730 bandmasks[2] = screenCapCM.getBlueMask(); 731 732 raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null); 733 SunWritableRaster.makeTrackable(buffer); 734 735 image = new BufferedImage(screenCapCM, raster, false, null); 736 737 return image; 738 } 739 740 private static void checkValidRect(Rectangle rect) { 741 if (rect.width <= 0 || rect.height <= 0) { 742 throw new IllegalArgumentException("Rectangle width and height must be > 0"); 743 } 744 } 745 746 private static void checkScreenCaptureAllowed() { 747 SecurityManager security = System.getSecurityManager(); 748 if (security != null) { 749 security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION); 750 } 751 } 752 753 /* 754 * Called after an event is generated 755 */ 756 private void afterEvent() { 757 autoWaitForIdle(); 758 autoDelay(); 759 } 760 761 /** 762 * Returns whether this Robot automatically invokes <code>waitForIdle</code> 763 * after generating an event. 764 * @return Whether <code>waitForIdle</code> is automatically called 765 */ 766 public synchronized boolean isAutoWaitForIdle() { 767 return isAutoWaitForIdle; 768 } 769 770 /** 771 * Sets whether this Robot automatically invokes <code>waitForIdle</code> 772 * after generating an event. 773 * @param isOn Whether <code>waitForIdle</code> is automatically invoked 774 */ 775 public synchronized void setAutoWaitForIdle(boolean isOn) { 776 isAutoWaitForIdle = isOn; 777 } 778 779 /* 780 * Calls waitForIdle after every event if so desired. 781 */ 782 private void autoWaitForIdle() { 783 if (isAutoWaitForIdle) { 784 waitForIdle(); 785 } 786 } 787 788 /** 789 * Returns delay length for {@link #waitForIdle()} method 790 * 791 * @return Current delay value 792 * 793 * @see #waitForIdle() 794 */ 795 public int getSyncDelay() { 796 return this.syncDelay; 797 } 798 799 /** 800 * Returns the number of milliseconds this Robot sleeps after generating an event. 801 * 802 * @return the delay duration in milliseconds 803 */ 804 public synchronized int getAutoDelay() { 805 return autoDelay; 806 } 807 808 /** 809 * Sets the number of milliseconds this Robot sleeps after generating an event. 810 * 811 * @param ms the delay duration in milliseconds 812 * @throws IllegalArgumentException If {@code ms} 813 * is not between 0 and 60,000 milliseconds inclusive 814 */ 815 public synchronized void setAutoDelay(int ms) { 816 checkDelayArgument(ms); 817 autoDelay = ms; 818 } 819 820 /* 821 * Automatically sleeps for the specified interval after event generated. 822 */ 823 private void autoDelay() { 824 delay(autoDelay); 825 } 826 827 /** 828 * Sleeps for the specified time. 829 * To catch any <code>InterruptedException</code>s that occur, 830 * <code>Thread.sleep()</code> may be used instead. 831 * 832 * @param ms time to sleep in milliseconds 833 * @throws IllegalArgumentException if {@code ms} 834 * is not between 0 and 60,000 milliseconds inclusive 835 * @see java.lang.Thread#sleep 836 */ 837 public synchronized void delay(int ms) { 838 checkDelayArgument(ms); 839 try { 840 Thread.sleep(ms); 841 } catch(InterruptedException ite) { 842 ite.printStackTrace(); 843 } 844 } 845 846 private void checkDelayArgument(int ms) { 847 if (ms < 0 || ms > MAX_DELAY) { 848 throw new IllegalArgumentException("Delay must be to 0 to 60,000ms"); 849 } 850 } 851 852 /** 853 * Waits until all events currently on the event queue have been processed with given 854 * delay after syncing threads. 855 * 856 * @param delayValue Additional delay length in milliseconds to wait until thread 857 * sync been completed 858 * @throws sun.awt.SunToolkit.IllegalThreadException if called on the AWT event 859 * dispatching thread 860 */ 861 public synchronized void waitForIdle(int delayValue) { 862 SunToolkit.flushPendingEvents(); 863 ((SunToolkit) Toolkit.getDefaultToolkit()).realSync(); 864 delay(delayValue); 865 } 866 867 /** 868 * Static method for synchronizing event queue with current thread. Waits until 869 * all events currently on the event queue have been processed with a small delay 870 * of 500 ms after syncing threads. 871 * 872 * @throws sun.awt.SunToolkit.IllegalThreadException if called on the AWT event 873 * dispatching thread 874 */ 875 public static synchronized void syncEventQueue() { 876 SunToolkit.flushPendingEvents(); 877 ((SunToolkit) Toolkit.getDefaultToolkit()).realSync(); 878 try { 879 Thread.sleep(DEFAULT_SYNC_DELAY); 880 } catch (InterruptedException ite) { 881 ite.printStackTrace(); 882 } 883 } 884 885 /** 886 * Waits until all events currently on the event queue have been processed with delay 887 * {@link #getSyncDelay()} after syncing threads. 888 * 889 * @throws sun.awt.SunToolkit.IllegalThreadException if called on the AWT event 890 * dispatching thread 891 * 892 * @see #waitForIdle(int) 893 */ 894 public synchronized void waitForIdle() { 895 waitForIdle(syncDelay); 896 } 897 898 private void checkNotDispatchThread() { 899 if (EventQueue.isDispatchThread()) { 900 throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread"); 901 } 902 } 903 904 /** 905 * Returns a string representation of this Robot. 906 * 907 * @return the string representation. 908 */ 909 public synchronized String toString() { 910 String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle(); 911 return getClass().getName() + "[ " + params + " ]"; 912 } 913 }