1 /*
2  * Copyright (c) 1997, 2016, 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 package org.netbeans.jemmy.drivers.input;
26 
27 import java.awt.event.InputEvent;
28 import java.awt.event.KeyEvent;
29 import java.lang.reflect.InvocationTargetException;
30 
31 import org.netbeans.jemmy.ClassReference;
32 import org.netbeans.jemmy.JemmyException;
33 import org.netbeans.jemmy.JemmyProperties;
34 import org.netbeans.jemmy.QueueTool;
35 import org.netbeans.jemmy.TestOut;
36 import org.netbeans.jemmy.Timeout;
37 import org.netbeans.jemmy.drivers.LightSupportiveDriver;
38 
39 /**
40  * Superclass for all drivers using robot.
41  *
42  * @author Alexandre Iline(alexandre.iline@oracle.com)
43  */
44 public class RobotDriver extends LightSupportiveDriver {
45 
46     private boolean haveOldPos;
47     private boolean smooth = false;
48     private double oldX;
49     private double oldY;
50     private static final double CONSTANT1 = 0.75;
51     private static final double CONSTANT2 = 12.0;
52     /**
53      * A reference to the robot instance.
54      */
55     protected ClassReference robotReference = null;
56 
57     /**
58      * A QueueTool instance.
59      */
60     protected QueueTool qtool;
61 
62     protected Timeout autoDelay;
63 
64     /**
65      * Constructs a RobotDriver object.
66      *
67      * @param autoDelay Time for {@code Robot.setAutoDelay(long)} method.
68      * @param supported an array of supported class names
69      */
RobotDriver(Timeout autoDelay, String[] supported)70     public RobotDriver(Timeout autoDelay, String[] supported) {
71         super(supported);
72         qtool = new QueueTool();
73         qtool.setOutput(TestOut.getNullOutput());
74         this.autoDelay = autoDelay;
75     }
76 
RobotDriver(Timeout autoDelay, String[] supported, boolean smooth)77     public RobotDriver(Timeout autoDelay, String[] supported, boolean smooth) {
78         this(autoDelay, supported);
79         this.smooth = smooth;
80     }
81 
82     /**
83      * Constructs a RobotDriver object.
84      *
85      * @param autoDelay Time for {@code Robot.setAutoDelay(long)} method.
86      */
RobotDriver(Timeout autoDelay)87     public RobotDriver(Timeout autoDelay) {
88         this(autoDelay, new String[]{"org.netbeans.jemmy.operators.ComponentOperator"});
89     }
90 
RobotDriver(Timeout autoDelay, boolean smooth)91     public RobotDriver(Timeout autoDelay, boolean smooth) {
92         this(autoDelay);
93         this.smooth = smooth;
94     }
95 
pressMouse(int mouseButton, int modifiers)96     public void pressMouse(int mouseButton, int modifiers) {
97         pressModifiers(modifiers);
98         makeAnOperation("mousePress",
99                 new Object[]{mouseButton},
100                 new Class<?>[]{Integer.TYPE});
101     }
102 
releaseMouse(int mouseButton, int modifiers)103     public void releaseMouse(int mouseButton, int modifiers) {
104         makeAnOperation("mouseRelease",
105                 new Object[]{mouseButton},
106                 new Class<?>[]{Integer.TYPE});
107         releaseModifiers(modifiers);
108     }
109 
moveMouse(int x, int y)110     public void moveMouse(int x, int y) {
111         if (!smooth) {
112             makeAnOperation("mouseMove",
113                     new Object[]{x, y},
114                     new Class<?>[]{Integer.TYPE, Integer.TYPE});
115         } else {
116             double targetX = x;
117             double targetY = y;
118             if (haveOldPos) {
119                 double currX = oldX;
120                 double currY = oldY;
121                 double vx = 0.0;
122                 double vy = 0.0;
123                 while (Math.round(currX) != Math.round(targetX)
124                         || Math.round(currY) != Math.round(targetY)) {
125                     vx = vx * CONSTANT1 + (targetX - currX) / CONSTANT2 * (1.0 - CONSTANT1);
126                     vy = vy * CONSTANT1 + (targetY - currY) / CONSTANT2 * (1.0 - CONSTANT1);
127                     currX += vx;
128                     currY += vy;
129                     makeAnOperation("mouseMove", new Object[]{
130                                     (int) Math.round(currX),
131                                     (int) Math.round(currY)},
132                             new Class<?>[]{Integer.TYPE, Integer.TYPE});
133                 }
134             } else {
135                 makeAnOperation("mouseMove", new Object[]{
136                                 (int) Math.round(targetX),
137                                 (int) Math.round(targetY)},
138                         new Class<?>[]{Integer.TYPE, Integer.TYPE});
139             }
140             haveOldPos = true;
141             oldX = targetX;
142             oldY = targetY;
143         }
144     }
145 
clickMouse(int x, int y, int clickCount, int mouseButton, int modifiers, Timeout mouseClick)146     public void clickMouse(int x, int y, int clickCount, int mouseButton,
147             int modifiers, Timeout mouseClick) {
148         pressModifiers(modifiers);
149         moveMouse(x, y);
150         makeAnOperation("mousePress", new Object[]{mouseButton}, new Class<?>[]{Integer.TYPE});
151         for (int i = 1; i < clickCount; i++) {
152             makeAnOperation("mouseRelease", new Object[]{mouseButton}, new Class<?>[]{Integer.TYPE});
153             makeAnOperation("mousePress", new Object[]{mouseButton}, new Class<?>[]{Integer.TYPE});
154         }
155         mouseClick.sleep();
156         makeAnOperation("mouseRelease", new Object[]{mouseButton}, new Class<?>[]{Integer.TYPE});
157         releaseModifiers(modifiers);
158     }
159 
dragMouse(int x, int y, int mouseButton, int modifiers)160     public void dragMouse(int x, int y, int mouseButton, int modifiers) {
161         moveMouse(x, y);
162     }
163 
dragNDrop(int start_x, int start_y, int end_x, int end_y, int mouseButton, int modifiers, Timeout before, Timeout after)164     public void dragNDrop(int start_x, int start_y, int end_x, int end_y,
165             int mouseButton, int modifiers, Timeout before, Timeout after) {
166         moveMouse(start_x, start_y);
167         pressMouse(mouseButton, modifiers);
168         before.sleep();
169         moveMouse(end_x, end_y);
170         after.sleep();
171         releaseMouse(mouseButton, modifiers);
172     }
173 
174     /**
175      * Presses a key.
176      *
177      * @param keyCode Key code ({@code KeyEventVK_*} field.
178      * @param modifiers a combination of {@code InputEvent.*_MASK} fields.
179      */
pressKey(int keyCode, int modifiers)180     public void pressKey(int keyCode, int modifiers) {
181         pressModifiers(modifiers);
182         makeAnOperation("keyPress",
183                 new Object[]{keyCode},
184                 new Class<?>[]{Integer.TYPE});
185     }
186 
187     /**
188      * Releases a key.
189      *
190      * @param keyCode Key code ({@code KeyEventVK_*} field.
191      * @param modifiers a combination of {@code InputEvent.*_MASK} fields.
192      */
releaseKey(int keyCode, int modifiers)193     public void releaseKey(int keyCode, int modifiers) {
194         releaseModifiers(modifiers);
195         makeAnOperation("keyRelease",
196                 new Object[]{keyCode},
197                 new Class<?>[]{Integer.TYPE});
198     }
199 
200     /**
201      * Performs a single operation.
202      *
203      * @param method a name of {@code java.awt.Robot} method.
204      * @param params method parameters
205      * @param paramClasses method parameters classes
206      */
makeAnOperation(final String method, final Object[] params, final Class<?>[] paramClasses)207     protected void makeAnOperation(final String method, final Object[] params, final Class<?>[] paramClasses) {
208         if (robotReference == null) {
209             initRobot();
210         }
211         try {
212             robotReference.invokeMethod(method, params, paramClasses);
213             synchronizeRobot();
214         } catch (InvocationTargetException
215                 | IllegalStateException
216                 | NoSuchMethodException
217                 | IllegalAccessException e) {
218             throw (new JemmyException("Exception during java.awt.Robot accessing", e));
219         }
220     }
221 
222     /**
223      * Calls {@code java.awt.Robot.waitForIdle()} method.
224      */
synchronizeRobot()225     protected void synchronizeRobot() {
226         if (!QueueTool.isDispatchThread()) {
227             if ((JemmyProperties.getCurrentDispatchingModel() & JemmyProperties.QUEUE_MODEL_MASK) != 0) {
228                 if (robotReference == null) {
229                     initRobot();
230                 }
231                 try {
232                     robotReference.invokeMethod("waitForIdle", null, null);
233                 } catch (Exception e) {
234                     e.printStackTrace();
235                 }
236             }
237         }
238     }
239 
240     /**
241      * Presses modifiers keys by robot.
242      *
243      * @param modifiers a combination of {@code InputEvent.*_MASK} fields.
244      */
pressModifiers(int modifiers)245     protected void pressModifiers(int modifiers) {
246         if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
247             pressKey(KeyEvent.VK_SHIFT, modifiers & ~InputEvent.SHIFT_MASK);
248         } else if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
249             pressKey(KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
250         } else if ((modifiers & InputEvent.ALT_MASK) != 0) {
251             pressKey(KeyEvent.VK_ALT, modifiers & ~InputEvent.ALT_MASK);
252         } else if ((modifiers & InputEvent.META_MASK) != 0) {
253             pressKey(KeyEvent.VK_META, modifiers & ~InputEvent.META_MASK);
254         } else if ((modifiers & InputEvent.CTRL_MASK) != 0) {
255             pressKey(KeyEvent.VK_CONTROL, modifiers & ~InputEvent.CTRL_MASK);
256         }
257     }
258 
259     /*
260     protected void pressModifiers(ComponentOperator oper, int modifiers) {
261         if       ((modifiers & InputEvent.SHIFT_MASK) != 0) {
262             pressKey(oper, KeyEvent.VK_SHIFT,     modifiers & ~InputEvent.SHIFT_MASK);
263         } else if((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
264             pressKey(oper, KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
265         } else if((modifiers & InputEvent.ALT_MASK) != 0) {
266             pressKey(oper, KeyEvent.VK_ALT,       modifiers & ~InputEvent.ALT_MASK);
267         } else if((modifiers & InputEvent.META_MASK) != 0) {
268             pressKey(oper, KeyEvent.VK_META,      modifiers & ~InputEvent.META_MASK);
269         } else if((modifiers & InputEvent.CTRL_MASK) != 0) {
270             pressKey(oper, KeyEvent.VK_CONTROL,   modifiers & ~InputEvent.CTRL_MASK);
271         }
272     }
273      */
274     /**
275      * Releases modifiers keys by robot.
276      *
277      * @param modifiers a combination of {@code InputEvent.*_MASK} fields.
278      */
releaseModifiers(int modifiers)279     protected void releaseModifiers(int modifiers) {
280         if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
281             releaseKey(KeyEvent.VK_SHIFT, modifiers & ~InputEvent.SHIFT_MASK);
282         } else if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
283             releaseKey(KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
284         } else if ((modifiers & InputEvent.ALT_MASK) != 0) {
285             releaseKey(KeyEvent.VK_ALT, modifiers & ~InputEvent.ALT_MASK);
286         } else if ((modifiers & InputEvent.META_MASK) != 0) {
287             releaseKey(KeyEvent.VK_META, modifiers & ~InputEvent.META_MASK);
288         } else if ((modifiers & InputEvent.CTRL_MASK) != 0) {
289             releaseKey(KeyEvent.VK_CONTROL, modifiers & ~InputEvent.CTRL_MASK);
290         }
291     }
292 
293     /*
294     protected void releaseModifiers(ComponentOperator oper, int modifiers) {
295         if       ((modifiers & InputEvent.SHIFT_MASK) != 0) {
296             releaseKey(oper, KeyEvent.VK_SHIFT,     modifiers & ~InputEvent.SHIFT_MASK);
297         } else if((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
298             releaseKey(oper, KeyEvent.VK_ALT_GRAPH, modifiers & ~InputEvent.ALT_GRAPH_MASK);
299         } else if((modifiers & InputEvent.ALT_MASK) != 0) {
300             releaseKey(oper, KeyEvent.VK_ALT,       modifiers & ~InputEvent.ALT_MASK);
301         } else if((modifiers & InputEvent.META_MASK) != 0) {
302             releaseKey(oper, KeyEvent.VK_META,      modifiers & ~InputEvent.META_MASK);
303         } else if((modifiers & InputEvent.CTRL_MASK) != 0) {
304             releaseKey(oper, KeyEvent.VK_CONTROL,   modifiers & ~InputEvent.CTRL_MASK);
305         }
306     }
307      */
initRobot()308     private void initRobot() {
309         // need to init Robot in dispatch thread because it hangs on Linux
310         // (see http://www.netbeans.org/issues/show_bug.cgi?id=37476)
311         if (QueueTool.isDispatchThread()) {
312             doInitRobot();
313         } else {
314             qtool.invokeAndWait(new Runnable() {
315                 @Override
316                 public void run() {
317                     doInitRobot();
318                 }
319             });
320         }
321     }
322 
doInitRobot()323     private void doInitRobot() {
324         try {
325             ClassReference robotClassReverence = new ClassReference("java.awt.Robot");
326             robotReference = new ClassReference(robotClassReverence.newInstance(null, null));
327             robotReference.invokeMethod("setAutoDelay",
328                     new Object[]{(int) ((autoDelay != null)
329                             ? autoDelay.getValue()
330                             : 0)},
331                     new Class<?>[]{Integer.TYPE});
332         } catch (InvocationTargetException
333                 | IllegalStateException
334                 | NoSuchMethodException
335                 | IllegalAccessException
336                 | ClassNotFoundException
337                 | InstantiationException e) {
338             throw (new JemmyException("Exception during java.awt.Robot accessing", e));
339         }
340     }
341 
342 }
343