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 
26 import java.awt.AWTException;
27 import java.awt.Robot;
28 import java.awt.GraphicsDevice;
29 import java.awt.Toolkit;
30 import java.awt.Point;
31 import java.awt.MouseInfo;
32 import java.awt.event.InputEvent;
33 import 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 
54 public 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      */
ExtendedRobot()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      */
ExtendedRobot(GraphicsDevice screen)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      */
getSyncDelay()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      */
click(int buttons)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      */
click()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      */
waitForIdle(int delayValue)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
waitForIdle()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      */
glide(int x, int y)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      */
glide(Point dest)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      */
glide(int fromX, int fromY, int toX, int toY)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      */
glide(Point src, Point dest)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      */
glide(int srcX, int srcY, int destX, int destY, int stepLength, int speed)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      */
mouseMove(Point position)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      */
dragAndDrop(int fromX, int fromY, int toX, int toY)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      */
dragAndDrop(Point from, Point to)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      */
type(int keycode)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      */
type(char c)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      */
type(char[] symbols)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      */
type(String s)405     public void type(String s) {
406         type(s.toCharArray());
407     }
408 }
409