From ea57e48ed529c6b9ace07f52a50782280f4c46c7 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 25 Mar 2014 23:59:48 -0400 Subject: [PATCH] Add keyboard support --- src/com/limelight/Game.java | 268 +++++++++++++--------- src/com/limelight/KeyboardTranslator.java | 260 +++++++++++++++++++++ 2 files changed, 416 insertions(+), 112 deletions(-) create mode 100644 src/com/limelight/KeyboardTranslator.java diff --git a/src/com/limelight/Game.java b/src/com/limelight/Game.java index 64694461..2575fa94 100644 --- a/src/com/limelight/Game.java +++ b/src/com/limelight/Game.java @@ -7,6 +7,7 @@ import com.limelight.nvstream.NvConnectionListener; import com.limelight.nvstream.StreamConfiguration; import com.limelight.nvstream.av.video.VideoDecoderRenderer; import com.limelight.nvstream.input.ControllerPacket; +import com.limelight.nvstream.input.KeyboardPacket; import com.limelight.utils.Dialog; import com.limelight.utils.SpinnerDialog; @@ -47,6 +48,8 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi private int lastTouchY = 0; private boolean hasMoved = false; + private KeyboardTranslator keybTranslator; + private int height; private int width; private Point screenSize = new Point(0, 0); @@ -123,13 +126,14 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi Display display = getWindowManager().getDefaultDisplay(); display.getSize(screenSize); - + // Warn the user if they're on a metered connection checkDataConnection(); // Start the connection conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this, new StreamConfiguration(width, height, refreshRate)); + keybTranslator = new KeyboardTranslator(conn); conn.start(PlatformBinding.getDeviceName(), sv.getHolder(), drFlags, PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer()); } @@ -179,128 +183,168 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi finish(); } + private static byte getModifierState(KeyEvent event) { + byte modifier = 0; + if (event.isShiftPressed()) { + modifier |= KeyboardPacket.MODIFIER_SHIFT; + } + if (event.isCtrlPressed()) { + modifier |= KeyboardPacket.MODIFIER_CTRL; + } + if (event.isAltPressed()) { + modifier |= KeyboardPacket.MODIFIER_ALT; + } + return modifier; + } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_BUTTON_START: - case KeyEvent.KEYCODE_MENU: - inputMap |= ControllerPacket.PLAY_FLAG; - break; - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_BUTTON_SELECT: - inputMap |= ControllerPacket.BACK_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_LEFT: - inputMap |= ControllerPacket.LEFT_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - inputMap |= ControllerPacket.RIGHT_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_UP: - inputMap |= ControllerPacket.UP_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - inputMap |= ControllerPacket.DOWN_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_B: - inputMap |= ControllerPacket.B_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_A: - inputMap |= ControllerPacket.A_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_X: - inputMap |= ControllerPacket.X_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_Y: - inputMap |= ControllerPacket.Y_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_L1: - inputMap |= ControllerPacket.LB_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_R1: - inputMap |= ControllerPacket.RB_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBL: - inputMap |= ControllerPacket.LS_CLK_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBR: - inputMap |= ControllerPacket.RS_CLK_FLAG; - break; - default: - return super.onKeyDown(keyCode, event); + if (event.getDevice() != null && + (event.getDevice().getSources() & InputDevice.SOURCE_KEYBOARD) != 0) { + short translated = keybTranslator.translate(event.getKeyCode()); + if (translated == 0) { + return super.onKeyDown(keyCode, event); + } + + keybTranslator.sendKeyDown(translated, + getModifierState(event)); } - - // We detect back+start as the special button combo - if ((inputMap & ControllerPacket.BACK_FLAG) != 0 && - (inputMap & ControllerPacket.PLAY_FLAG) != 0) - { - inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG); - inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG; + else { + switch (keyCode) { + case KeyEvent.KEYCODE_BUTTON_START: + case KeyEvent.KEYCODE_MENU: + inputMap |= ControllerPacket.PLAY_FLAG; + break; + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_BUTTON_SELECT: + inputMap |= ControllerPacket.BACK_FLAG; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + inputMap |= ControllerPacket.LEFT_FLAG; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + inputMap |= ControllerPacket.RIGHT_FLAG; + break; + case KeyEvent.KEYCODE_DPAD_UP: + inputMap |= ControllerPacket.UP_FLAG; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + inputMap |= ControllerPacket.DOWN_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_B: + inputMap |= ControllerPacket.B_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_A: + inputMap |= ControllerPacket.A_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_X: + inputMap |= ControllerPacket.X_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_Y: + inputMap |= ControllerPacket.Y_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_L1: + inputMap |= ControllerPacket.LB_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_R1: + inputMap |= ControllerPacket.RB_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + inputMap |= ControllerPacket.LS_CLK_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: + inputMap |= ControllerPacket.RS_CLK_FLAG; + break; + default: + return super.onKeyDown(keyCode, event); + } + + // We detect back+start as the special button combo + if ((inputMap & ControllerPacket.BACK_FLAG) != 0 && + (inputMap & ControllerPacket.PLAY_FLAG) != 0) + { + inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG); + inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG; + } + + sendControllerInputPacket(); } - - sendControllerInputPacket(); + return true; } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_BUTTON_START: - case KeyEvent.KEYCODE_MENU: - inputMap &= ~ControllerPacket.PLAY_FLAG; - break; - case KeyEvent.KEYCODE_BACK: - case KeyEvent.KEYCODE_BUTTON_SELECT: - inputMap &= ~ControllerPacket.BACK_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_LEFT: - inputMap &= ~ControllerPacket.LEFT_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - inputMap &= ~ControllerPacket.RIGHT_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_UP: - inputMap &= ~ControllerPacket.UP_FLAG; - break; - case KeyEvent.KEYCODE_DPAD_DOWN: - inputMap &= ~ControllerPacket.DOWN_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_B: - inputMap &= ~ControllerPacket.B_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_A: - inputMap &= ~ControllerPacket.A_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_X: - inputMap &= ~ControllerPacket.X_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_Y: - inputMap &= ~ControllerPacket.Y_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_L1: - inputMap &= ~ControllerPacket.LB_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_R1: - inputMap &= ~ControllerPacket.RB_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBL: - inputMap &= ~ControllerPacket.LS_CLK_FLAG; - break; - case KeyEvent.KEYCODE_BUTTON_THUMBR: - inputMap &= ~ControllerPacket.RS_CLK_FLAG; - break; - default: - return super.onKeyUp(keyCode, event); + if (event.getDevice() != null && + (event.getDevice().getSources() & InputDevice.SOURCE_KEYBOARD) != 0) { + short translated = keybTranslator.translate(event.getKeyCode()); + if (translated == 0) { + return super.onKeyUp(keyCode, event); + } + + keybTranslator.sendKeyUp(translated, + getModifierState(event)); + } + else { + switch (keyCode) { + case KeyEvent.KEYCODE_BUTTON_START: + case KeyEvent.KEYCODE_MENU: + inputMap &= ~ControllerPacket.PLAY_FLAG; + break; + case KeyEvent.KEYCODE_BACK: + case KeyEvent.KEYCODE_BUTTON_SELECT: + inputMap &= ~ControllerPacket.BACK_FLAG; + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + inputMap &= ~ControllerPacket.LEFT_FLAG; + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + inputMap &= ~ControllerPacket.RIGHT_FLAG; + break; + case KeyEvent.KEYCODE_DPAD_UP: + inputMap &= ~ControllerPacket.UP_FLAG; + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + inputMap &= ~ControllerPacket.DOWN_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_B: + inputMap &= ~ControllerPacket.B_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_A: + inputMap &= ~ControllerPacket.A_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_X: + inputMap &= ~ControllerPacket.X_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_Y: + inputMap &= ~ControllerPacket.Y_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_L1: + inputMap &= ~ControllerPacket.LB_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_R1: + inputMap &= ~ControllerPacket.RB_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBL: + inputMap &= ~ControllerPacket.LS_CLK_FLAG; + break; + case KeyEvent.KEYCODE_BUTTON_THUMBR: + inputMap &= ~ControllerPacket.RS_CLK_FLAG; + break; + default: + return super.onKeyUp(keyCode, event); + } + + // If one of the two is up, the special button comes up too + if ((inputMap & ControllerPacket.BACK_FLAG) == 0 || + (inputMap & ControllerPacket.PLAY_FLAG) == 0) + { + inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG; + } + + sendControllerInputPacket(); } - // If one of the two is up, the special button comes up too - if ((inputMap & ControllerPacket.BACK_FLAG) == 0 || - (inputMap & ControllerPacket.PLAY_FLAG) == 0) - { - inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG; - } - - sendControllerInputPacket(); return true; } diff --git a/src/com/limelight/KeyboardTranslator.java b/src/com/limelight/KeyboardTranslator.java new file mode 100644 index 00000000..5da04c0e --- /dev/null +++ b/src/com/limelight/KeyboardTranslator.java @@ -0,0 +1,260 @@ +package com.limelight; + +import android.view.KeyEvent; + +import com.limelight.nvstream.NvConnection; +import com.limelight.nvstream.input.KeycodeTranslator; + +/** + * Class to translate a Android key code into the codes GFE is expecting + * @author Diego Waxemberg + * @author Cameron Gutman + */ +public class KeyboardTranslator extends KeycodeTranslator { + + /** + * GFE's prefix for every key code + */ + public static final short KEY_PREFIX = (short) 0x80; + + public static final int VK_0 = 48; + public static final int VK_9 = 57; + public static final int VK_A = 65; + public static final int VK_Z = 90; + public static final int VK_ALT = 18; + public static final int VK_NUMPAD0 = 96; + public static final int VK_BACK_SLASH = 92; + public static final int VK_CAPS_LOCK = 20; + public static final int VK_CLEAR = 12; + public static final int VK_COMMA = 44; + public static final int VK_CONTROL = 17; + public static final int VK_BACK_SPACE = 8; + public static final int VK_EQUALS = 61; + public static final int VK_ESCAPE = 27; + public static final int VK_F1 = 112; + public static final int VK_PERIOD = 46; + public static final int VK_INSERT = 155; + public static final int VK_OPEN_BRACKET = 91; + public static final int VK_WINDOWS = 524; + public static final int VK_MINUS = 45; + public static final int VK_END = 35; + public static final int VK_HOME = 36; + public static final int VK_NUM_LOCK = 144; + public static final int VK_PAGE_UP = 33; + public static final int VK_PAGE_DOWN = 34; + public static final int VK_PLUS = 521; + public static final int VK_CLOSE_BRACKET = 93; + public static final int VK_SCROLL_LOCK = 145; + public static final int VK_SEMICOLON = 59; + public static final int VK_SHIFT = 16; + public static final int VK_SLASH = 47; + public static final int VK_SPACE = 32; + public static final int VK_PRINTSCREEN = 154; + public static final int VK_TAB = 9; + public static final int VK_LEFT = 37; + public static final int VK_RIGHT = 39; + public static final int VK_UP = 38; + public static final int VK_DOWN = 40; + public static final int VK_BACK_QUOTE = 192; + public static final int VK_QUOTE = 222; + public static final int VK_PAUSE = 19; + + /** + * Constructs a new translator for the specified connection + * @param conn the connection to which the translated codes are sent + */ + public KeyboardTranslator(NvConnection conn) { + super(conn); + } + + /** + * Translates the given keycode and returns the GFE keycode + * @param keycode the code to be translated + * @return a GFE keycode for the given keycode + */ + @Override + public short translate(int keycode) { + int translated; + + /* There seems to be no clean mapping between Android key codes + * and what Nvidia sends over the wire. If someone finds one, + * I'll happily delete this code :) + */ + if (keycode >= KeyEvent.KEYCODE_0 && + keycode <= KeyEvent.KEYCODE_9) { + translated = (keycode - KeyEvent.KEYCODE_0) + VK_0; + } + else if (keycode >= KeyEvent.KEYCODE_A && + keycode <= KeyEvent.KEYCODE_Z) { + translated = (keycode - KeyEvent.KEYCODE_A) + VK_A; + } + else if (keycode >= KeyEvent.KEYCODE_NUMPAD_0 && + keycode <= KeyEvent.KEYCODE_NUMPAD_9) { + translated = (keycode - KeyEvent.KEYCODE_NUMPAD_0) + VK_NUMPAD0; + } + else if (keycode >= KeyEvent.KEYCODE_F1 && + keycode <= KeyEvent.KEYCODE_F12) { + translated = (keycode - KeyEvent.KEYCODE_F1) + VK_F1; + } + else { + switch (keycode) { + case KeyEvent.KEYCODE_ALT_LEFT: + case KeyEvent.KEYCODE_ALT_RIGHT: + translated = VK_ALT; + break; + + case KeyEvent.KEYCODE_BACKSLASH: + translated = 0xdc; + break; + + case KeyEvent.KEYCODE_CAPS_LOCK: + translated = VK_CAPS_LOCK; + break; + + case KeyEvent.KEYCODE_CLEAR: + translated = VK_CLEAR; + break; + + case KeyEvent.KEYCODE_COMMA: + translated = 0xbc; + break; + + case KeyEvent.KEYCODE_CTRL_LEFT: + case KeyEvent.KEYCODE_CTRL_RIGHT: + translated = VK_CONTROL; + break; + + case KeyEvent.KEYCODE_DEL: + translated = VK_BACK_SPACE; + break; + + case KeyEvent.KEYCODE_ENTER: + translated = 0x0d; + break; + + case KeyEvent.KEYCODE_EQUALS: + translated = 0xbb; + break; + + case KeyEvent.KEYCODE_ESCAPE: + translated = VK_ESCAPE; + break; + + case KeyEvent.KEYCODE_FORWARD_DEL: + // Nvidia maps period to delete + translated = VK_PERIOD; + break; + + case KeyEvent.KEYCODE_INSERT: + translated = -1; + break; + + case KeyEvent.KEYCODE_LEFT_BRACKET: + translated = 0xdb; + break; + + case KeyEvent.KEYCODE_META_LEFT: + case KeyEvent.KEYCODE_META_RIGHT: + translated = VK_WINDOWS; + break; + + case KeyEvent.KEYCODE_MINUS: + translated = 0xbd; + break; + + case KeyEvent.KEYCODE_MOVE_END: + translated = VK_END; + break; + + case KeyEvent.KEYCODE_MOVE_HOME: + translated = VK_HOME; + break; + + case KeyEvent.KEYCODE_NUM_LOCK: + translated = VK_NUM_LOCK; + break; + + case KeyEvent.KEYCODE_PAGE_DOWN: + translated = VK_PAGE_DOWN; + break; + + case KeyEvent.KEYCODE_PAGE_UP: + translated = VK_PAGE_UP; + break; + + case KeyEvent.KEYCODE_PERIOD: + translated = 0xbe; + break; + + case KeyEvent.KEYCODE_RIGHT_BRACKET: + translated = 0xdd; + break; + + case KeyEvent.KEYCODE_SCROLL_LOCK: + translated = VK_SCROLL_LOCK; + break; + + case KeyEvent.KEYCODE_SEMICOLON: + translated = 0xba; + break; + + case KeyEvent.KEYCODE_SHIFT_LEFT: + case KeyEvent.KEYCODE_SHIFT_RIGHT: + translated = VK_SHIFT; + break; + + case KeyEvent.KEYCODE_SLASH: + translated = 0xbf; + break; + + case KeyEvent.KEYCODE_SPACE: + translated = VK_SPACE; + break; + + case KeyEvent.KEYCODE_SYSRQ: + // Android defines this as SysRq/PrntScrn + translated = VK_PRINTSCREEN; + break; + + case KeyEvent.KEYCODE_TAB: + translated = VK_TAB; + break; + + case KeyEvent.KEYCODE_DPAD_LEFT: + translated = VK_LEFT; + break; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + translated = VK_RIGHT; + break; + + case KeyEvent.KEYCODE_DPAD_UP: + translated = VK_UP; + break; + + case KeyEvent.KEYCODE_DPAD_DOWN: + translated = VK_DOWN; + break; + + case KeyEvent.KEYCODE_GRAVE: + translated = VK_BACK_QUOTE; + break; + + case KeyEvent.KEYCODE_APOSTROPHE: + translated = 0xde; + break; + + case KeyEvent.KEYCODE_BREAK: + translated = VK_PAUSE; + break; + + default: + System.out.println("No key for "+keycode); + return 0; + } + } + + return (short) ((KEY_PREFIX << 8) | translated); + } + +}