1 /* 2 * Copyright (c) 1999, 2015, 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.geom.AffineTransform; 31 import java.awt.image.BaseMultiResolutionImage; 32 import java.awt.image.BufferedImage; 33 import java.awt.image.DataBufferInt; 34 import java.awt.image.DirectColorModel; 35 import java.awt.image.Raster; 36 import java.awt.image.WritableRaster; 37 import java.awt.peer.RobotPeer; 38 39 import sun.awt.AWTPermissions; 40 import sun.awt.ComponentFactory; 41 import sun.awt.SunToolkit; 42 import sun.awt.image.SunWritableRaster; 43 44 /** 45 * This class is used to generate native system input events 46 * for the purposes of test automation, self-running demos, and 47 * other applications where control of the mouse and keyboard 48 * is needed. The primary purpose of Robot is to facilitate 49 * automated testing of Java platform implementations. 50 * <p> 51 * Using the class to generate input events differs from posting 52 * events to the AWT event queue or AWT components in that the 53 * events are generated in the platform's native input 54 * queue. For example, {@code Robot.mouseMove} will actually move 55 * the mouse cursor instead of just generating mouse move events. 56 * <p> 57 * Note that some platforms require special privileges or extensions 58 * to access low-level input control. If the current platform configuration 59 * does not allow input control, an {@code AWTException} will be thrown 60 * when trying to construct Robot objects. For example, X-Window systems 61 * will throw the exception if the XTEST 2.2 standard extension is not supported 62 * (or not enabled) by the X server. 63 * <p> 64 * Applications that use Robot for purposes other than self-testing should 65 * handle these error conditions gracefully. 66 * 67 * @author Robi Khan 68 * @since 1.3 69 */ 70 public class Robot { 71 private static final int MAX_DELAY = 60000; 72 private RobotPeer peer; 73 private boolean isAutoWaitForIdle = false; 74 private int autoDelay = 0; 75 private static int LEGAL_BUTTON_MASK = 0; 76 77 private DirectColorModel screenCapCM = null; 78 79 /** 80 * Constructs a Robot object in the coordinate system of the primary screen. 81 * 82 * @throws AWTException if the platform configuration does not allow 83 * low-level input control. This exception is always thrown when 84 * GraphicsEnvironment.isHeadless() returns true 85 * @throws SecurityException if {@code createRobot} permission is not granted 86 * @see java.awt.GraphicsEnvironment#isHeadless 87 * @see SecurityManager#checkPermission 88 * @see AWTPermission 89 */ 90 public Robot() throws AWTException { 91 if (GraphicsEnvironment.isHeadless()) { 92 throw new AWTException("headless environment"); 93 } 94 init(GraphicsEnvironment.getLocalGraphicsEnvironment() 95 .getDefaultScreenDevice()); 96 } 97 98 /** 99 * Creates a Robot for the given screen device. Coordinates passed 100 * to Robot method calls like mouseMove and createScreenCapture will 101 * be interpreted as being in the same coordinate system as the 102 * specified screen. Note that depending on the platform configuration, 103 * multiple screens may either: 104 * <ul> 105 * <li>share the same coordinate system to form a combined virtual screen</li> 106 * <li>use different coordinate systems to act as independent screens</li> 107 * </ul> 108 * This constructor is meant for the latter case. 109 * <p> 110 * If screen devices are reconfigured such that the coordinate system is 111 * affected, the behavior of existing Robot objects is undefined. 112 * 113 * @param screen A screen GraphicsDevice indicating the coordinate 114 * system the Robot will operate in. 115 * @throws AWTException if the platform configuration does not allow 116 * low-level input control. This exception is always thrown when 117 * GraphicsEnvironment.isHeadless() returns true. 118 * @throws IllegalArgumentException if {@code screen} is not a screen 119 * GraphicsDevice. 120 * @throws SecurityException if {@code createRobot} permission is not granted 121 * @see java.awt.GraphicsEnvironment#isHeadless 122 * @see GraphicsDevice 123 * @see SecurityManager#checkPermission 124 * @see AWTPermission 125 */ 126 public Robot(GraphicsDevice screen) throws AWTException { 127 checkIsScreenDevice(screen); 128 init(screen); 129 } 130 131 private void init(GraphicsDevice screen) throws AWTException { 132 checkRobotAllowed(); 133 Toolkit toolkit = Toolkit.getDefaultToolkit(); 134 if (toolkit instanceof ComponentFactory) { 135 peer = ((ComponentFactory)toolkit).createRobot(this, screen); 136 disposer = new RobotDisposer(peer); 137 sun.java2d.Disposer.addRecord(anchor, disposer); 138 } 139 initLegalButtonMask(); 140 } 141 142 private static synchronized void initLegalButtonMask() { 143 if (LEGAL_BUTTON_MASK != 0) return; 144 145 int tmpMask = 0; 146 if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){ 147 if (Toolkit.getDefaultToolkit() instanceof SunToolkit) { 148 final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons(); 149 for (int i = 0; i < buttonsNumber; i++){ 150 tmpMask |= InputEvent.getMaskForButton(i+1); 151 } 152 } 153 } 154 tmpMask |= InputEvent.BUTTON1_MASK| 155 InputEvent.BUTTON2_MASK| 156 InputEvent.BUTTON3_MASK| 157 InputEvent.BUTTON1_DOWN_MASK| 158 InputEvent.BUTTON2_DOWN_MASK| 159 InputEvent.BUTTON3_DOWN_MASK; 160 LEGAL_BUTTON_MASK = tmpMask; 161 } 162 163 /* determine if the security policy allows Robot's to be created */ 164 private void checkRobotAllowed() { 165 SecurityManager security = System.getSecurityManager(); 166 if (security != null) { 167 security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION); 168 } 169 } 170 171 /* check if the given device is a screen device */ 172 private void checkIsScreenDevice(GraphicsDevice device) { 173 if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) { 174 throw new IllegalArgumentException("not a valid screen device"); 175 } 176 } 177 178 private transient Object anchor = new Object(); 179 180 static class RobotDisposer implements sun.java2d.DisposerRecord { 181 private final RobotPeer peer; 182 public RobotDisposer(RobotPeer peer) { 183 this.peer = peer; 184 } 185 public void dispose() { 186 if (peer != null) { 187 peer.dispose(); 188 } 189 } 190 } 191 192 private transient RobotDisposer disposer; 193 194 /** 195 * Moves mouse pointer to given screen coordinates. 196 * @param x X position 197 * @param y Y position 198 */ 199 public synchronized void mouseMove(int x, int y) { 200 peer.mouseMove(x, y); 201 afterEvent(); 202 } 203 204 /** 205 * Presses one or more mouse buttons. The mouse buttons should 206 * be released using the {@link #mouseRelease(int)} method. 207 * 208 * @param buttons the Button mask; a combination of one or more 209 * mouse button masks. 210 * <p> 211 * It is allowed to use only a combination of valid values as a {@code buttons} parameter. 212 * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, 213 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} 214 * and values returned by the 215 * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. 216 * 217 * The valid combination also depends on a 218 * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: 219 * <ul> 220 * <li> If support for extended mouse buttons is 221 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 222 * then it is allowed to use only the following standard button masks: 223 * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK}, 224 * {@code InputEvent.BUTTON3_DOWN_MASK}. 225 * <li> If support for extended mouse buttons is 226 * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 227 * then it is allowed to use the standard button masks 228 * and masks for existing extended mouse buttons, if the mouse has more then three buttons. 229 * In that way, it is allowed to use the button masks corresponding to the buttons 230 * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}. 231 * <br> 232 * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} 233 * method to obtain the mask for any mouse button by its number. 234 * </ul> 235 * <p> 236 * The following standard button masks are also accepted: 237 * <ul> 238 * <li>{@code InputEvent.BUTTON1_MASK} 239 * <li>{@code InputEvent.BUTTON2_MASK} 240 * <li>{@code InputEvent.BUTTON3_MASK} 241 * </ul> 242 * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK}, 243 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} instead. 244 * Either extended {@code _DOWN_MASK} or old {@code _MASK} values 245 * should be used, but both those models should not be mixed. 246 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 247 * and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 248 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 249 * that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 250 * @see #mouseRelease(int) 251 * @see InputEvent#getMaskForButton(int) 252 * @see Toolkit#areExtraMouseButtonsEnabled() 253 * @see java.awt.MouseInfo#getNumberOfButtons() 254 * @see java.awt.event.MouseEvent 255 */ 256 public synchronized void mousePress(int buttons) { 257 checkButtonsArgument(buttons); 258 peer.mousePress(buttons); 259 afterEvent(); 260 } 261 262 /** 263 * Releases one or more mouse buttons. 264 * 265 * @param buttons the Button mask; a combination of one or more 266 * mouse button masks. 267 * <p> 268 * It is allowed to use only a combination of valid values as a {@code buttons} parameter. 269 * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, 270 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} 271 * and values returned by the 272 * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. 273 * 274 * The valid combination also depends on a 275 * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: 276 * <ul> 277 * <li> If the support for extended mouse buttons is 278 * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 279 * then it is allowed to use only the following standard button masks: 280 * {@code InputEvent.BUTTON1_DOWN_MASK}, {@code InputEvent.BUTTON2_DOWN_MASK}, 281 * {@code InputEvent.BUTTON3_DOWN_MASK}. 282 * <li> If the support for extended mouse buttons is 283 * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 284 * then it is allowed to use the standard button masks 285 * and masks for existing extended mouse buttons, if the mouse has more then three buttons. 286 * In that way, it is allowed to use the button masks corresponding to the buttons 287 * in the range from 1 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}. 288 * <br> 289 * It is recommended to use the {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} 290 * method to obtain the mask for any mouse button by its number. 291 * </ul> 292 * <p> 293 * The following standard button masks are also accepted: 294 * <ul> 295 * <li>{@code InputEvent.BUTTON1_MASK} 296 * <li>{@code InputEvent.BUTTON2_MASK} 297 * <li>{@code InputEvent.BUTTON3_MASK} 298 * </ul> 299 * However, it is recommended to use {@code InputEvent.BUTTON1_DOWN_MASK}, 300 * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} instead. 301 * Either extended {@code _DOWN_MASK} or old {@code _MASK} values 302 * should be used, but both those models should not be mixed. 303 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 304 * and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java 305 * @throws IllegalArgumentException if the {@code buttons} mask contains the mask for extra mouse button 306 * that does not exist on the mouse and support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java 307 * @see #mousePress(int) 308 * @see InputEvent#getMaskForButton(int) 309 * @see Toolkit#areExtraMouseButtonsEnabled() 310 * @see java.awt.MouseInfo#getNumberOfButtons() 311 * @see java.awt.event.MouseEvent 312 */ 313 public synchronized void mouseRelease(int buttons) { 314 checkButtonsArgument(buttons); 315 peer.mouseRelease(buttons); 316 afterEvent(); 317 } 318 319 private void checkButtonsArgument(int buttons) { 320 if ( (buttons|LEGAL_BUTTON_MASK) != LEGAL_BUTTON_MASK ) { 321 throw new IllegalArgumentException("Invalid combination of button flags"); 322 } 323 } 324 325 /** 326 * Rotates the scroll wheel on wheel-equipped mice. 327 * 328 * @param wheelAmt number of "notches" to move the mouse wheel 329 * Negative values indicate movement up/away from the user, 330 * positive values indicate movement down/towards the user. 331 * 332 * @since 1.4 333 */ 334 public synchronized void mouseWheel(int wheelAmt) { 335 peer.mouseWheel(wheelAmt); 336 afterEvent(); 337 } 338 339 /** 340 * Presses a given key. The key should be released using the 341 * {@code keyRelease} method. 342 * <p> 343 * Key codes that have more than one physical key associated with them 344 * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the 345 * left or right shift key) will map to the left key. 346 * 347 * @param keycode Key to press (e.g. {@code KeyEvent.VK_A}) 348 * @throws IllegalArgumentException if {@code keycode} is not 349 * a valid key 350 * @see #keyRelease(int) 351 * @see java.awt.event.KeyEvent 352 */ 353 public synchronized void keyPress(int keycode) { 354 checkKeycodeArgument(keycode); 355 peer.keyPress(keycode); 356 afterEvent(); 357 } 358 359 /** 360 * Releases a given key. 361 * <p> 362 * Key codes that have more than one physical key associated with them 363 * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the 364 * left or right shift key) will map to the left key. 365 * 366 * @param keycode Key to release (e.g. {@code KeyEvent.VK_A}) 367 * @throws IllegalArgumentException if {@code keycode} is not a 368 * valid key 369 * @see #keyPress(int) 370 * @see java.awt.event.KeyEvent 371 */ 372 public synchronized void keyRelease(int keycode) { 373 checkKeycodeArgument(keycode); 374 peer.keyRelease(keycode); 375 afterEvent(); 376 } 377 378 private void checkKeycodeArgument(int keycode) { 379 // rather than build a big table or switch statement here, we'll 380 // just check that the key isn't VK_UNDEFINED and assume that the 381 // peer implementations will throw an exception for other bogus 382 // values e.g. -1, 999999 383 if (keycode == KeyEvent.VK_UNDEFINED) { 384 throw new IllegalArgumentException("Invalid key code"); 385 } 386 } 387 388 /** 389 * Returns the color of a pixel at the given screen coordinates. 390 * @param x X position of pixel 391 * @param y Y position of pixel 392 * @return Color of the pixel 393 */ 394 public synchronized Color getPixelColor(int x, int y) { 395 Color color = new Color(peer.getRGBPixel(x, y)); 396 return color; 397 } 398 399 private static int interp(int pixels[], int x, int y, int w, int h, 400 int fractx1, int fracty1) { 401 int fractx0 = 256 - fractx1; 402 int fracty0 = 256 - fracty1; 403 int i = y * w + x; 404 int rgb00 = (x < 0 || y < 0 || x >= w || y >= h) ? 0 : pixels[i]; 405 if (fracty1 == 0) { 406 // No interplation with pixels[y+1] 407 if (fractx1 == 0) { 408 // No interpolation with any neighbors 409 return rgb00; 410 } 411 int rgb10 = (y < 0 || x + 1 >= w || y >= h) ? 0 : pixels[i + 1]; 412 return interp(rgb00, rgb10, fractx0, fractx1); 413 } else if (fractx1 == 0) { 414 // No interpolation with pixels[x+1] 415 int rgb01 = (x < 0 || x >= w || y + 1 >= h) ? 0 : pixels[i + w]; 416 return interp(rgb00, rgb01, fracty0, fracty1); 417 } else { 418 // All 4 neighbors must be interpolated 419 int rgb10 = (y < 0 || x + 1 >= w || y >= h) ? 0 : pixels[i + 1]; 420 int rgb01 = (x < 0 || x >= w || y + 1 >= h) ? 0 : pixels[i + w]; 421 int rgb11 = (x + 1 >= w || y + 1 >= h) ? 0 : pixels[i + w + 1]; 422 return interp(interp(rgb00, rgb10, fractx0, fractx1), 423 interp(rgb01, rgb11, fractx0, fractx1), 424 fracty0, fracty1); 425 } 426 } 427 428 private static int interp(int rgb0, int rgb1, int fract0, int fract1) { 429 int a0 = (rgb0 >> 24) & 0xff; 430 int r0 = (rgb0 >> 16) & 0xff; 431 int g0 = (rgb0 >> 8) & 0xff; 432 int b0 = (rgb0) & 0xff; 433 int a1 = (rgb1 >> 24) & 0xff; 434 int r1 = (rgb1 >> 16) & 0xff; 435 int g1 = (rgb1 >> 8) & 0xff; 436 int b1 = (rgb1) & 0xff; 437 int a = (a0 * fract0 + a1 * fract1) >> 8; 438 int r = (r0 * fract0 + r1 * fract1) >> 8; 439 int g = (g0 * fract0 + g1 * fract1) >> 8; 440 int b = (b0 * fract0 + b1 * fract1) >> 8; 441 return (a << 24) | (r << 16) | (g << 8) | b; 442 } 443 444 /** 445 * Creates an image containing pixels read from the screen. 446 * This image does not include the mouse cursor. 447 * Returns BufferedImage for Non-HiDPI display and MultiResolutionImage 448 * for HiDPI display with two resolution variants. 449 * @param screenRect Rect to capture in screen coordinates 450 * @return The captured image 451 * @throws IllegalArgumentException if {@code screenRect} width and height are not greater than zero 452 * @throws SecurityException if {@code readDisplayPixels} permission is not granted 453 * @see SecurityManager#checkPermission 454 * @see AWTPermission 455 */ 456 457 public synchronized Image createHiDPIScreenCapture(Rectangle screenRect) { 458 checkScreenCaptureAllowed(); 459 460 checkValidRect(screenRect); 461 462 BufferedImage lowResolutionImage; 463 BufferedImage highResolutionImage; 464 DataBufferInt buffer; 465 WritableRaster raster; 466 467 if (screenCapCM == null) { 468 /* 469 * Fix for 4285201 470 * Create a DirectColorModel equivalent to the default RGB ColorModel, 471 * except with no Alpha component. 472 */ 473 474 screenCapCM = new DirectColorModel(24, 475 /* red mask */ 0x00FF0000, 476 /* green mask */ 0x0000FF00, 477 /* blue mask */ 0x000000FF); 478 } 479 480 int[] bandmasks = new int[3]; 481 bandmasks[0] = screenCapCM.getRedMask(); 482 bandmasks[1] = screenCapCM.getGreenMask(); 483 bandmasks[2] = screenCapCM.getBlueMask(); 484 485 // need to sync the toolkit prior to grabbing the pixels since in some 486 // cases rendering to the screen may be delayed 487 Toolkit.getDefaultToolkit().sync(); 488 AffineTransform tx = GraphicsEnvironment. 489 getLocalGraphicsEnvironment().getDefaultScreenDevice(). 490 getDefaultConfiguration().getDefaultTransform(); 491 double uiScaleX = tx.getScaleX(); 492 double uiScaleY = tx.getScaleY(); 493 int pixels[]; 494 495 // Return normal screenCapture for Non-HiDPI Display 496 if(uiScaleX ==1 && uiScaleY==1){ 497 return createScreenCapture(screenRect); 498 } 499 500 int x = screenRect.x; 501 int y = screenRect.y; 502 int width = screenRect.width; 503 int height = screenRect.height; 504 int pminx = (int) Math.floor(x * uiScaleX); 505 int pminy = (int) Math.floor(y * uiScaleY); 506 int pmaxx = (int) Math.ceil((x + width) * uiScaleX); 507 int pmaxy = (int) Math.ceil((y + height) * uiScaleY); 508 int pwidth = pmaxx - pminx; 509 int pheight = pmaxy - pminy; 510 int temppixels[]; 511 Rectangle rect = new Rectangle(pminx, pminy, pwidth, pheight); 512 temppixels = peer.getRGBPixels(rect); 513 514 // HighResolutionImage 515 pixels = temppixels; 516 screenRect = rect; 517 518 buffer = new DataBufferInt(pixels, pixels.length); 519 raster = Raster.createPackedRaster(buffer, screenRect.width, 520 screenRect.height, screenRect.width, bandmasks, null); 521 SunWritableRaster.makeTrackable(buffer); 522 523 highResolutionImage = new BufferedImage(screenCapCM, raster, false, null); 524 525 // LowResolutionImage 526 pixels = new int[width * height]; 527 int index = 0; 528 for (int iy = 0; iy < height; iy++) { 529 float rely = (float) (((y + iy + 0.5f) * uiScaleY) 530 - (pminy + 0.5f)); 531 int irely = (int) Math.floor(rely); 532 int fracty = (int) ((rely - irely) * 256); 533 for (int ix = 0; ix < width; ix++) { 534 float relx = (float) (((x + ix + 0.5f) * uiScaleX) 535 - (pminx + 0.5f)); 536 int irelx = (int) Math.floor(relx); 537 int fractx = (int) ((relx - irelx) * 256); 538 pixels[index++] 539 = interp(temppixels, irelx, irely, pwidth, 540 pheight, fractx, fracty); 541 } 542 } 543 screenRect.width = width; 544 screenRect.height = height; 545 546 buffer = new DataBufferInt(pixels, pixels.length); 547 raster = Raster.createPackedRaster(buffer, screenRect.width, 548 screenRect.height, screenRect.width, bandmasks, null); 549 SunWritableRaster.makeTrackable(buffer); 550 551 lowResolutionImage = new BufferedImage(screenCapCM, raster, false, null); 552 553 // MultiResoltuionImage 554 BaseMultiResolutionImage image = new BaseMultiResolutionImage( 555 lowResolutionImage,highResolutionImage); 556 557 return image; 558 } 559 560 /** 561 * Creates an image containing pixels read from the screen. This image does 562 * not include the mouse cursor. 563 * @param screenRect Rect to capture in screen coordinates 564 * @return The captured image 565 * @throws IllegalArgumentException if {@code screenRect} width and height are not greater than zero 566 * @throws SecurityException if {@code readDisplayPixels} permission is not granted 567 * @see SecurityManager#checkPermission 568 * @see AWTPermission 569 */ 570 public synchronized BufferedImage createScreenCapture(Rectangle screenRect) { 571 checkScreenCaptureAllowed(); 572 573 checkValidRect(screenRect); 574 575 BufferedImage image; 576 DataBufferInt buffer; 577 WritableRaster raster; 578 579 if (screenCapCM == null) { 580 /* 581 * Fix for 4285201 582 * Create a DirectColorModel equivalent to the default RGB ColorModel, 583 * except with no Alpha component. 584 */ 585 586 screenCapCM = new DirectColorModel(24, 587 /* red mask */ 0x00FF0000, 588 /* green mask */ 0x0000FF00, 589 /* blue mask */ 0x000000FF); 590 } 591 592 // need to sync the toolkit prior to grabbing the pixels since in some 593 // cases rendering to the screen may be delayed 594 Toolkit.getDefaultToolkit().sync(); 595 596 int pixels[]; 597 int[] bandmasks = new int[3]; 598 599 pixels = peer.getRGBPixels(screenRect); 600 buffer = new DataBufferInt(pixels, pixels.length); 601 602 bandmasks[0] = screenCapCM.getRedMask(); 603 bandmasks[1] = screenCapCM.getGreenMask(); 604 bandmasks[2] = screenCapCM.getBlueMask(); 605 606 raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null); 607 SunWritableRaster.makeTrackable(buffer); 608 609 image = new BufferedImage(screenCapCM, raster, false, null); 610 611 return image; 612 } 613 614 private static void checkValidRect(Rectangle rect) { 615 if (rect.width <= 0 || rect.height <= 0) { 616 throw new IllegalArgumentException("Rectangle width and height must be > 0"); 617 } 618 } 619 620 private static void checkScreenCaptureAllowed() { 621 SecurityManager security = System.getSecurityManager(); 622 if (security != null) { 623 security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION); 624 } 625 } 626 627 /* 628 * Called after an event is generated 629 */ 630 private void afterEvent() { 631 autoWaitForIdle(); 632 autoDelay(); 633 } 634 635 /** 636 * Returns whether this Robot automatically invokes {@code waitForIdle} 637 * after generating an event. 638 * @return Whether {@code waitForIdle} is automatically called 639 */ 640 public synchronized boolean isAutoWaitForIdle() { 641 return isAutoWaitForIdle; 642 } 643 644 /** 645 * Sets whether this Robot automatically invokes {@code waitForIdle} 646 * after generating an event. 647 * @param isOn Whether {@code waitForIdle} is automatically invoked 648 */ 649 public synchronized void setAutoWaitForIdle(boolean isOn) { 650 isAutoWaitForIdle = isOn; 651 } 652 653 /* 654 * Calls waitForIdle after every event if so desired. 655 */ 656 private void autoWaitForIdle() { 657 if (isAutoWaitForIdle) { 658 waitForIdle(); 659 } 660 } 661 662 /** 663 * Returns the number of milliseconds this Robot sleeps after generating an event. 664 * 665 * @return the delay duration in milliseconds 666 */ 667 public synchronized int getAutoDelay() { 668 return autoDelay; 669 } 670 671 /** 672 * Sets the number of milliseconds this Robot sleeps after generating an event. 673 * 674 * @param ms the delay duration in milliseconds 675 * @throws IllegalArgumentException If {@code ms} 676 * is not between 0 and 60,000 milliseconds inclusive 677 */ 678 public synchronized void setAutoDelay(int ms) { 679 checkDelayArgument(ms); 680 autoDelay = ms; 681 } 682 683 /* 684 * Automatically sleeps for the specified interval after event generated. 685 */ 686 private void autoDelay() { 687 delay(autoDelay); 688 } 689 690 /** 691 * Sleeps for the specified time. 692 * To catch any {@code InterruptedException}s that occur, 693 * {@code Thread.sleep()} may be used instead. 694 * 695 * @param ms time to sleep in milliseconds 696 * @throws IllegalArgumentException if {@code ms} 697 * is not between 0 and 60,000 milliseconds inclusive 698 * @see java.lang.Thread#sleep 699 */ 700 public synchronized void delay(int ms) { 701 checkDelayArgument(ms); 702 try { 703 Thread.sleep(ms); 704 } catch(InterruptedException ite) { 705 ite.printStackTrace(); 706 } 707 } 708 709 private void checkDelayArgument(int ms) { 710 if (ms < 0 || ms > MAX_DELAY) { 711 throw new IllegalArgumentException("Delay must be to 0 to 60,000ms"); 712 } 713 } 714 715 /** 716 * Waits until all events currently on the event queue have been processed. 717 * @throws IllegalThreadStateException if called on the AWT event dispatching thread 718 */ 719 public synchronized void waitForIdle() { 720 checkNotDispatchThread(); 721 SunToolkit.flushPendingEvents(); 722 ((SunToolkit) Toolkit.getDefaultToolkit()).realSync(); 723 } 724 725 private void checkNotDispatchThread() { 726 if (EventQueue.isDispatchThread()) { 727 throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread"); 728 } 729 } 730 731 /** 732 * Returns a string representation of this Robot. 733 * 734 * @return the string representation. 735 */ 736 @Override 737 public synchronized String toString() { 738 String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle(); 739 return getClass().getName() + "[ " + params + " ]"; 740 } 741 }