From 5d898d7ba195dc252afd616034f4ebcaa4c09046 Mon Sep 17 00:00:00 2001 From: Iwan Timmer Date: Tue, 7 Jan 2014 20:53:44 +0100 Subject: [PATCH] Replace keyboard and mouse handler with evdev --- src/com/limelight/Limelight.java | 18 +- src/com/limelight/input/EvdevHandler.java | 252 +++++++++++++++++++ src/com/limelight/input/KeyboardHandler.java | 109 -------- src/com/limelight/input/MouseHandler.java | 229 ----------------- 4 files changed, 265 insertions(+), 343 deletions(-) create mode 100644 src/com/limelight/input/EvdevHandler.java delete mode 100644 src/com/limelight/input/KeyboardHandler.java delete mode 100644 src/com/limelight/input/MouseHandler.java diff --git a/src/com/limelight/Limelight.java b/src/com/limelight/Limelight.java index d46652d..b590099 100644 --- a/src/com/limelight/Limelight.java +++ b/src/com/limelight/Limelight.java @@ -20,6 +20,8 @@ import com.limelight.settings.PreferencesManager.Preferences.Resolution; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; import org.xmlpull.v1.XmlPullParserException; /** @@ -46,7 +48,7 @@ public class Limelight implements NvConnectionListener { /* * Creates a connection to the host and starts up the stream. */ - private void startUp(StreamConfiguration streamConfig, boolean fullscreen) { + private void startUp(StreamConfiguration streamConfig, List inputs) { conn = new NvConnection(host, this, streamConfig); conn.start(PlatformBinding.getDeviceName(), null, VideoDecoderRenderer.FLAG_PREFER_QUALITY, @@ -143,7 +145,7 @@ public class Limelight implements NvConnectionListener { //TODO: make this less jank private static void parseCommandLine(String[] args) { String host = null; - boolean fullscreen = false; + List inputs = new ArrayList(); boolean pair = false; int resolution = 720; int refresh = 30; @@ -157,10 +159,16 @@ public class Limelight implements NvConnectionListener { System.out.println("Syntax error: hostname or ip address expected after -host"); System.exit(3); } + } else if (args[i].equals("-input")) { + if (i + 1 < args.length) { + inputs.add(args[i+1]); + i++; + } else { + System.out.println("Syntax error: input device expected after -input"); + System.exit(3); + } } else if (args[i].equals("-pair")) { pair = true; - } else if (args[i].equals("-fs")) { - fullscreen = true; } else if (args[i].equals("-720")) { resolution = 720; } else if (args[i].equals("-1080")) { @@ -195,7 +203,7 @@ public class Limelight implements NvConnectionListener { Limelight limelight = new Limelight(host); if (!pair) - limelight.startUp(streamConfig, fullscreen); + limelight.startUp(streamConfig, inputs); else limelight.pair(); } diff --git a/src/com/limelight/input/EvdevHandler.java b/src/com/limelight/input/EvdevHandler.java new file mode 100644 index 0000000..b085091 --- /dev/null +++ b/src/com/limelight/input/EvdevHandler.java @@ -0,0 +1,252 @@ +package com.limelight.input; + +import com.limelight.nvstream.NvConnection; +import com.limelight.nvstream.input.KeyboardPacket; +import com.limelight.nvstream.input.MouseButtonPacket; +import java.awt.event.KeyEvent; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; + +/** + * Class that handles keyboard input using the Evdev interface + * @author Iwan Timmer + */ +public class EvdevHandler implements Runnable { + + private static KeyboardTranslator translator; + + private static final short KEY_CODES[] = { + 0, //KeyEvent.VK_RESERVED + KeyEvent.VK_ESCAPE, + KeyEvent.VK_1, + KeyEvent.VK_2, + KeyEvent.VK_3, + KeyEvent.VK_4, + KeyEvent.VK_5, + KeyEvent.VK_6, + KeyEvent.VK_7, + KeyEvent.VK_8, + KeyEvent.VK_9, + KeyEvent.VK_0, + KeyEvent.VK_MINUS, + KeyEvent.VK_EQUALS, + KeyEvent.VK_BACK_SPACE, + KeyEvent.VK_TAB, + KeyEvent.VK_Q, + KeyEvent.VK_W, + KeyEvent.VK_E, + KeyEvent.VK_R, + KeyEvent.VK_T, + KeyEvent.VK_Y, + KeyEvent.VK_U, + KeyEvent.VK_I, + KeyEvent.VK_O, + KeyEvent.VK_P, + 0, //KeyEvent.VK_LEFTBRACE, + 0, //KeyEvent.VK_RIGHTBRACE, + KeyEvent.VK_ENTER, + KeyEvent.VK_CONTROL, // Left control */ + KeyEvent.VK_A, + KeyEvent.VK_S, + KeyEvent.VK_D, + KeyEvent.VK_F, + KeyEvent.VK_G, + KeyEvent.VK_H, + KeyEvent.VK_J, + KeyEvent.VK_K, + KeyEvent.VK_L, + KeyEvent.VK_SEMICOLON, + 0, //KeyEvent.VK_APOSTROPHE, + 0, //KeyEvent.VK_GRAVE, + 0, //KeyEvent.VK_LEFTSHIFT, + 0, //KeyEvent.VK_BACKSLASH, + KeyEvent.VK_Z, + KeyEvent.VK_X, + KeyEvent.VK_C, + KeyEvent.VK_V, + KeyEvent.VK_B, + KeyEvent.VK_N, + KeyEvent.VK_M, + KeyEvent.VK_COMMA, + 0, //KeyEvent.VK_DOT, + KeyEvent.VK_SLASH, + 0, //KeyEvent.VK_RIGHTSHIFT, + 0, //KeyEvent.VK_KPASTERISK, + 0, //KeyEvent.VK_LEFTALT, + KeyEvent.VK_SPACE, + 0, //KeyEvent.VK_CAPSLOCK, + KeyEvent.VK_F1, + KeyEvent.VK_F2, + KeyEvent.VK_F3, + KeyEvent.VK_F4, + KeyEvent.VK_F5, + KeyEvent.VK_F6, + KeyEvent.VK_F7, + KeyEvent.VK_F8, + KeyEvent.VK_F9, + KeyEvent.VK_F10, + KeyEvent.VK_NUM_LOCK, + KeyEvent.VK_SCROLL_LOCK, + KeyEvent.VK_NUMPAD7, + KeyEvent.VK_NUMPAD8, + KeyEvent.VK_NUMPAD9, + 0, //KeyEvent.VK_NUMPAD_MINUS, + KeyEvent.VK_NUMPAD4, + KeyEvent.VK_NUMPAD5, + KeyEvent.VK_NUMPAD6, + 0, //KeyEvent.VK_NUMPADPLUS, + KeyEvent.VK_NUMPAD1, + KeyEvent.VK_NUMPAD2, + KeyEvent.VK_NUMPAD3, + KeyEvent.VK_NUMPAD0, + 0, //KeyEvent.VK_NUMPADDOT, + 0, + 0, //KeyEvent.VK_ZENKAKUHANKAKU, + 0, //KeyEvent.VK_102ND, + KeyEvent.VK_F11, + KeyEvent.VK_F12, + 0, //KeyEvent.VK_RO, + KeyEvent.VK_KATAKANA, + KeyEvent.VK_HIRAGANA, + 0, //KeyEvent.VK_HENKAN, + 0, //KeyEvent.VK_KATAKANAHIRAGANA, + 0, //KeyEvent.VK_MUHENKAN, + 0, //KeyEvent.VK_KPJPCOMMA, + 0, //KeyEvent.VK_KPENTER, + 0, //KeyEvent.VK_RIGHTCTRL, + 0, //KeyEvent.VK_KPSLASH, + 0, //KeyEvent.VK_SYSRQ, + 0, //KeyEvent.VK_RIGHTALT, + 0, //KeyEvent.VK_LINEFEED, + KeyEvent.VK_HOME, + KeyEvent.VK_UP, + KeyEvent.VK_PAGE_UP, + KeyEvent.VK_LEFT, + KeyEvent.VK_RIGHT, + KeyEvent.VK_END, + KeyEvent.VK_DOWN, + KeyEvent.VK_PAGE_DOWN, + KeyEvent.VK_INSERT, + KeyEvent.VK_DELETE, + 0, //KeyEvent.VK_MACRO, + 0, //KeyEvent.VK_MUTE, + 0, //KeyEvent.VK_VOLUMEDOWN, + 0, //KeyEvent.VK_VOLUMEUP, + 0, //KeyEvent.VK_POWER, /* SC System Power Down */ + 0, //KeyEvent.VK_KPEQUAL, + 0, //KeyEvent.VK_KPPLUSMINUS, + KeyEvent.VK_PAUSE, + 0, //KeyEvent.VK_SCALE, /* AL Compiz Scale (Expose) */ + }; + + private static final int STRUCT_SIZE_BYTES = 24; + + /* GFE's prefix for every key code */ + public static final short KEY_PREFIX = (short) 0x80; + + /* Event types */ + public static final short EV_KEY = 0x01; + public static final short EV_REL = 0x02; + + public static final short REL_X = 0x00; + public static final short REL_Y = 0x01; + + public static final short BTN_LEFT = 0x110; + public static final short BTN_RIGHT = 0x111; + public static final short BTN_MIDDLE = 0x112; + + /* Events */ + public static final int KEY_RELEASED = 0; + public static final int KEY_PRESSED = 1; + + private NvConnection conn; + private FileChannel deviceInput; + private ByteBuffer inputBuffer; + + public EvdevHandler(NvConnection conn, String device) throws FileNotFoundException { + this.conn = conn; + FileInputStream in = new FileInputStream(device); + deviceInput = in.getChannel(); + inputBuffer = ByteBuffer.allocate(STRUCT_SIZE_BYTES); + inputBuffer.order(ByteOrder.nativeOrder()); + + translator = new KeyboardTranslator(conn); + } + + public void start() { + Thread thread = new Thread(this); + thread.setDaemon(true); + thread.setName("Input - Receiver"); + thread.start(); + } + + private void parseEvent(ByteBuffer buffer) { + long time_sec = buffer.getLong(); + long time_usec = buffer.getLong(); + short type = buffer.getShort(); + short code = buffer.getShort(); + int value = buffer.getInt(); + + if (type==EV_KEY) { + if (code0) { + if (value==KEY_PRESSED) + conn.sendMouseButtonDown(mouseButton); + else if (value==KEY_RELEASED) + conn.sendMouseButtonUp(mouseButton); + } + } + } else if (type==EV_REL) { + switch (code) { + case REL_X: + conn.sendMouseMove((short) value, (short) 0); + break; + case REL_Y: + conn.sendMouseMove((short) 0, (short) value); + break; + } + } + } + + @Override + public void run() { + try { + while (true) { + while(inputBuffer.hasRemaining()) + deviceInput.read(inputBuffer); + + inputBuffer.flip(); + parseEvent(inputBuffer); + inputBuffer.clear(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/com/limelight/input/KeyboardHandler.java b/src/com/limelight/input/KeyboardHandler.java deleted file mode 100644 index 496a081..0000000 --- a/src/com/limelight/input/KeyboardHandler.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.limelight.input; - -import java.awt.event.KeyEvent; -import java.awt.event.KeyListener; - -import com.limelight.gui.StreamFrame; -import com.limelight.nvstream.NvConnection; -import com.limelight.nvstream.input.KeyboardPacket; - -/** - * Class that handles keyboard input - * @author Diego Waxemberg - */ -public class KeyboardHandler implements KeyListener { - - private static KeyboardTranslator translator; - private StreamFrame parent; - - /** - * Constructs a new keyboard listener that will send key events to the specified connection - * and belongs to the specified frame - * @param conn the connection to send key events to - * @param parent the frame that owns this handler - */ - public KeyboardHandler(NvConnection conn, StreamFrame parent) { - translator = new KeyboardTranslator(conn); - this.parent = parent; - } - - /** - * Invoked when a key is pressed and will send that key-down event to the host - * @param event the key-down event - */ - @Override - public void keyPressed(KeyEvent event) { - short keyMap = translator.translate(event.getKeyCode()); - - byte modifier = 0x0; - - int modifiers = event.getModifiersEx(); - if ((modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0) { - modifier |= KeyboardPacket.MODIFIER_SHIFT; - } - if ((modifiers & KeyEvent.CTRL_DOWN_MASK) != 0) { - modifier |= KeyboardPacket.MODIFIER_CTRL; - } - if ((modifiers & KeyEvent.ALT_DOWN_MASK) != 0) { - modifier |= KeyboardPacket.MODIFIER_ALT; - } - - if ((modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0 && - (modifiers & KeyEvent.ALT_DOWN_MASK) != 0 && - (modifiers & KeyEvent.CTRL_DOWN_MASK) != 0 && - event.getKeyCode() == KeyEvent.VK_Q) { - System.out.println("quitting"); - parent.close(); - } else if ( - (modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0 && - (modifiers & KeyEvent.ALT_DOWN_MASK) != 0 && - (modifiers & KeyEvent.CTRL_DOWN_MASK) != 0) { - parent.freeMouse(); - return; - } - - - - translator.sendKeyDown(keyMap, modifier); - } - - /** - * Invoked when a key is released and will send that key-up event to the host - * @param event the key-up event - */ - @Override - public void keyReleased(KeyEvent event) { - int modifiers = event.getModifiersEx(); - - if ((modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0 || - (modifiers & KeyEvent.ALT_DOWN_MASK) != 0 || - (modifiers & KeyEvent.CTRL_DOWN_MASK) != 0) { - parent.captureMouse(); - } - - short keyMap = translator.translate(event.getKeyCode()); - - byte modifier = 0x0; - - if ((modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0) { - modifier |= KeyboardPacket.MODIFIER_SHIFT; - } - if ((modifiers & KeyEvent.CTRL_DOWN_MASK) != 0) { - modifier |= KeyboardPacket.MODIFIER_CTRL; - } - if ((modifiers & KeyEvent.ALT_DOWN_MASK) != 0) { - modifier |= KeyboardPacket.MODIFIER_ALT; - } - - translator.sendKeyUp(keyMap, modifier); - } - - /** - * Unimplemented - * @param event unused - */ - @Override - public void keyTyped(KeyEvent event) { - } - -} diff --git a/src/com/limelight/input/MouseHandler.java b/src/com/limelight/input/MouseHandler.java deleted file mode 100644 index 7beada5..0000000 --- a/src/com/limelight/input/MouseHandler.java +++ /dev/null @@ -1,229 +0,0 @@ -package com.limelight.input; - -import java.awt.AWTException; -import java.awt.Dimension; -import java.awt.Point; -import java.awt.Robot; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; - -import javax.swing.SwingUtilities; - -import com.limelight.gui.StreamFrame; -import com.limelight.nvstream.NvConnection; -import com.limelight.nvstream.input.MouseButtonPacket; - -/** - * Handles mouse input and sends them via the connection to the host - * @author Diego Waxemberg - */ -public class MouseHandler implements MouseListener, MouseMotionListener { - private NvConnection conn; - private Robot robot; - private Dimension size; - private StreamFrame parent; - private int lastX = 0; - private int lastY = 0; - private boolean captureMouse = true; - - private final double mouseThresh = 0.45; - - /** - * Constructs a new handler for the specified connection and belonging to the specified frame - * @param conn the connection to which mouse events will be sent - * @param parent the frame that owns this handler - */ - public MouseHandler(NvConnection conn, StreamFrame parent) { - this.conn = conn; - this.parent = parent; - try { - this.robot = new Robot(); - } catch (AWTException e) { - e.printStackTrace(); - } - size = new Dimension(); - } - - /** - * Frees the mouse, that is stops capturing events and allows it to move freely - */ - public void free() { - captureMouse = false; - } - - /** - * Starts capturing mouse events and limits its motion - */ - public void capture() { - moveMouse((int)parent.getLocationOnScreen().getX() + (size.width/2), - (int)parent.getLocationOnScreen().getY() + (size.height/2)); - captureMouse = true; - } - - /** - * Only used to hide the cursor when the user clicks back into the frame. - *
The event is not sent to the host - * @param e click event used to know that the cursor should now be hidden - */ - @Override - public void mouseClicked(MouseEvent e) { - if (captureMouse) { - parent.hideCursor(); - } - } - - /** - * Unimplemented - * @param e Unused - */ - @Override - public void mouseEntered(MouseEvent e) { - } - - /** - * Invoked when the mouse leaves the frame. - *
If this happens when we are capturing the mouse, the mouse is moved back to the center of the frame. - * @param e the event created by the mouse leaving the frame - */ - @Override - public void mouseExited(MouseEvent e) { - if (captureMouse) { - checkBoundaries(e); - } - } - - /** - * Invoked when a mouse button is pressed. - *
The button pressed is sent to the host if we are capturing the mouse. - * @param e event containing the mouse button that was pressed - */ - @Override - public void mousePressed(MouseEvent e) { - if (captureMouse) { - byte mouseButton = 0x0; - - if (SwingUtilities.isLeftMouseButton(e)) { - mouseButton = MouseButtonPacket.BUTTON_1; - } - - if (SwingUtilities.isMiddleMouseButton(e)) { - mouseButton = MouseButtonPacket.BUTTON_2; - } - - if (SwingUtilities.isRightMouseButton(e)) { - mouseButton = MouseButtonPacket.BUTTON_3; - } - - if (mouseButton > 0) { - conn.sendMouseButtonDown(mouseButton); - } - } - } - - /** - * Invoked when a mouse button is released. - *
The button released is sent to the host if we are capturing the mouse. - * @param e event containing the mouse button that was released - */ - @Override - public void mouseReleased(MouseEvent e) { - if (captureMouse) { - byte mouseButton = 0x0; - - if (SwingUtilities.isLeftMouseButton(e)) { - mouseButton = MouseButtonPacket.BUTTON_1; - } - - if (SwingUtilities.isMiddleMouseButton(e)) { - mouseButton = MouseButtonPacket.BUTTON_2; - } - - if (SwingUtilities.isRightMouseButton(e)) { - mouseButton = MouseButtonPacket.BUTTON_3; - } - - if (mouseButton > 0) { - conn.sendMouseButtonUp(mouseButton); - } - } - } - - /** - * Invoked when the mouse is dragged, that is moved while a button is held down. - *
This method simply calls the mouseMoved() method because GFE handles movements all the same - * when a button is held down or not. - */ - @Override - public void mouseDragged(MouseEvent e) { - if (captureMouse) { - mouseMoved(e); - } - } - - /** - * Invoked when the mouse is moved. - *
The change in position is calculated and sent to the host. - *
If the mouse moves outside a certain boundary, the mouse is moved back to the center- this gives the user - * the illusion that they are controlling the mouse they see rather than their own. - * @param e the mouse move event containing the new location of the mouse - */ - @Override - public void mouseMoved(MouseEvent e) { - if (captureMouse) { - Point mouse = e.getLocationOnScreen(); - int x = (int)mouse.getX(); - int y = (int)mouse.getY(); - conn.sendMouseMove((short)(x - lastX), (short)(y - lastY)); - lastX = x; - lastY = y; - - checkBoundaries(e); - } - } - - /* - * Checks if the mouse has moved outside the boundaries. - * If so, the mouse is moved back to the center. - */ - private void checkBoundaries(MouseEvent e) { - parent.getSize(size); - - int leftEdge = (int) parent.getLocationOnScreen().getX(); - int rightEdge = leftEdge + size.width; - int upperEdge = (int) parent.getLocationOnScreen().getY(); - int lowerEdge = upperEdge + size.height; - - Point mouse = e.getLocationOnScreen(); - - double xThresh = (size.width * mouseThresh); - double yThresh = (size.height * mouseThresh); - - int newX = (int)mouse.getX(); - int newY = (int)mouse.getY(); - boolean shouldMoveMouse = false; - - if (mouse.getX() < leftEdge + xThresh || mouse.getX() > rightEdge - xThresh) { - newX = (leftEdge + rightEdge) / 2; - shouldMoveMouse = true; - } - if (mouse.getY() < upperEdge + yThresh || mouse.getY() > lowerEdge - yThresh) { - newY = (upperEdge + lowerEdge) / 2; - shouldMoveMouse = true; - } - - if (shouldMoveMouse) { - moveMouse(newX, newY); - } - } - - /* - * Moves the mouse to the specified coordinates on-screen - */ - private void moveMouse(int x, int y) { - robot.mouseMove(x, y); - lastX = x; - lastY = y; - } - -}