1/*
2 * Copyright (c) 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
26import java.awt.AWTException;
27import java.awt.Robot;
28import java.awt.GraphicsDevice;
29import java.awt.Toolkit;
30import java.awt.Point;
31import java.awt.MouseInfo;
32import java.awt.event.InputEvent;
33import java.awt.event.KeyEvent;
34
35/**
36 * ExtendedRobot is a subclass of {@link java.awt.Robot}. It provides some convenience methods that are
37 * ought to be moved to {@link java.awt.Robot} class.
38 * <p>
39 * ExtendedRobot uses delay {@link #getSyncDelay()} to make syncing threads with {@link #waitForIdle()}
40 * more stable. This delay can be set once on creating object and could not be changed throughout object
41 * lifecycle. Constructor reads vm integer property {@code java.awt.robotdelay} and sets the delay value
42 * equal to the property value. If the property was not set 500 milliseconds default value is used.
43 * <p>
44 * When using jtreg you would include this class via something like:
45 * <pre>
46 * {@literal @}library ../../../../lib/testlibrary
47 * {@literal @}build ExtendedRobot
48 * </pre>
49 *
50 * @author      Dmitriy Ermashov
51 * @since       9
52 */
53
54public class ExtendedRobot extends Robot {
55
56    private static int DEFAULT_SPEED = 20;       // Speed for mouse glide and click
57    private static int DEFAULT_SYNC_DELAY = 500; // Default Additional delay for waitForIdle()
58    private static int DEFAULT_STEP_LENGTH = 2;  // Step length (in pixels) for mouse glide
59
60    private final int syncDelay = DEFAULT_SYNC_DELAY;
61
62    //TODO: uncomment three lines below after moving functionality to java.awt.Robot
63    //{
64    //    syncDelay = AccessController.doPrivileged(new GetIntegerAction("java.awt.robotdelay", DEFAULT_SYNC_DELAY));
65    //}
66
67    /**
68     * Constructs an ExtendedRobot object in the coordinate system of the primary screen.
69     *
70     * @throws  AWTException if the platform configuration does not allow low-level input
71     *          control. This exception is always thrown when
72     *          GraphicsEnvironment.isHeadless() returns true
73     * @throws  SecurityException if {@code createRobot} permission is not granted
74     *
75     * @see     java.awt.GraphicsEnvironment#isHeadless
76     * @see     SecurityManager#checkPermission
77     * @see     java.awt.AWTPermission
78     */
79    public ExtendedRobot() throws AWTException {
80        super();
81    }
82
83    /**
84     * Creates an ExtendedRobot for the given screen device. Coordinates passed
85     * to ExtendedRobot method calls like mouseMove and createScreenCapture will
86     * be interpreted as being in the same coordinate system as the specified screen.
87     * Note that depending on the platform configuration, multiple screens may either:
88     * <ul>
89     * <li>share the same coordinate system to form a combined virtual screen</li>
90     * <li>use different coordinate systems to act as independent screens</li>
91     * </ul>
92     * This constructor is meant for the latter case.
93     * <p>
94     * If screen devices are reconfigured such that the coordinate system is
95     * affected, the behavior of existing ExtendedRobot objects is undefined.
96     *
97     * @param   screen  A screen GraphicsDevice indicating the coordinate
98     *                  system the Robot will operate in.
99     * @throws  AWTException if the platform configuration does not allow low-level input
100     *          control. This exception is always thrown when
101     *          GraphicsEnvironment.isHeadless() returns true.
102     * @throws  IllegalArgumentException if {@code screen} is not a screen
103     *          GraphicsDevice.
104     * @throws  SecurityException if {@code createRobot} permission is not granted
105     *
106     * @see     java.awt.GraphicsEnvironment#isHeadless
107     * @see     GraphicsDevice
108     * @see     SecurityManager#checkPermission
109     * @see     java.awt.AWTPermission
110     */
111    public ExtendedRobot(GraphicsDevice screen) throws AWTException {
112        super(screen);
113    }
114
115    /**
116     * Returns delay length for {@link #waitForIdle()} method
117     *
118     * @return  Current delay value
119     *
120     * @see     #waitForIdle()
121     */
122    public int getSyncDelay(){ return this.syncDelay; }
123
124    /**
125     * Clicks mouse button(s) by calling {@link java.awt.Robot#mousePress(int)} and
126     * {@link java.awt.Robot#mouseRelease(int)} methods
127     *
128     *
129     * @param   buttons The button mask; a combination of one or more mouse button masks.
130     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
131     *          extra mouse button and support for extended mouse buttons is
132     *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
133     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
134     *          extra mouse button that does not exist on the mouse and support for extended
135     *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
136     *          by Java
137     *
138     * @see     #mousePress(int)
139     * @see     #mouseRelease(int)
140     * @see     InputEvent#getMaskForButton(int)
141     * @see     Toolkit#areExtraMouseButtonsEnabled()
142     * @see     java.awt.event.MouseEvent
143     */
144    public void click(int buttons) {
145        mousePress(buttons);
146        waitForIdle(DEFAULT_SPEED);
147        mouseRelease(buttons);
148        waitForIdle();
149    }
150
151    /**
152     * Clicks mouse button 1
153     *
154     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
155     *          extra mouse button and support for extended mouse buttons is
156     *          {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
157     * @throws  IllegalArgumentException if the {@code buttons} mask contains the mask for
158     *          extra mouse button that does not exist on the mouse and support for extended
159     *          mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
160     *          by Java
161     *
162     * @see     #click(int)
163     */
164    public void click() {
165        click(InputEvent.BUTTON1_DOWN_MASK);
166    }
167
168    /**
169     * Waits until all events currently on the event queue have been processed with given
170     * delay after syncing threads. It uses more advanced method of synchronizing threads
171     * unlike {@link java.awt.Robot#waitForIdle()}
172     *
173     * @param   delayValue  Additional delay length in milliseconds to wait until thread
174     *                      sync been completed
175     * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
176     *          dispatching thread
177     */
178    public synchronized void waitForIdle(int delayValue) {
179        super.waitForIdle();
180        delay(delayValue);
181    }
182
183    /**
184     * Waits until all events currently on the event queue have been processed with delay
185     * {@link #getSyncDelay()} after syncing threads. It uses more advanced method of
186     * synchronizing threads unlike {@link java.awt.Robot#waitForIdle()}
187     *
188     * @throws  sun.awt.SunToolkit.IllegalThreadException if called on the AWT event
189     *          dispatching thread
190     *
191     * @see     #waitForIdle(int)
192     */
193    @Override
194    public synchronized void waitForIdle() {
195        waitForIdle(syncDelay);
196    }
197
198    /**
199     * Move the mouse in multiple steps from where it is
200     * now to the destination coordinates.
201     *
202     * @param   x   Destination point x coordinate
203     * @param   y   Destination point y coordinate
204     *
205     * @see     #glide(int, int, int, int)
206     */
207    public void glide(int x, int y) {
208        Point p = MouseInfo.getPointerInfo().getLocation();
209        glide(p.x, p.y, x, y);
210    }
211
212    /**
213     * Move the mouse in multiple steps from where it is
214     * now to the destination point.
215     *
216     * @param   dest    Destination point
217     *
218     * @see     #glide(int, int)
219     */
220    public void glide(Point dest) {
221        glide(dest.x, dest.y);
222    }
223
224    /**
225     * Move the mouse in multiple steps from source coordinates
226     * to the destination coordinates.
227     *
228     * @param   fromX   Source point x coordinate
229     * @param   fromY   Source point y coordinate
230     * @param   toX     Destination point x coordinate
231     * @param   toY     Destination point y coordinate
232     *
233     * @see     #glide(int, int, int, int, int, int)
234     */
235    public void glide(int fromX, int fromY, int toX, int toY) {
236        glide(fromX, fromY, toX, toY, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
237    }
238
239    /**
240     * Move the mouse in multiple steps from source point to the
241     * destination point with default speed and step length.
242     *
243     * @param   src     Source point
244     * @param   dest    Destination point
245     *
246     * @see     #glide(int, int, int, int, int, int)
247     */
248    public void glide(Point src, Point dest) {
249        glide(src.x, src.y, dest.x, dest.y, DEFAULT_STEP_LENGTH, DEFAULT_SPEED);
250    }
251
252    /**
253     * Move the mouse in multiple steps from source point to the
254     * destination point with given speed and step length.
255     *
256     * @param   srcX        Source point x cordinate
257     * @param   srcY        Source point y cordinate
258     * @param   destX       Destination point x cordinate
259     * @param   destY       Destination point y cordinate
260     * @param   stepLength  Approximate length of one step
261     * @param   speed       Delay between steps.
262     *
263     * @see     #mouseMove(int, int)
264     * @see     #delay(int)
265     */
266     public void glide(int srcX, int srcY, int destX, int destY, int stepLength, int speed) {
267        int stepNum;
268        double tDx, tDy;
269        double dx, dy, ds;
270        double x, y;
271
272        dx = (destX - srcX);
273        dy = (destY - srcY);
274        ds = Math.sqrt(dx*dx + dy*dy);
275
276        tDx = dx / ds * stepLength;
277        tDy = dy / ds * stepLength;
278
279        int stepsCount = (int) ds / stepLength;
280
281        // Walk the mouse to the destination one step at a time
282        mouseMove(srcX, srcY);
283
284        for (x = srcX, y = srcY, stepNum = 0;
285             stepNum < stepsCount;
286             stepNum++) {
287            x += tDx;
288            y += tDy;
289            mouseMove((int)x, (int)y);
290            delay(speed);
291        }
292
293        // Ensure the mouse moves to the right destination.
294        // The steps may have led the mouse to a slightly wrong place.
295        mouseMove(destX, destY);
296    }
297
298    /**
299     * Moves mouse pointer to given screen coordinates.
300     *
301     * @param   position    Target position
302     *
303     * @see     java.awt.Robot#mouseMove(int, int)
304     */
305    public synchronized void mouseMove(Point position) {
306        mouseMove(position.x, position.y);
307    }
308
309
310    /**
311     * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}.
312     * The method successively moves mouse cursor to point with coordinates
313     * ({@code fromX}, {@code fromY}), presses mouse button 1, drag mouse to
314     * point with coordinates ({@code toX}, {@code toY}) and releases mouse
315     * button 1 at last.
316     *
317     * @param   fromX   Source point x coordinate
318     * @param   fromY   Source point y coordinate
319     * @param   toX     Destination point x coordinate
320     * @param   toY     Destination point y coordinate
321     *
322     * @see     #mousePress(int)
323     * @see     #glide(int, int, int, int)
324     */
325    public void dragAndDrop(int fromX, int fromY, int toX, int toY){
326        mouseMove(fromX, fromY);
327        mousePress(InputEvent.BUTTON1_DOWN_MASK);
328        waitForIdle();
329        glide(toX, toY);
330        mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
331        waitForIdle();
332    }
333
334    /**
335     * Emulate native drag and drop process using {@code InputEvent.BUTTON1_DOWN_MASK}.
336     * The method successively moves mouse cursor to point {@code from},
337     * presses mouse button 1, drag mouse to point {@code to} and releases
338     * mouse button 1 at last.
339     *
340     * @param   from    Source point
341     * @param   to      Destination point
342     *
343     * @see     #mousePress(int)
344     * @see     #glide(int, int, int, int)
345     * @see     #dragAndDrop(int, int, int, int)
346     */
347    public void dragAndDrop(Point from, Point to){
348        dragAndDrop(from.x, from.y, to.x, to.y);
349    }
350
351    /**
352     * Successively presses and releases a given key.
353     * <p>
354     * Key codes that have more than one physical key associated with them
355     * (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
356     * left or right shift key) will map to the left key.
357     *
358     * @param   keycode Key to press (e.g. {@code KeyEvent.VK_A})
359     * @throws  IllegalArgumentException if {@code keycode} is not
360     *          a valid key
361     *
362     * @see     java.awt.Robot#keyPress(int)
363     * @see     java.awt.Robot#keyRelease(int)
364     * @see     java.awt.event.KeyEvent
365     */
366    public void type(int keycode) {
367        keyPress(keycode);
368        waitForIdle(DEFAULT_SPEED);
369        keyRelease(keycode);
370        waitForIdle(DEFAULT_SPEED);
371    }
372
373    /**
374     * Types given character
375     *
376     * @param   c   Character to be typed (e.g. {@code 'a'})
377     *
378     * @see     #type(int)
379     * @see     java.awt.event.KeyEvent
380     */
381    public void type(char c) {
382        type(KeyEvent.getExtendedKeyCodeForChar(c));
383    }
384
385    /**
386     * Types given array of characters one by one
387     *
388     * @param   symbols Array of characters to be typed
389     *
390     * @see     #type(char)
391     */
392    public void type(char[] symbols) {
393        for (int i = 0; i < symbols.length; i++) {
394            type(symbols[i]);
395        }
396    }
397
398    /**
399     * Types given string
400     *
401     * @param   s   String to be typed
402     *
403     * @see     #type(char[])
404     */
405    public void type(String s) {
406        type(s.toCharArray());
407    }
408}
409