diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 964c6a2f..6159a176 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -107,6 +107,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, private static final int THREE_FINGER_TAP_THRESHOLD = 300; private ControllerHandler controllerHandler; + private KeyboardTranslator keyboardTranslator; private VirtualController virtualController; private PreferenceConfiguration prefConfig; @@ -443,9 +444,11 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Initialize the connection conn = new NvConnection(host, uniqueId, config, PlatformBinding.getCryptoProvider(this), serverCert); controllerHandler = new ControllerHandler(this, conn, this, prefConfig); + keyboardTranslator = new KeyboardTranslator(); InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE); inputManager.registerInputDeviceListener(controllerHandler, null); + inputManager.registerInputDeviceListener(keyboardTranslator, null); // Initialize touch contexts for (int i = 0; i < touchContextMap.length; i++) { @@ -886,10 +889,13 @@ public class Game extends Activity implements SurfaceHolder.Callback, protected void onDestroy() { super.onDestroy(); + InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE); if (controllerHandler != null) { - InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE); inputManager.unregisterInputDeviceListener(controllerHandler); } + if (keyboardTranslator != null) { + inputManager.unregisterInputDeviceListener(keyboardTranslator); + } if (lowLatencyWifiLock != null) { lowLatencyWifiLock.release(); @@ -1109,7 +1115,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, if (!handled) { // Try the keyboard handler - short translated = KeyboardTranslator.translate(event.getKeyCode()); + short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId()); if (translated == 0) { return false; } @@ -1179,7 +1185,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, if (!handled) { // Try the keyboard handler - short translated = KeyboardTranslator.translate(event.getKeyCode()); + short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId()); if (translated == 0) { return false; } @@ -1922,7 +1928,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, @Override public void keyboardEvent(boolean buttonDown, short keyCode) { - short keyMap = KeyboardTranslator.translate(keyCode); + short keyMap = keyboardTranslator.translate(keyCode, -1); if (keyMap != 0) { // handleSpecialKeys() takes the Android keycode if (handleSpecialKeys(keyCode, buttonDown)) { diff --git a/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java b/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java index 0aa7fb96..8ccda4dd 100644 --- a/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java +++ b/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java @@ -1,13 +1,20 @@ package com.limelight.binding.input; +import android.annotation.TargetApi; +import android.hardware.input.InputManager; +import android.os.Build; +import android.util.SparseArray; +import android.view.InputDevice; import android.view.KeyEvent; +import java.util.Arrays; + /** * Class to translate a Android key code into the codes GFE is expecting * @author Diego Waxemberg * @author Cameron Gutman */ -public class KeyboardTranslator { +public class KeyboardTranslator implements InputManager.InputDeviceListener { /** * GFE's prefix for every key code @@ -48,6 +55,55 @@ public class KeyboardTranslator { public static final int VK_QUOTE = 222; public static final int VK_PAUSE = 19; + private static class KeyboardMapping { + private final InputDevice device; + private final int[] deviceKeyCodeToQwertyKeyCode; + + @TargetApi(33) + public KeyboardMapping(InputDevice device) { + int maxKeyCode = KeyEvent.getMaxKeyCode(); + + this.device = device; + this.deviceKeyCodeToQwertyKeyCode = new int[maxKeyCode + 1]; + + // Any unmatched keycodes are treated as unknown + Arrays.fill(deviceKeyCodeToQwertyKeyCode, KeyEvent.KEYCODE_UNKNOWN); + + for (int i = 0; i <= maxKeyCode; i++) { + int deviceKeyCode = device.getKeyCodeForKeyLocation(i); + if (deviceKeyCode != KeyEvent.KEYCODE_UNKNOWN) { + deviceKeyCodeToQwertyKeyCode[deviceKeyCode] = i; + } + } + } + + @TargetApi(33) + public int getDeviceKeyCodeForQwertyKeyCode(int qwertyKeyCode) { + return device.getKeyCodeForKeyLocation(qwertyKeyCode); + } + + public int getQwertyKeyCodeForDeviceKeyCode(int deviceKeyCode) { + if (deviceKeyCode > KeyEvent.getMaxKeyCode()) { + return KeyEvent.KEYCODE_UNKNOWN; + } + + return deviceKeyCodeToQwertyKeyCode[deviceKeyCode]; + } + } + + private final SparseArray keyboardMappings = new SparseArray<>(); + + public KeyboardTranslator() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + for (int deviceId : InputDevice.getDeviceIds()) { + InputDevice device = InputDevice.getDevice(deviceId); + if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + keyboardMappings.set(deviceId, new KeyboardMapping(device)); + } + } + } + } + public static boolean needsShift(int keycode) { switch (keycode) { @@ -65,10 +121,24 @@ public class KeyboardTranslator { /** * Translates the given keycode and returns the GFE keycode * @param keycode the code to be translated + * @param deviceId InputDevice.getId() or -1 if unknown * @return a GFE keycode for the given keycode */ - public static short translate(int keycode) { + public short translate(int keycode, int deviceId) { int translated; + + // If a device ID was provided, look up the keyboard mapping + if (deviceId >= 0) { + KeyboardMapping mapping = keyboardMappings.get(deviceId); + if (mapping != null) { + // Try to map this device-specific keycode onto a QWERTY layout. + // GFE assumes incoming keycodes are from a QWERTY keyboard. + int qwertyKeyCode = mapping.getQwertyKeyCodeForDeviceKeyCode(keycode); + if (qwertyKeyCode != KeyEvent.KEYCODE_UNKNOWN) { + keycode = qwertyKeyCode; + } + } + } // This is a poor man's mapping between Android key codes // and Windows VK_* codes. For all defined VK_ codes, see: @@ -294,4 +364,30 @@ public class KeyboardTranslator { return (short) ((KEY_PREFIX << 8) | translated); } + @Override + public void onInputDeviceAdded(int index) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + InputDevice device = InputDevice.getDevice(index); + if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + keyboardMappings.put(index, new KeyboardMapping(device)); + } + } + } + + @Override + public void onInputDeviceRemoved(int index) { + keyboardMappings.remove(index); + } + + @Override + public void onInputDeviceChanged(int index) { + keyboardMappings.remove(index); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + InputDevice device = InputDevice.getDevice(index); + if (device != null && device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + keyboardMappings.set(index, new KeyboardMapping(device)); + } + } + } }