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