Replace keyboard and mouse handler with evdev

This commit is contained in:
Iwan Timmer
2014-01-07 20:53:44 +01:00
parent 85ffdc2426
commit 7e03e40825
4 changed files with 265 additions and 343 deletions

View File

@@ -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<String> 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<String> inputs = new ArrayList<String>();
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();
}

View File

@@ -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 (code<KEY_CODES.length) {
short gfCode = translator.translate(KEY_CODES[code]);
if (value==KEY_PRESSED)
conn.sendKeyboardInput(gfCode, KeyboardPacket.KEY_DOWN, (byte) 0);
else if (value==KEY_RELEASED)
conn.sendKeyboardInput(gfCode, KeyboardPacket.KEY_UP, (byte) 0);
} else {
byte mouseButton = 0;
switch (code) {
case BTN_LEFT:
mouseButton = MouseButtonPacket.BUTTON_1;
break;
case BTN_MIDDLE:
mouseButton = MouseButtonPacket.BUTTON_2;
break;
case BTN_RIGHT:
mouseButton = MouseButtonPacket.BUTTON_3;
break;
}
if (mouseButton>0) {
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();
}
}
}

View File

@@ -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) {
}
}

View File

@@ -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.
* <br>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.
* <br>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.
* <br>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.
* <br>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.
* <br>This method simply calls the <code>mouseMoved()</code> 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.
* <br>The change in position is calculated and sent to the host.
* <br>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;
}
}