/******************************************************************************* * Copyright (c) 2004, 2010 BREDEX GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * BREDEX GmbH - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.jubula.rc.swt.driver; import java.awt.AWTException; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.eclipse.jubula.rc.common.logger.AutServerLogger; import org.eclipse.jubula.tools.internal.utils.TimeUtil; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; /** * Provides the functionality of <code>java.awt.Robot</code> for use in SWT environments. * @author BREDEX GmbH */ public class SwtRobot { /** the logger */ private static AutServerLogger log = new AutServerLogger(SwtRobot.class); /** the # of modifiers- like SWT.CTRL, SWT.ALT, etc- that are currently defined */ private static final int MODIFIER_COUNT = 7; /** mask used to identify if any mouse buttons were clicked */ private static final int BUTTON_MASK = (SWT.BUTTON1 | SWT.BUTTON2 | SWT.BUTTON3); /** AWT <--> SWT key mapping for US-keyboards */ private static int[][] mappings = { { SWT.ESC, KeyEvent.VK_ESCAPE, 0 }, { SWT.DEL, KeyEvent.VK_DELETE, 0 }, { ' ', KeyEvent.VK_SPACE, 0 }, { '\t', KeyEvent.VK_TAB, 0 }, { '~', KeyEvent.VK_BACK_QUOTE, 1 }, { '`', KeyEvent.VK_BACK_QUOTE, 0 }, { '!', KeyEvent.VK_1, 1 }, { '@', KeyEvent.VK_2, 1 }, { '#', KeyEvent.VK_3, 1 }, { '$', KeyEvent.VK_4, 1 }, { '%', KeyEvent.VK_5, 1 }, { '^', KeyEvent.VK_6, 1 }, { '&', KeyEvent.VK_7, 1 }, { '*', KeyEvent.VK_8, 1 }, { '(', KeyEvent.VK_9, 1 }, { ')', KeyEvent.VK_0, 1 }, { '-', (SWT.getPlatform().equals("gtk")) ? KeyEvent.VK_UNDERSCORE : KeyEvent.VK_MINUS, 0 }, //$NON-NLS-1$ { '_', (SWT.getPlatform().equals("gtk")) ? KeyEvent.VK_UNDERSCORE : KeyEvent.VK_MINUS, 1 }, //$NON-NLS-1$ { '=', KeyEvent.VK_EQUALS, 0 }, { '+', KeyEvent.VK_EQUALS, 1 }, { '[', KeyEvent.VK_OPEN_BRACKET, 0 }, { '{', KeyEvent.VK_OPEN_BRACKET, 1 }, { ']', KeyEvent.VK_CLOSE_BRACKET, 0 }, { '}', KeyEvent.VK_CLOSE_BRACKET, 1 }, { '|', KeyEvent.VK_BACK_SLASH, 1 }, { ';', KeyEvent.VK_SEMICOLON, 0 }, { ':', KeyEvent.VK_SEMICOLON, 1 }, { ',', KeyEvent.VK_COMMA, 0 }, { '<', KeyEvent.VK_COMMA, 1 }, { '.', KeyEvent.VK_PERIOD, 0 }, { '>', (SWT.getPlatform().equals("gtk")) ? KeyEvent.VK_GREATER : KeyEvent.VK_PERIOD, 1 }, //$NON-NLS-1$ { '/', KeyEvent.VK_SLASH, 0 }, { '?', KeyEvent.VK_SLASH, 1 }, { '\\', KeyEvent.VK_BACK_SLASH, 0 }, { '|', KeyEvent.VK_BACK_SLASH, 1 }, { '\'', KeyEvent.VK_QUOTE, 0 }, { '"', KeyEvent.VK_QUOTE, 1 }, { '\r', KeyEvent.VK_ENTER, 0 }, { '\t', KeyEvent.VK_TAB, 0 } }; /** a mapping from unicode characters (non-letter,non-digit) to keycodes for US keyboards */ private static Map<Character, CharCode> keycodes = new HashMap<Character, CharCode>(); /** the display associated with this robot */ private Display m_displayProperty; /** the java.awt.Robot that does all the leg-work for us */ private java.awt.Robot m_robot; /** * Simple internal class for storing info about a keystroke. * @author BREDEX GmbH * @created 18.07.2006 */ private static class CharCode { /***/ private int m_keycode; /***/ private boolean m_shift; /** * Constructor * @param keycode the key code * @param shift the shift */ private CharCode(int keycode, int shift) { m_keycode = keycode; m_shift = (shift == 1); } } /** * Constructs a <code>Robot</code> object in the coordinate system of the primary screen. * @throws SWTException throw AWT-Exception */ public SwtRobot() throws SWTException { try { m_robot = new java.awt.Robot(); keycodes = new HashMap<Character, CharCode>(); for (int i = 0; i < mappings.length; i++) { keycodes.put(new Character((char)mappings[i][0]), new CharCode( mappings[i][1], mappings[i][2])); } } catch (AWTException awte) { throw new SWTException("(Translated AWTException) " + awte.getMessage()); //$NON-NLS-1$ } } /** * CURRENTLY THIS IS FUNCTIONALLY THE SAME AS THE NO-PARAM CONSTRUCTOR * Creates a <code>Robot</code> for the given <code>Display</code>. * @param display the <code>Display</code> associated with this robot * @throws SWTException thrown AWT-Exception */ public SwtRobot(Display display) throws SWTException { this(); m_displayProperty = display; } /** * Creates an image containing pixels read from the screen. <p> * NOTE: Application code must explicitly invoke the * <code>Image.dispose()</code> method to release the operating system * resources managed by each instance when those instances are no longer required. </p> * @param rect <code>Rectangle</code> to capture in screen coordinates * @return the captured <code>Image</code> */ public synchronized Image createScreenCapture(Rectangle rect) { PaletteData pData = new PaletteData(255 << 16, 255 << 8, 255); ImageData iData = new ImageData(rect.width, rect.height, 24, pData); Color color; for (int x = 0; x < rect.width; x++) { for (int y = 0; y < rect.height; y++) { color = getPixelColor(rect.x + x, rect.y + y); iData.setPixel(x, y, color.getRed() << 16 | color.getGreen() << 8 | color.getBlue()); } } Image image = new Image(Display.getDefault(), iData); return image; } /** * Returns the color of a pixel at the given screen coordinates. <p> * NOTE: Application code must explicitly invoke the * <code>Color.dispose()</code> method to release the operating system * resources managed by each instance when those instances are no longer required. </p> * @param x X-position of pixel * @param y Y-position of pixel * @return color of the specified pixel */ public synchronized Color getPixelColor(int x, int y) { java.awt.Color awtColor = m_robot.getPixelColor(x, y); return new Color(Display.getDefault(), awtColor.getRed(), awtColor .getGreen(), awtColor.getBlue()); } /** * Sleep for the specified amount of time, taking care to NOT sleep a thread * that has a corresponding Display object and event loop associated with it. * @param ms the number of milliseconds to sleep */ public static void delay(int ms) { final Display display = Display.getCurrent(); if (display == null) { // Not in UI thread, so just wait. TimeUtil.delay(ms); return; } final boolean[] continueWait = new boolean[] { true }; display.timerExec(ms, new Runnable() { public void run() { continueWait[0] = false; // Makes sure we wake up and see the flag has been turned off. display.asyncExec(null); } }); while (continueWait[0]) { if (!display.readAndDispatch()) { display.sleep(); } } } /** * @return the number of milliseconds this <code>Robot</code> sleeps after * generating an event. */ public synchronized int getAutoDelay() { return m_robot.getAutoDelay(); } /** * Returns whether this <code>Robot</code> automatically invokes * <code>waitForIdle</code> after generating an event. * @return whether <code>waitForIdle</code> is automatically called */ public synchronized boolean isAutoWaitForIdle() { return m_robot.isAutoWaitForIdle(); } /** * Sets the number of milliseconds this <code>Robot</code> sleeps after * generating an event. * @param ms time to sleep in milliseconds */ public synchronized void setAutoDelay(int ms) { m_robot.setAutoDelay(ms); } /** * Sets whether this <code>Robot</code> automatically invokes * <code>waitForIdle</code> after generating an event. * @param isOn Whether <code>waitForIdle</code> is automatically invoked */ public synchronized void setAutoWaitForIdle(boolean isOn) { m_robot.setAutoWaitForIdle(isOn); } /** * @return string representation of this <code>Robot</code>. */ public synchronized String toString() { String params = "autoDelay = " + getAutoDelay() + ", " //$NON-NLS-1$ //$NON-NLS-2$ + "autoWaitForIdle = " + isAutoWaitForIdle(); //$NON-NLS-1$ return getClass().getName() + "[ " + params + " ]"; //$NON-NLS-1$ //$NON-NLS-2$ } /** * Dispatches events from the OS's event queue until all events in the * queue, prior to calling this method, have been handled. */ public synchronized void waitForIdle() { final Display d = m_displayProperty; if (d.getThread() == Thread.currentThread()) { boolean continueWaiting = d.readAndDispatch(); while (continueWaiting) { continueWaiting = d.readAndDispatch(); } } else { d.syncExec(new Runnable() { public void run() { boolean continueWaiting = d.readAndDispatch(); while (continueWaiting) { continueWaiting = d.readAndDispatch(); } } }); } } /** * Presses all keys in a given accelerator. To type a character or digit, * just use the unicode value. For example, <code> accelerator = 'k', * accelerator = 'K', accelerator = '5' </code>. * Note that this is case- sensitive, so the <code>SWT.SHIFT</code> key is * implied if the character is uppercase or otherwise requires the * <code>SWT.SHIFT</code> key. * For non-character keys, use the keycodes defined in org.eclipse.swt.SWT. * For example, to type F1, <code> accelerator = SWT.F1; </code> * Note that an accelerator can contain multiple modifier-key masks- like * <code>accelerator = SWT.CTRL | SWT.ALT | SWT.SHIFT;</code>- but at * most one character or keycode. Also, mouse-button masks are ignored here. * This method ignores characters that do not appear on a US keyboard. * @param accelerator SWT accelerator containing the keys to be pressed * {@inheritDoc} */ public synchronized void keyPress(int accelerator) { int[] keys = getVirtualKeycode(accelerator); boolean shift = false; for (int i = 0; i < keys.length; i++) { if (keys[i] != 0 && keys[i] != KeyEvent.VK_UNDEFINED // make sure // that this // entry is // not empty // and not // invalid && !(keys[i] == KeyEvent.VK_SHIFT && shift)) { try { m_robot.keyPress(keys[i]); } catch (IllegalArgumentException iae) { log.error("IllegalArgumentException: keystroke :" //$NON-NLS-1$ + keys[i] + "\nAccelerator:" //$NON-NLS-1$ + accelerator + "\nCast as a char: " //$NON-NLS-1$ + (char)accelerator); } } if (keys[i] == KeyEvent.VK_SHIFT) { shift = true; } } } /** * Releases all keys in a given accelerator. * @param accelerator SWT accelerator containing the keys to be released * This method ignores characters that do not appear on a US keyboard. * {@inheritDoc} * {@inheritDoc} */ public synchronized void keyRelease(int accelerator) { int[] keys = getVirtualKeycode(accelerator); boolean shift = false; for (int i = 0; i < keys.length; i++) { if (keys[i] != 0) { // make sure that this entry is not empty if (!(keys[i] == KeyEvent.VK_SHIFT && shift)) { m_robot.keyRelease(keys[i]); } if (keys[i] == KeyEvent.VK_SHIFT) { shift = true; } } } } /** * Moves mouse pointer to given screen coordinates. * @param x X-position * @param y Y-position */ public synchronized void mouseMove(final int x, final int y) { m_robot.mouseMove(x, y); } /** * Presses all mouse buttons contained in a given accelerator, which can * include any/all of the following: * <code> SWT.BUTTON1, SWT.BUTTON2, SWT.BUTTON3</code>. * Note that this method will not generate any keystrokes, only mouse button * presses. * @param accelerator SWT accelerator containing the mouse buttons to be pressed * {@inheritDoc} * {@inheritDoc} */ public synchronized void mousePress(int accelerator) { int acc = accelerator; acc &= BUTTON_MASK; if ((acc & SWT.BUTTON1) == SWT.BUTTON1) { m_robot.mousePress(InputEvent.BUTTON1_MASK); } if ((acc & SWT.BUTTON2) == SWT.BUTTON2) { m_robot.mousePress(InputEvent.BUTTON2_MASK); } if ((acc & SWT.BUTTON3) == SWT.BUTTON3) { m_robot.mousePress(InputEvent.BUTTON3_MASK); } } /** * Releases all mouse buttons contained in a given accelerator. <p> * Note that this method will not release any keys, only mouse buttons. </p> * @param accelerator SWT accelerator containing the mouse buttons to be released * {@inheritDoc} * {@inheritDoc} * {@inheritDoc} */ public synchronized void mouseRelease(int accelerator) { int acc = accelerator; acc &= BUTTON_MASK; if ((acc & SWT.BUTTON1) == SWT.BUTTON1) { m_robot.mouseRelease(InputEvent.BUTTON1_MASK); } if ((acc & SWT.BUTTON2) == SWT.BUTTON2) { m_robot.mouseRelease(InputEvent.BUTTON2_MASK); } if ((acc & SWT.BUTTON3) == SWT.BUTTON3) { m_robot.mouseRelease(InputEvent.BUTTON3_MASK); } } /** * Converts a single accelerator code into multiple virtual keycodes. This * ignores mouse buttons. * @param code the key single accelerator code * @return the converted code */ private int[] getVirtualKeycode(int code) { int[] res = new int[MODIFIER_COUNT + 2]; // one extra for the // keycode/character(last pos) // and VK_SHIFT may occupy more // than one pos Arrays.fill(res, 0); int idx = 0; if ((SWT.MODIFIER_MASK & code) != 0) { // check for all modifier keys if ((code & SWT.ALT) == SWT.ALT) { res[idx++] = KeyEvent.VK_ALT; } if ((code & SWT.SHIFT) == SWT.SHIFT) { res[idx++] = KeyEvent.VK_SHIFT; } if ((code & SWT.CTRL) == SWT.CTRL) { res[idx++] = KeyEvent.VK_CONTROL; } if ((code & SWT.COMMAND) == SWT.COMMAND) { res[idx++] = KeyEvent.VK_META; } } int keyCode = code & SWT.KEY_MASK; if ((SWT.KEYCODE_BIT & keyCode) != 0) { // code contains a keycode- set // the last array element res = convResult(keyCode, res); } else { // code contains a unicode character or digit (or ESC or DEL) // handle ESC or DEL if (keyCode == SWT.DEL) { res[MODIFIER_COUNT + 1] = KeyEvent.VK_DELETE; } else if (keyCode == SWT.ESC) { res[MODIFIER_COUNT + 1] = KeyEvent.VK_ESCAPE; } // handle unicode chars if (Character.isDigit((char)keyCode)) { // digits res[MODIFIER_COUNT + 1] = (keyCode - Character.getNumericValue('0')) + KeyEvent.VK_0; } else if ((keyCode >= 'a' && keyCode <= 'z') || (keyCode >= 'A' && keyCode <= 'Z')) { // letters // 'a'-'z' and 'A' - 'Z' if (Character.isUpperCase((char)keyCode)) { res[MODIFIER_COUNT] = KeyEvent.VK_SHIFT; keyCode = Character.getNumericValue(Character.toLowerCase( (char)keyCode)); } res[MODIFIER_COUNT + 1] = Character.getNumericValue( Character.toLowerCase((char)keyCode)); } else { // all other chars on US keyboard CharCode cc = keycodes.get(new Character((char) keyCode)); if (cc == null) { res[MODIFIER_COUNT + 1] = KeyEvent.VK_UNDEFINED; } else { if (cc.m_shift) { res[MODIFIER_COUNT] = KeyEvent.VK_SHIFT; } res[MODIFIER_COUNT + 1] = cc.m_keycode; } } } return res; } /** * @param keyCode the current keycode * @param res the result array * @return the converted result array */ private int[] convResult(int keyCode, int[] res) { switch (keyCode) { case SWT.ARROW_UP: res[MODIFIER_COUNT + 1] = KeyEvent.VK_UP; break; case SWT.ARROW_DOWN: res[MODIFIER_COUNT + 1] = KeyEvent.VK_DOWN; break; case SWT.ARROW_LEFT: res[MODIFIER_COUNT + 1] = KeyEvent.VK_LEFT; break; case SWT.ARROW_RIGHT: res[MODIFIER_COUNT + 1] = KeyEvent.VK_RIGHT; break; case SWT.PAGE_UP: res[MODIFIER_COUNT + 1] = KeyEvent.VK_PAGE_UP; break; case SWT.PAGE_DOWN: res[MODIFIER_COUNT + 1] = KeyEvent.VK_PAGE_DOWN; break; case SWT.HOME: res[MODIFIER_COUNT + 1] = KeyEvent.VK_HOME; break; case SWT.END: res[MODIFIER_COUNT + 1] = KeyEvent.VK_END; break; case SWT.INSERT: res[MODIFIER_COUNT + 1] = KeyEvent.VK_INSERT; break; case SWT.F1: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F1; break; case SWT.F2: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F2; break; case SWT.F3: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F3; break; case SWT.F4: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F4; break; case SWT.F5: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F5; break; case SWT.F6: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F6; break; case SWT.F7: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F7; break; case SWT.F8: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F8; break; case SWT.F9: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F9; break; case SWT.F10: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F10; break; case SWT.F11: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F11; break; case SWT.F12: res[MODIFIER_COUNT + 1] = KeyEvent.VK_F12; break; default: res[MODIFIER_COUNT + 1] = KeyEvent.VK_UNDEFINED; break; } return res; } /** * Get the <code>Display</code> object with which this robot is synchronized. * @return the <code>Display</code> associated with this <code>Robot</code> */ public Display getDisplay() { return m_displayProperty; } }