package com.limelight.input; import com.limelight.LimeLog; 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.awt.event.KeyEvent; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; /** * Class that handles keyboard input using the Evdev interface * @author Iwan Timmer */ public class EvdevHandler extends EvdevReader { private static KeyboardTranslator translator; /* GFE's prefix for every key code */ public static final short KEY_PREFIX = (short) 0x80; /* Global controller ID state */ private static int assignedControllerIds = 0; private static Object controllerIdLock = new Object(); /* Gamepad state */ private short buttonFlags; private byte leftTrigger, rightTrigger; private short leftStickX, leftStickY, rightStickX, rightStickY; private boolean gamepadSynced; private short controllerId = -1; private short mouseDeltaX, mouseDeltaY; private byte mouseScroll; private byte keyModifiers; private EvdevAbsolute absLX, absLY, absRX, absRY, absLT, absRT, absDX, absDY; private NvConnection conn; private GamepadMapping mapping; public EvdevHandler(NvConnection conn, String device, GamepadMapping mapping) throws FileNotFoundException, IOException { super(device); this.conn = conn; this.mapping = mapping; 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_z, mapping.reverse_z); absRT = new EvdevAbsolute(device, mapping.abs_rz, mapping.reverse_rz); 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); gamepadSynced = true; } private void assignNewControllerId() { synchronized (controllerIdLock) { for (short i = 0; i < 4; i++) { if ((assignedControllerIds & (1 << i)) == 0) { // Assign this unused value to this input device assignedControllerIds |= (1 << i); controllerId = i; LimeLog.info("Assigning controller ID "+i); return; } } } // All IDs have been assigned so use controller 0 for the rest controllerId = 0; } private void releaseControllerId() { LimeLog.info("Releasing controller ID "+controllerId); synchronized (controllerIdLock) { assignedControllerIds &= ~(1 << controllerId); } } @Override protected 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(); boolean gamepadModified = false; if (type==EvdevConstants.EV_SYN) { if (!gamepadSynced) { // Assign a controller ID if one hasn't been assigned yet. // Note that we're only assigning IDs to things that actually send // some form of controller input to avoid falsely reserving IDs for // devices that aren't actually controllers or are inactive controllers. if (controllerId < 0) { assignNewControllerId(); } conn.sendControllerInput(controllerId, buttonFlags, leftTrigger, rightTrigger, leftStickX, leftStickY, rightStickX, rightStickY); gamepadSynced = true; } if (mouseDeltaX != 0 || mouseDeltaY != 0) { conn.sendMouseMove(mouseDeltaX, mouseDeltaY); mouseDeltaX = mouseDeltaY = 0; } if (mouseScroll != 0) { conn.sendMouseScroll(mouseScroll); mouseScroll = 0; } } else 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 { gamepadModified = true; if (gamepadButton != 0) { if (value==EvdevConstants.KEY_PRESSED) buttonFlags |= gamepadButton; else if (value==EvdevConstants.KEY_RELEASED) buttonFlags &= ~gamepadButton; } else if (code==mapping.btn_tl2) leftTrigger = (byte) (value==EvdevConstants.KEY_PRESSED ? 0xFF : 0); else if (code==mapping.btn_tr2) rightTrigger = (byte) (value==EvdevConstants.KEY_PRESSED ? 0xFF : 0); else gamepadModified = false; } } } else if (type==EvdevConstants.EV_REL) { if (code==EvdevConstants.REL_X) mouseDeltaX = (short) value; else if (code==EvdevConstants.REL_Y) mouseDeltaY = (short) value; else if (code==EvdevConstants.REL_WHEEL) mouseScroll = (byte) value; } else if (type==EvdevConstants.EV_ABS) { gamepadModified = true; 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_z) leftTrigger = absLT.getByte(value); else if (code==mapping.abs_rz) rightTrigger = absRT.getByte(value); else if (code==mapping.abs_dpad_x) { int dir = absDX.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 = absDY.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; } } else gamepadModified = false; } gamepadSynced &= !gamepadModified; } @Override protected void deviceRemoved() { // Release this device's controller ID (if it has one) if (controllerId >= 0) { releaseControllerId(); } } private short accountForDeadzone(short value) { return Math.abs(value) > mapping.abs_deadzone?value:0; } }