ExtendedRobot.java 13.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
/*
 * Copyright (c) 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.
 */

import sun.awt.ExtendedKeyCodes;
import sun.awt.SunToolkit;
import sun.security.action.GetIntegerAction;

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.GraphicsDevice;
import java.awt.Toolkit;
import java.awt.Point;
import java.awt.MouseInfo;
import java.awt.event.InputEvent;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * ExtendedRobot is a subclass of {@link java.awt.Robot}. It provides some convenience methods that are
 * ought to be moved to {@link java.awt.Robot} class.
 * <p>
 * ExtendedRobot 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.
 * <p>
 * When using jtreg you would include this class via something like:
 * <pre>
 * {@literal @}library ../../../../lib/testlibrary
 * {@literal @}build ExtendedRobot
 * </pre>
 *
 * @author      Dmitriy Ermashov
 * @since       1.9
 */

public class ExtendedRobot extends Robot {

    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 = DEFAULT_SYNC_DELAY;

    //TODO: uncomment three lines below after moving functionality to java.awt.Robot
    //{
    //    syncDelay = AccessController.doPrivileged(new GetIntegerAction("java.awt.robotdelay", DEFAULT_SYNC_DELAY));
    //}

    /**
     * Constructs an ExtendedRobot 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 {@code createRobot} permission is not granted
     *
     * @see     java.awt.GraphicsEnvironment#isHeadless
     * @see     SecurityManager#checkPermission
     * @see     java.awt.AWTPermission
     */
    public ExtendedRobot() throws AWTException {
        super();
    }

    /**
     * Creates an ExtendedRobot for the given screen device. Coordinates passed
     * to ExtendedRobot 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:
     * <ul>
     * <li>share the same coordinate system to form a combined virtual screen</li>
     * <li>use different coordinate systems to act as independent screens</li>
     * </ul>
     * This constructor is meant for the latter case.
     * <p>
     * If screen devices are reconfigured such that the coordinate system is
     * affected, the behavior of existing ExtendedRobot 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 {@code screen} is not a screen
     *          GraphicsDevice.
     * @throws  SecurityException if {@code createRobot} permission is not granted
     *
     * @see     java.awt.GraphicsEnvironment#isHeadless
     * @see     GraphicsDevice
     * @see     SecurityManager#checkPermission
     * @see     java.awt.AWTPermission
     */
    public ExtendedRobot(GraphicsDevice screen) throws AWTException {
        super(screen);
    }

    /**
     * Returns delay length for {@link #waitForIdle()} method
     *
     * @return  Current delay value
     *
     * @see     #waitForIdle()
     */
    public int getSyncDelay(){ return this.syncDelay; }

    /**
     * Clicks mouse button(s) by calling {@link java.awt.Robot#mousePress(int)} and
     * {@link java.awt.Robot#mouseRelease(int)} methods
     *
     *
     * @param   buttons The button mask; a combination of one or more mouse button masks.
     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
     *          extra mouse button and support for extended mouse buttons is
     *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
     *          extra mouse button that does not exist on the mouse and support for extended
     *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
     *          by Java
     *
     * @see     #mousePress(int)
     * @see     #mouseRelease(int)
     * @see     InputEvent#getMaskForButton(int)
     * @see     Toolkit#areExtraMouseButtonsEnabled()
     * @see     java.awt.event.MouseEvent
     */
    public void click(int buttons) {
        mousePress(buttons);
        waitForIdle(DEFAULT_SPEED);
        mouseRelease(buttons);
        waitForIdle();
    }

    /**
     * Clicks mouse button 1
     *
     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
     *          extra mouse button and support for extended mouse buttons is
     *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
     *          extra mouse button that does not exist on the mouse and support for extended
     *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
     *          by Java
     *
     * @see     #click(int)
     */
    public void click() {
        click(InputEvent.BUTTON1_DOWN_MASK);
    }

    /**
     * Waits until all events currently on the event queue have been processed with given
     * delay after syncing threads. It uses more advanced method of synchronizing threads
     * unlike {@link java.awt.Robot#waitForIdle()}
     *
     * @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);
    }

    /**
     * Waits until all events currently on the event queue have been processed with delay
     * {@link #getSyncDelay()} after syncing threads. It uses more advanced method of
     * synchronizing threads unlike {@link java.awt.Robot#waitForIdle()}
     *
     * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
     *          dispatching thread
     *
     * @see     #waitForIdle(int)
     */
    @Override
    public synchronized void waitForIdle() {
        waitForIdle(syncDelay);
    }

    /**
     * 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);
    }

    /**
     * Moves mouse pointer to given screen coordinates.
     *
     * @param   position    Target position
     *
     * @see     java.awt.Robot#mouseMove(int, int)
     */
    public synchronized void mouseMove(Point position) {
        mouseMove(position.x, position.y);
    }

    /**
     * Successively presses and releases a given key.
     * <p>
     * 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     java.awt.Robot#keyPress(int)
     * @see     java.awt.Robot#keyRelease(int)
     * @see     java.awt.event.KeyEvent
     */
    public void type(int keycode) {
        keyPress(keycode);
        waitForIdle(DEFAULT_SPEED);
        keyRelease(keycode);
        waitForIdle(DEFAULT_SPEED);
    }

    /**
     * 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());
    }
}