/******************************************************************************* * Copyright 2016 Jalian Systems Pvt. Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package net.sourceforge.marathon.javaagent; import java.awt.AWTEvent; import java.awt.Component; import java.awt.Dimension; import java.awt.KeyboardFocusManager; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.awt.dnd.DnDConstants; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.logging.Logger; import javax.swing.SwingUtilities; public class EventQueueDevice extends Device { public static final Logger LOGGER = Logger.getLogger(EventQueueDevice.class.getName()); public class DeviceState { private boolean button1Pressed = false; private boolean button2Pressed = false; private boolean button3Pressed = false; private Component component; private int y; private int x; private Component dragSource; Map<JavaAgentKeys, Boolean> keyStates = new HashMap<JavaAgentKeys, Boolean>(); public DeviceState() { keyStates.put(JavaAgentKeys.SHIFT, false); keyStates.put(JavaAgentKeys.CONTROL, false); keyStates.put(JavaAgentKeys.ALT, false); keyStates.put(JavaAgentKeys.META, false); } public void toggleKeyState(Component component, JavaAgentKeys key) { Boolean pressed = !keyStates.get(key); keyStates.put(key, pressed); if (pressed) { pressKey(component, key); } else { releaseKey(component, key); } } public void setKeyStatePressed(Component component, JavaAgentKeys key) { keyStates.put(key, true); } public void setKeyStateReleased(Component component, JavaAgentKeys key) { keyStates.put(key, false); } private boolean isModifier(CharSequence keys) { return keys == JavaAgentKeys.CONTROL || keys == JavaAgentKeys.ALT || keys == JavaAgentKeys.META || keys == JavaAgentKeys.SHIFT; } private void resetModifierState(Component component) { for (Entry<JavaAgentKeys, Boolean> keyState : keyStates.entrySet()) { if (keyState.getValue()) { toggleKeyState(component, keyState.getKey()); } } } public boolean isShiftPressed() { return keyStates.get(JavaAgentKeys.SHIFT); } public boolean isCtrlPressed() { return keyStates.get(JavaAgentKeys.CONTROL); } public boolean isAltPressed() { return keyStates.get(JavaAgentKeys.ALT); } public boolean isMetaPressed() { return keyStates.get(JavaAgentKeys.META); } public int getModifierEx() { int modifiersEx = 0; if (isShiftPressed()) { modifiersEx |= InputEvent.SHIFT_DOWN_MASK | InputEvent.SHIFT_MASK; } if (isCtrlPressed()) { modifiersEx |= InputEvent.CTRL_DOWN_MASK | InputEvent.CTRL_MASK; } if (isAltPressed()) { modifiersEx |= InputEvent.ALT_DOWN_MASK | InputEvent.ALT_MASK; } if (isMetaPressed()) { modifiersEx |= InputEvent.META_DOWN_MASK | InputEvent.META_MASK; } return modifiersEx; } private void storeMouseDown(int button) { if (button == MouseEvent.BUTTON1) { button1Pressed = true; } if (button == MouseEvent.BUTTON2) { button2Pressed = true; } if (button == MouseEvent.BUTTON3) { button3Pressed = true; } } private void storeMouseUp(int button) { if (button == MouseEvent.BUTTON1) { button1Pressed = false; } if (button == MouseEvent.BUTTON2) { button2Pressed = false; } if (button == MouseEvent.BUTTON3) { button3Pressed = false; } } public int getButtons() { if (button1Pressed) { return MouseEvent.BUTTON1; } if (button2Pressed) { return MouseEvent.BUTTON2; } if (button3Pressed) { return MouseEvent.BUTTON3; } return 0; } public int getButtonMask() { if (button1Pressed) { return InputEvent.BUTTON1_DOWN_MASK; } if (button2Pressed) { return InputEvent.BUTTON2_DOWN_MASK; } if (button3Pressed) { return InputEvent.BUTTON3_DOWN_MASK; } return 0; } public void setComponent(Component component) { if (this.component != component) { x = 0; y = 0; } this.component = component; } public Component getComponent() { if (component == null) { Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); if (activeWindow != null) { return activeWindow.getFocusOwner(); } Window[] windows = Window.getWindows(); if (windows.length > 0) { if (windows[0].getFocusOwner() != null) { return windows[0].getFocusOwner(); } return windows[0]; } } return component; } private void setMousePosition(int x, int y) { this.x = x; this.y = y; } public void setDragSource(Component dragSource) { this.dragSource = dragSource; } public Component getDragSource() { return dragSource; } public boolean isDnDCopyPressed() { if (Platform.getCurrent().is(Platform.MAC)) { return isAltPressed(); } else { return isCtrlPressed(); } } @Override public String toString() { return "DeviceState [shiftPressed=" + isShiftPressed() + ", ctrlPressed=" + isCtrlPressed() + ", altPressed=" + isAltPressed() + ", metaPressed=" + isMetaPressed() + ", button1Pressed=" + button1Pressed + ", button2Pressed=" + button2Pressed + ", button3Pressed=" + button3Pressed + ", y=" + y + ", x=" + x + "]"; } } private DeviceState deviceState = new DeviceState(); public EventQueueDevice() { } /* * (non-Javadoc) * * @see * net.sourceforge.marathon.javaagent.Device#sendKeys(java.awt.Component, * java.lang.CharSequence) */ @Override public void sendKeys(Component component, CharSequence... keysToSend) { for (CharSequence seq : keysToSend) { for (int i = 0; i < seq.length(); i++) { sendKey(component, seq.charAt(i)); } } EventQueueWait.empty(); } /* * (non-Javadoc) * * @see net.sourceforge.marathon.javaagent.Device#pressKey(net.sourceforge. * marathon .javaagent.Keys) */ @Override public void pressKey(Component component, JavaAgentKeys keyToPress) { if (keyToPress == JavaAgentKeys.NULL) { deviceState.resetModifierState(component); } else { if (deviceState.isModifier(keyToPress)) { deviceState.setKeyStatePressed(component, keyToPress); } dispatchKeyEvent(component, keyToPress, KeyEvent.KEY_PRESSED, KeyEvent.CHAR_UNDEFINED); } EventQueueWait.empty(); } /* * (non-Javadoc) * * @see * net.sourceforge.marathon.javaagent.Device#releaseKey(net.sourceforge. * marathon.javaagent.Keys) */ @Override public void releaseKey(Component component, JavaAgentKeys keyToRelease) { if (deviceState.isModifier(keyToRelease)) { deviceState.setKeyStateReleased(component, keyToRelease); } dispatchKeyEvent(component, keyToRelease, KeyEvent.KEY_RELEASED, KeyEvent.CHAR_UNDEFINED); EventQueueWait.empty(); } @Override public void moveto(final Component component) { try { Dimension d = EventQueueWait.exec(new Callable<Dimension>() { @Override public Dimension call() { return component.getSize(); } }); moveto(component, d.width / 2, d.height / 2); } catch (Exception e) { throw new RuntimeException("Unexpected exception while getting component size", e); } EventQueueWait.empty(); } @Override public void moveto(Component component, int xOffset, int yOffset) { int buttons = deviceState.getButtons(); if (component != deviceState.getComponent()) { if (deviceState.getComponent() != null) { dispatchEvent(new MouseEvent(deviceState.getComponent(), MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), deviceState.getModifierEx(), deviceState.x, deviceState.y, 0, false, buttons)); } dispatchEvent(new MouseEvent(component, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), deviceState.getModifierEx(), xOffset, yOffset, 0, false, buttons)); } Component source = component; int id = MouseEvent.MOUSE_MOVED; Point p = new Point(xOffset, yOffset); if (buttons != 0) { id = MouseEvent.MOUSE_DRAGGED; source = deviceState.getDragSource(); if (source != component) { p = SwingUtilities.convertPoint(component, xOffset, yOffset, source); } } int modifierEx = deviceState.getModifierEx() | deviceState.getButtonMask(); MouseEvent mouseEvent = new MouseEvent(source, id, System.currentTimeMillis(), modifierEx, p.x, p.y, 0, false, buttons); dispatchEvent(mouseEvent); EventQueueWait.empty(); deviceState.setComponent(component); deviceState.setMousePosition(xOffset, yOffset); } @Override public void buttonDown(Component component, Buttons button, int xoffset, int yoffset) { dispatchEvent(new MouseEvent(component, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), deviceState.getModifierEx() | InputEvent.BUTTON1_DOWN_MASK, xoffset, yoffset, 1, false, MouseEvent.BUTTON1)); deviceState.storeMouseDown(MouseEvent.BUTTON1); deviceState.setDragSource(component); EventQueueWait.empty(); } @Override public void buttonUp(Component component, Buttons button, int xoffset, int yoffset) { if (button.getButton() == 0) { if (handleDnD(component, xoffset, yoffset)) { EventQueueWait.empty(); return; } } dispatchEvent(new MouseEvent(component, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), deviceState.getModifierEx(), xoffset, yoffset, 1, false, MouseEvent.BUTTON1)); EventQueueWait.empty(); if (deviceState.getComponent() == component) { dispatchMouseEvent(component, button.getButton() == 2, 1, MouseEvent.BUTTON1, xoffset, yoffset); } deviceState.storeMouseUp(MouseEvent.BUTTON1); deviceState.setDragSource(null); EventQueueWait.empty(); } private Boolean handleDnD(Component component, int xoffset, int yoffset) { int dropAction; if (deviceState.isDnDCopyPressed()) { dropAction = DnDConstants.ACTION_COPY; } else { dropAction = DnDConstants.ACTION_MOVE; } Logger.getLogger(EventQueueDevice.class.getName()).info("Performing Drop"); DnDHandler dnd = new DnDHandler(deviceState.getDragSource(), component, xoffset, yoffset, dropAction); return dnd.performDrop(); } private void sendKey(Component component, char c) { component = Device.getActiveComponent(component); JavaAgentKeys keys = JavaAgentKeys.getKeyFromUnicode(c); if (keys == null) { dispatchNormal(component, c); } else if (keys == JavaAgentKeys.NULL) { deviceState.resetModifierState(component); } else if (deviceState.isModifier(keys)) { deviceState.toggleKeyState(component, keys); } else { pressKey(component, keys); if (keys == JavaAgentKeys.SPACE) { dispatchKeyEvent(component, keys, KeyEvent.KEY_TYPED, ' '); } if (keys == JavaAgentKeys.ENTER) { dispatchKeyEvent(component, keys, KeyEvent.KEY_TYPED, '\n'); } releaseKey(component, keys); } } private void dispatchNormal(Component component, char c) { KeyboardMap kbMap = new KeyboardMap(c); List<CharSequence[]> keysList = kbMap.getKeys(); if (keysList == null) { // Generate Key Typed dispatchEvent(new KeyEvent(component, KeyEvent.KEY_TYPED, System.currentTimeMillis(), deviceState.getModifierEx(), KeyEvent.VK_UNDEFINED, c)); return; } for (CharSequence[] keys : keysList) { // Generate Key Press for (CharSequence key : keys) { if (deviceState.isModifier(key)) { pressKey(component, (JavaAgentKeys) key); } else { int keyCode = Integer.parseInt(key.toString()); dispatchEvent(new KeyEvent(component, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), deviceState.getModifierEx(), keyCode, c)); } } // Generate Key Typed dispatchEvent(new KeyEvent(component, KeyEvent.KEY_TYPED, System.currentTimeMillis(), deviceState.getModifierEx(), KeyEvent.VK_UNDEFINED, c)); // Generate Key Release for (int i = keys.length - 1; i >= 0; i--) { CharSequence key = keys[i]; if (deviceState.isModifier(key)) { releaseKey(component, (JavaAgentKeys) key); } else { int keyCode = Integer.parseInt(key.toString()); dispatchEvent(new KeyEvent(component, KeyEvent.KEY_RELEASED, System.currentTimeMillis(), deviceState.getModifierEx(), keyCode, c)); } } } } private void dispatchKeyEvent(final Component component, JavaAgentKeys keyToPress, int id, char c) { try { Rectangle d = EventQueueWait.exec(new Callable<Rectangle>() { @Override public Rectangle call() { return component.getBounds(); } }); ensureVisible(component.getParent(), d); EventQueueWait.call_noexc(component, "requestFocusInWindow"); } catch (Exception e) { throw new RuntimeException("getBounds failed for " + component.getClass().getName(), e); } final KeysMap keysMap = KeysMap.findMap(keyToPress); if (keysMap == null) { return; } int m = deviceState.getModifierEx(); int keyCode = keysMap.getCode(); if (id == KeyEvent.KEY_TYPED) { keyCode = KeyEvent.VK_UNDEFINED; } dispatchEvent(new KeyEvent(component, id, System.currentTimeMillis(), m, keyCode, c)); EventQueueWait.empty(); } private void dispatchEvent(final AWTEvent event) { LOGGER.info(event.toString()); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { ((Component) event.getSource()).dispatchEvent(event); } }); } private void dispatchMouseEvent(Component component, boolean popupTrigger, int clickCount, int buttons, int x, int y) { ensureVisible(component, new Rectangle(x, y, 50, 50)); EventQueueWait.call_noexc(component, "requestFocusInWindow"); int modifierEx = deviceState.getModifierEx(); if (component != deviceState.getComponent()) { if (deviceState.getComponent() != null) { dispatchEvent(new MouseEvent(deviceState.getComponent(), MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), modifierEx, deviceState.x, deviceState.y, 0, popupTrigger, buttons)); } dispatchEvent(new MouseEvent(component, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), modifierEx, x, y, 0, popupTrigger, buttons)); } for (int n = 1; n <= clickCount; n++) { int buttonMask = InputEvent.BUTTON1_DOWN_MASK | InputEvent.BUTTON1_MASK; if (buttons == 3) { buttonMask = InputEvent.BUTTON3_DOWN_MASK | InputEvent.BUTTON3_MASK; } dispatchEvent(new MouseEvent(component, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), modifierEx | buttonMask, x, y, n, popupTrigger, buttons)); buttonMask = InputEvent.BUTTON1_MASK; if (buttons == 3) { buttonMask = InputEvent.BUTTON3_MASK; } dispatchEvent(new MouseEvent(component, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), modifierEx | buttonMask, x, y, n, false, buttons)); dispatchEvent(new MouseEvent(component, MouseEvent.MOUSE_CLICKED, System.currentTimeMillis(), modifierEx | buttonMask, x, y, n, false, buttons)); } } @Override public void click(Component component, Buttons button, int clickCount, int xoffset, int yoffset) { int b = MouseEvent.BUTTON1; if (button.getButton() == 0) { b = MouseEvent.BUTTON1; } else if (button.getButton() == 1) { b = MouseEvent.BUTTON2; } else if (button.getButton() == 2) { b = MouseEvent.BUTTON3; } dispatchMouseEvent(component, button.getButton() == 2, clickCount, b, xoffset, yoffset); EventQueueWait.empty(); deviceState.setComponent(component); } @Override public String toString() { return "EventQueueDevice [deviceState=" + deviceState + "]"; } }