package com.limelight.input; import com.limelight.nvstream.NvConnection; import com.limelight.nvstream.input.ControllerPacket; import com.limelight.nvstream.input.KeyboardPacket; import com.limelight.nvstream.input.MouseButtonPacket; import java.io.File; 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; /* GFE's prefix for every key code */ public static final short KEY_PREFIX = (short) 0x80; /* Gamepad state */ private short buttonFlags; private byte leftTrigger, rightTrigger; private short leftStickX, leftStickY, rightStickX, rightStickY; private EvdevAbsolute absLX, absLY, absRX, absRY, absLT, absRT, absDX, absDY; private NvConnection conn; private FileChannel deviceInput; private ByteBuffer inputBuffer; private GamepadMapping mapping; public EvdevHandler(NvConnection conn, String device, GamepadMapping mapping) throws FileNotFoundException, IOException { this.conn = conn; this.mapping = mapping; File file = new File(device); if (!file.exists()) throw new FileNotFoundException("File " + device + " not found"); if (!file.canRead()) throw new IOException("Can't read from " + device); FileInputStream in = new FileInputStream(file); deviceInput = in.getChannel(); inputBuffer = ByteBuffer.allocate(EvdevConstants.MAX_STRUCT_SIZE_BYTES); inputBuffer.order(ByteOrder.nativeOrder()); absLX = new EvdevAbsolute(device, mapping.abs_x, mapping.reverse_x); absLY = new EvdevAbsolute(device, mapping.abs_y, !mapping.reverse_y); absRX = new EvdevAbsolute(device, mapping.abs_rx, mapping.reverse_rx); absRY = new EvdevAbsolute(device, mapping.abs_ry, !mapping.reverse_ry); absLT = new EvdevAbsolute(device, mapping.abs_rudder, mapping.reverse_rudder); absRT = new EvdevAbsolute(device, mapping.abs_throttle, mapping.reverse_throttle); absDX = new EvdevAbsolute(device, mapping.abs_dpad_x, mapping.reverse_dpad_x); absDY = new EvdevAbsolute(device, mapping.abs_dpad_y, mapping.reverse_dpad_y); 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) { if (buffer.limit()==EvdevConstants.MAX_STRUCT_SIZE_BYTES) { long time_sec = buffer.getLong(); long time_usec = buffer.getLong(); } else { int time_sec = buffer.getInt(); int time_usec = buffer.getInt(); } short type = buffer.getShort(); short code = buffer.getShort(); int value = buffer.getInt(); if (type==EvdevConstants.EV_KEY) { if (code0) { if (value==EvdevConstants.KEY_PRESSED) conn.sendMouseButtonDown(mouseButton); else if (value==EvdevConstants.KEY_RELEASED) conn.sendMouseButtonUp(mouseButton); } else { if (gamepadButton != 0) { if (value==EvdevConstants.KEY_PRESSED) { buttonFlags |= gamepadButton; } else if (value==EvdevConstants.KEY_RELEASED){ buttonFlags &= ~gamepadButton; } } else if (code==mapping.btn_throttle) { leftTrigger = (byte) (value==EvdevConstants.KEY_PRESSED ? -1 : 0); } else if (code==mapping.btn_rudder) { rightTrigger = (byte) (value==EvdevConstants.KEY_PRESSED ? -1 : 0); } conn.sendControllerInput(buttonFlags, leftTrigger, rightTrigger, leftStickX, leftStickY, rightStickX, rightStickY); } } } else if (type==EvdevConstants.EV_REL) { if (code==EvdevConstants.REL_X) conn.sendMouseMove((short) value, (short) 0); else if (code==EvdevConstants.REL_Y) conn.sendMouseMove((short) 0, (short) value); } else if (type==EvdevConstants.EV_ABS) { if (code==mapping.abs_x) leftStickX = accountForDeadzone(absLX.getShort(value)); else if (code==mapping.abs_y) leftStickY = accountForDeadzone(absLY.getShort(value)); else if (code==mapping.abs_rx) rightStickX = accountForDeadzone(absRX.getShort(value)); else if (code==mapping.abs_ry) rightStickY = accountForDeadzone(absRY.getShort(value)); else if (code==mapping.abs_throttle) leftTrigger = absLT.getByte(value); else if (code==mapping.abs_rudder) rightTrigger = absRT.getByte(value); else if (code==mapping.abs_dpad_x) { int dir = absRT.getDirection(value); if (dir==EvdevAbsolute.UP) { buttonFlags |= ControllerPacket.RIGHT_FLAG; buttonFlags &= ~ControllerPacket.LEFT_FLAG; } else if (dir==EvdevAbsolute.NONE) { buttonFlags &= ~ControllerPacket.LEFT_FLAG; buttonFlags &= ~ControllerPacket.RIGHT_FLAG; } else if (dir==EvdevAbsolute.DOWN) { buttonFlags |= ControllerPacket.LEFT_FLAG; buttonFlags &= ~ControllerPacket.RIGHT_FLAG; } } else if (code==mapping.abs_dpad_y) { int dir = absRT.getDirection(value); if (dir==EvdevAbsolute.DOWN) { buttonFlags |= ControllerPacket.UP_FLAG; buttonFlags &= ~ControllerPacket.DOWN_FLAG; } else if (dir==EvdevAbsolute.NONE) { buttonFlags &= ~ControllerPacket.DOWN_FLAG; buttonFlags &= ~ControllerPacket.UP_FLAG; } else if (dir==EvdevAbsolute.UP) { buttonFlags |= ControllerPacket.DOWN_FLAG; buttonFlags &= ~ControllerPacket.UP_FLAG; } } conn.sendControllerInput(buttonFlags, leftTrigger, rightTrigger, leftStickX, leftStickY, rightStickX, rightStickY); } } private short accountForDeadzone(short value) { if (Math.abs(value) > mapping.abs_deadzone) { return value; } else { return 0; } } @Override public void run() { try { while (true) { while(inputBuffer.remaining()==EvdevConstants.MAX_STRUCT_SIZE_BYTES) deviceInput.read(inputBuffer); inputBuffer.flip(); parseEvent(inputBuffer); inputBuffer.clear(); } } catch (IOException e) { e.printStackTrace(); } } }