/* * Copyright (c) 1999, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.awt; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.awt.image.DirectColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.awt.peer.RobotPeer; import java.security.AccessController; import sun.awt.AWTPermissions; import sun.awt.ComponentFactory; import sun.awt.ExtendedKeyCodes; import sun.awt.SunToolkit; import sun.awt.image.SunWritableRaster; import sun.security.action.GetIntegerAction; /** * This class is used to generate native system input events * for the purposes of test automation, self-running demos, and * other applications where control of the mouse and keyboard * is needed. The primary purpose of Robot is to facilitate * automated testing of Java platform implementations. *
* Using the class to generate input events differs from posting
* events to the AWT event queue or AWT components in that the
* events are generated in the platform's native input
* queue. For example, Robot.mouseMove
will actually move
* the mouse cursor instead of just generating mouse move events.
*
* Robot uses delay {@link #getSyncDelay()} to make syncing threads * with {@link #waitForIdle()} more stable. This delay can be set once * on creating object and could not be changed throughout object lifecycle. * Constructor reads vm integer property {@code java.awt.robotdelay} and * sets the delay value equal to the property value. If the property was * not set 500 milliseconds default value is used. *
* Note that some platforms require special privileges or extensions
* to access low-level input control. If the current platform configuration
* does not allow input control, an AWTException
will be thrown
* when trying to construct Robot objects. For example, X-Window systems
* will throw the exception if the XTEST 2.2 standard extension is not supported
* (or not enabled) by the X server.
*
* Applications that use Robot for purposes other than self-testing should
* handle these error conditions gracefully.
*
* @author Robi Khan
* @since 1.3
*/
public class Robot {
private static final int MAX_DELAY = 60000;
private RobotPeer peer;
private boolean isAutoWaitForIdle = false;
private int autoDelay = 0;
private static int LEGAL_BUTTON_MASK = 0;
private static int DEFAULT_SPEED = 20; // Speed for mouse glide and click
private static int DEFAULT_SYNC_DELAY = 500; // Default Additional delay for waitForIdle()
private static int DEFAULT_STEP_LENGTH = 2; // Step length (in pixels) for mouse glide
private final int syncDelay;
{
syncDelay = AccessController.doPrivileged(
new GetIntegerAction("java.awt.robotdelay", DEFAULT_SYNC_DELAY));
}
private DirectColorModel screenCapCM = null;
/**
* Constructs a Robot object in the coordinate system of the primary screen.
*
* @throws AWTException if the platform configuration does not allow
* low-level input control. This exception is always thrown when
* GraphicsEnvironment.isHeadless() returns true
* @throws SecurityException if createRobot
permission is not granted
* @see java.awt.GraphicsEnvironment#isHeadless
* @see SecurityManager#checkPermission
* @see AWTPermission
*/
public Robot() throws AWTException {
if (GraphicsEnvironment.isHeadless()) {
throw new AWTException("headless environment");
}
init(GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice());
}
/**
* Creates a Robot for the given screen device. Coordinates passed
* to Robot method calls like mouseMove and createScreenCapture will
* be interpreted as being in the same coordinate system as the
* specified screen. Note that depending on the platform configuration,
* multiple screens may either:
*
* If screen devices are reconfigured such that the coordinate system is
* affected, the behavior of existing Robot objects is undefined.
*
* @param screen A screen GraphicsDevice indicating the coordinate
* system the Robot will operate in.
* @throws AWTException if the platform configuration does not allow
* low-level input control. This exception is always thrown when
* GraphicsEnvironment.isHeadless() returns true.
* @throws IllegalArgumentException if screen
is not a screen
* GraphicsDevice.
* @throws SecurityException if createRobot
permission is not granted
* @see java.awt.GraphicsEnvironment#isHeadless
* @see GraphicsDevice
* @see SecurityManager#checkPermission
* @see AWTPermission
*/
public Robot(GraphicsDevice screen) throws AWTException {
checkIsScreenDevice(screen);
init(screen);
}
private void init(GraphicsDevice screen) throws AWTException {
checkRobotAllowed();
Toolkit toolkit = Toolkit.getDefaultToolkit();
if (toolkit instanceof ComponentFactory) {
peer = ((ComponentFactory)toolkit).createRobot(this, screen);
disposer = new RobotDisposer(peer);
sun.java2d.Disposer.addRecord(anchor, disposer);
}
initLegalButtonMask();
}
private static synchronized void initLegalButtonMask() {
if (LEGAL_BUTTON_MASK != 0) return;
int tmpMask = 0;
if (Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){
if (Toolkit.getDefaultToolkit() instanceof SunToolkit) {
final int buttonsNumber = ((SunToolkit)(Toolkit.getDefaultToolkit())).getNumberOfButtons();
for (int i = 0; i < buttonsNumber; i++){
tmpMask |= InputEvent.getMaskForButton(i+1);
}
}
}
tmpMask |= InputEvent.BUTTON1_MASK|
InputEvent.BUTTON2_MASK|
InputEvent.BUTTON3_MASK|
InputEvent.BUTTON1_DOWN_MASK|
InputEvent.BUTTON2_DOWN_MASK|
InputEvent.BUTTON3_DOWN_MASK;
LEGAL_BUTTON_MASK = tmpMask;
}
/* determine if the security policy allows Robot's to be created */
private void checkRobotAllowed() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(AWTPermissions.CREATE_ROBOT_PERMISSION);
}
}
/* check if the given device is a screen device */
private void checkIsScreenDevice(GraphicsDevice device) {
if (device == null || device.getType() != GraphicsDevice.TYPE_RASTER_SCREEN) {
throw new IllegalArgumentException("not a valid screen device");
}
}
private transient Object anchor = new Object();
static class RobotDisposer implements sun.java2d.DisposerRecord {
private final RobotPeer peer;
public RobotDisposer(RobotPeer peer) {
this.peer = peer;
}
public void dispose() {
if (peer != null) {
peer.dispose();
}
}
}
private transient RobotDisposer disposer;
/**
* Moves mouse pointer to given screen coordinates.
* @param x X position
* @param y Y position
*/
public synchronized void mouseMove(int x, int y) {
peer.mouseMove(x, y);
afterEvent();
}
/**
* Moves mouse pointer to given screen coordinates.
*
* @param position Target position
*
* @see #mouseMove(int, int)
*/
public synchronized void mouseMove(Point position) {
mouseMove(position.x, position.y);
}
/**
* Move mouse cursor to the center of the Component.
*
* @param c Component the mouse is placed over
*
* @see #mouseMove(int, int)
*/
public synchronized void mouseMove(Component c) {
Point p = c.getLocationOnScreen();
Dimension size = c.getSize();
p.x += size.width / 2;
p.y += size.height / 2;
mouseMove(p.x, p.y);
}
/**
* Move the mouse in multiple steps from where it is
* now to the destination coordinates.
*
* @param x Destination point x coordinate
* @param y Destination point y coordinate
*
* @see #glide(int, int, int, int)
*/
public void glide(int x, int y) {
Point p = MouseInfo.getPointerInfo().getLocation();
glide(p.x, p.y, x, y);
}
/**
* Move the mouse in multiple steps from where it is
* now to the destination point.
*
* @param dest Destination point
*
* @see #glide(int, int)
*/
public void glide(Point dest) {
glide(dest.x, dest.y);
}
/**
* Move the mouse in multiple steps from source coordinates
* to the destination coordinates.
*
* @param fromX Source point x coordinate
* @param fromY Source point y coordinate
* @param toX Destination point x coordinate
* @param toY Destination point y coordinate
*
* @see #glide(int, int, int, int, int, int)
*/
public void glide(int fromX, int fromY, int toX, int toY) {
glide(fromX, fromY, toX, toY, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
}
/**
* Move the mouse in multiple steps from source point to the
* destination point with default speed and step length.
*
* @param src Source point
* @param dest Destination point
*
* @see #glide(int, int, int, int, int, int)
*/
public void glide(Point src, Point dest) {
glide(src.x, src.y, dest.x, dest.y, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
}
/**
* Move the mouse in multiple steps from source point to the
* destination point with given speed and step length.
*
* @param srcX Source point x cordinate
* @param srcY Source point y cordinate
* @param destX Destination point x cordinate
* @param destY Destination point y cordinate
* @param stepLength Approximate length of one step
* @param speed Delay between steps.
*
* @see #mouseMove(int, int)
* @see #delay(int)
*/
public void glide(int srcX, int srcY, int destX, int destY, int stepLength, int speed) {
int stepNum;
double tDx, tDy;
double dx, dy, ds;
double x, y;
dx = (destX - srcX);
dy = (destY - srcY);
ds = Math.sqrt(dx*dx + dy*dy);
tDx = dx / ds * stepLength;
tDy = dy / ds * stepLength;
int stepsCount = (int) ds / stepLength;
// Walk the mouse to the destination one step at a time
mouseMove(srcX, srcY);
for (x = srcX, y = srcY, stepNum = 0;
stepNum < stepsCount;
stepNum++) {
x += tDx;
y += tDy;
mouseMove((int)x, (int)y);
delay(speed);
}
// Ensure the mouse moves to the right destination.
// The steps may have led the mouse to a slightly wrong place.
mouseMove(destX, destY);
}
/**
* Presses one or more mouse buttons. The mouse buttons should
* be released using the {@link #mouseRelease(int)} method.
*
* @param buttons the Button mask; a combination of one or more
* mouse button masks.
*
* It is allowed to use only a combination of valid values as a {@code buttons} parameter. * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} * and values returned by the * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. * * The valid combination also depends on a * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: *
* The following standard button masks are also accepted: *
* It is allowed to use only a combination of valid values as a {@code buttons} parameter. * A valid combination consists of {@code InputEvent.BUTTON1_DOWN_MASK}, * {@code InputEvent.BUTTON2_DOWN_MASK}, {@code InputEvent.BUTTON3_DOWN_MASK} * and values returned by the * {@link InputEvent#getMaskForButton(int) InputEvent.getMaskForButton(button)} method. * * The valid combination also depends on a * {@link Toolkit#areExtraMouseButtonsEnabled() Toolkit.areExtraMouseButtonsEnabled()} value as follows: *
* The following standard button masks are also accepted: *
keyRelease
method.
*
* Key codes that have more than one physical key associated with them
* (e.g. KeyEvent.VK_SHIFT
could mean either the
* left or right shift key) will map to the left key.
*
* @param keycode Key to press (e.g. KeyEvent.VK_A
)
* @throws IllegalArgumentException if keycode
is not
* a valid key
* @see #keyRelease(int)
* @see java.awt.event.KeyEvent
*/
public synchronized void keyPress(int keycode) {
checkKeycodeArgument(keycode);
peer.keyPress(keycode);
afterEvent();
}
/**
* Releases a given key.
*
* Key codes that have more than one physical key associated with them
* (e.g. KeyEvent.VK_SHIFT
could mean either the
* left or right shift key) will map to the left key.
*
* @param keycode Key to release (e.g. KeyEvent.VK_A
)
* @throws IllegalArgumentException if keycode
is not a
* valid key
* @see #keyPress(int)
* @see java.awt.event.KeyEvent
*/
public synchronized void keyRelease(int keycode) {
checkKeycodeArgument(keycode);
peer.keyRelease(keycode);
afterEvent();
}
/**
* Successively presses and releases a given key.
*
* Key codes that have more than one physical key associated with them
* (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
* left or right shift key) will map to the left key.
*
* @param keycode Key to press (e.g. {@code KeyEvent.VK_A})
* @throws IllegalArgumentException if {@code keycode} is not
* a valid key
*
* @see #keyPress(int)
* @see #keyRelease(int)
* @see java.awt.event.KeyEvent
*/
public void type(int keycode) {
keyPress(keycode);
waitForIdle(DEFAULT_SPEED);
keyRelease(keycode);
waitForIdle();
}
/**
* Types given character
*
* @param c Character to be typed (e.g. {@code 'a'})
*
* @see #type(int)
* @see java.awt.event.KeyEvent
*/
public void type(char c) {
type(ExtendedKeyCodes.getExtendedKeyCodeForChar(c));
}
/**
* Types given array of characters one by one
*
* @param symbols Array of characters to be typed
*
* @see #type(char)
*/
public void type(char[] symbols) {
for (int i = 0; i < symbols.length; i++) {
type(symbols[i]);
}
}
/**
* Types given string
*
* @param s String to be typed
*
* @see #type(char[])
*/
public void type(String s) {
type(s.toCharArray());
}
private void checkKeycodeArgument(int keycode) {
// rather than build a big table or switch statement here, we'll
// just check that the key isn't VK_UNDEFINED and assume that the
// peer implementations will throw an exception for other bogus
// values e.g. -1, 999999
if (keycode == KeyEvent.VK_UNDEFINED) {
throw new IllegalArgumentException("Invalid key code");
}
}
/**
* Returns the color of a pixel at the given screen coordinates.
* @param x X position of pixel
* @param y Y position of pixel
* @return Color of the pixel
*/
public synchronized Color getPixelColor(int x, int y) {
Color color = new Color(peer.getRGBPixel(x, y));
return color;
}
/**
* Creates an image containing pixels read from the screen. This image does
* not include the mouse cursor.
* @param screenRect Rect to capture in screen coordinates
* @return The captured image
* @throws IllegalArgumentException if screenRect
width and height are not greater than zero
* @throws SecurityException if readDisplayPixels
permission is not granted
* @see SecurityManager#checkPermission
* @see AWTPermission
*/
public synchronized BufferedImage createScreenCapture(Rectangle screenRect) {
checkScreenCaptureAllowed();
checkValidRect(screenRect);
BufferedImage image;
DataBufferInt buffer;
WritableRaster raster;
if (screenCapCM == null) {
/*
* Fix for 4285201
* Create a DirectColorModel equivalent to the default RGB ColorModel,
* except with no Alpha component.
*/
screenCapCM = new DirectColorModel(24,
/* red mask */ 0x00FF0000,
/* green mask */ 0x0000FF00,
/* blue mask */ 0x000000FF);
}
// need to sync the toolkit prior to grabbing the pixels since in some
// cases rendering to the screen may be delayed
Toolkit.getDefaultToolkit().sync();
int pixels[];
int[] bandmasks = new int[3];
pixels = peer.getRGBPixels(screenRect);
buffer = new DataBufferInt(pixels, pixels.length);
bandmasks[0] = screenCapCM.getRedMask();
bandmasks[1] = screenCapCM.getGreenMask();
bandmasks[2] = screenCapCM.getBlueMask();
raster = Raster.createPackedRaster(buffer, screenRect.width, screenRect.height, screenRect.width, bandmasks, null);
SunWritableRaster.makeTrackable(buffer);
image = new BufferedImage(screenCapCM, raster, false, null);
return image;
}
private static void checkValidRect(Rectangle rect) {
if (rect.width <= 0 || rect.height <= 0) {
throw new IllegalArgumentException("Rectangle width and height must be > 0");
}
}
private static void checkScreenCaptureAllowed() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(AWTPermissions.READ_DISPLAY_PIXELS_PERMISSION);
}
}
/*
* Called after an event is generated
*/
private void afterEvent() {
autoWaitForIdle();
autoDelay();
}
/**
* Returns whether this Robot automatically invokes waitForIdle
* after generating an event.
* @return Whether waitForIdle
is automatically called
*/
public synchronized boolean isAutoWaitForIdle() {
return isAutoWaitForIdle;
}
/**
* Sets whether this Robot automatically invokes waitForIdle
* after generating an event.
* @param isOn Whether waitForIdle
is automatically invoked
*/
public synchronized void setAutoWaitForIdle(boolean isOn) {
isAutoWaitForIdle = isOn;
}
/*
* Calls waitForIdle after every event if so desired.
*/
private void autoWaitForIdle() {
if (isAutoWaitForIdle) {
waitForIdle();
}
}
/**
* Returns delay length for {@link #waitForIdle()} method
*
* @return Current delay value
*
* @see #waitForIdle()
*/
public int getSyncDelay() {
return this.syncDelay;
}
/**
* Returns the number of milliseconds this Robot sleeps after generating an event.
*
* @return the delay duration in milliseconds
*/
public synchronized int getAutoDelay() {
return autoDelay;
}
/**
* Sets the number of milliseconds this Robot sleeps after generating an event.
*
* @param ms the delay duration in milliseconds
* @throws IllegalArgumentException If {@code ms}
* is not between 0 and 60,000 milliseconds inclusive
*/
public synchronized void setAutoDelay(int ms) {
checkDelayArgument(ms);
autoDelay = ms;
}
/*
* Automatically sleeps for the specified interval after event generated.
*/
private void autoDelay() {
delay(autoDelay);
}
/**
* Sleeps for the specified time.
* To catch any InterruptedException
s that occur,
* Thread.sleep()
may be used instead.
*
* @param ms time to sleep in milliseconds
* @throws IllegalArgumentException if {@code ms}
* is not between 0 and 60,000 milliseconds inclusive
* @see java.lang.Thread#sleep
*/
public synchronized void delay(int ms) {
checkDelayArgument(ms);
try {
Thread.sleep(ms);
} catch(InterruptedException ite) {
ite.printStackTrace();
}
}
private void checkDelayArgument(int ms) {
if (ms < 0 || ms > MAX_DELAY) {
throw new IllegalArgumentException("Delay must be to 0 to 60,000ms");
}
}
/**
* Waits until all events currently on the event queue have been processed with given
* delay after syncing threads.
*
* @param delayValue Additional delay length in milliseconds to wait until thread
* sync been completed
* @throws sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
* dispatching thread
*/
public synchronized void waitForIdle(int delayValue) {
SunToolkit.flushPendingEvents();
((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
delay(delayValue);
}
/**
* Static method for synchronizing event queue with current thread. Waits until
* all events currently on the event queue have been processed with a small delay
* of 500 ms after syncing threads.
*
* @throws sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
* dispatching thread
*/
public static synchronized void syncEventQueue() {
SunToolkit.flushPendingEvents();
((SunToolkit) Toolkit.getDefaultToolkit()).realSync();
try {
Thread.sleep(DEFAULT_SYNC_DELAY);
} catch (InterruptedException ite) {
ite.printStackTrace();
}
}
/**
* Waits until all events currently on the event queue have been processed with delay
* {@link #getSyncDelay()} after syncing threads.
*
* @throws sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
* dispatching thread
*
* @see #waitForIdle(int)
*/
public synchronized void waitForIdle() {
waitForIdle(syncDelay);
}
private void checkNotDispatchThread() {
if (EventQueue.isDispatchThread()) {
throw new IllegalThreadStateException("Cannot call method from the event dispatcher thread");
}
}
/**
* Returns a string representation of this Robot.
*
* @return the string representation.
*/
public synchronized String toString() {
String params = "autoDelay = "+getAutoDelay()+", "+"autoWaitForIdle = "+isAutoWaitForIdle();
return getClass().getName() + "[ " + params + " ]";
}
}