diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 6e7aa0a9..3f58bb19 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -62,6 +62,7 @@ import android.os.IBinder; import android.util.Rational; import android.view.Display; import android.view.InputDevice; +import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -1299,33 +1300,42 @@ public class Game extends Activity implements SurfaceHolder.Callback, handled = controllerHandler.handleButtonDown(event); } + // Try the keyboard handler if it wasn't handled as a game controller if (!handled) { - // Try the keyboard handler - short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId()); - if (translated == 0) { - return false; - } - // Let this method take duplicate key down events if (handleSpecialKeys(event.getKeyCode(), true)) { return true; } - // Eat repeat down events - if (event.getRepeatCount() > 0) { - return true; - } - // Pass through keyboard input if we're not grabbing if (!grabbedInput) { return false; } - byte modifiers = getModifierState(event); - if (KeyboardTranslator.needsShift(event.getKeyCode())) { - modifiers |= KeyboardPacket.MODIFIER_SHIFT; + // We'll send it as a raw key event if we have a key mapping, otherwise we'll send it + // as UTF-8 text (if it's a printable character). + short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId()); + if (translated == 0) { + // Make sure it has a valid Unicode representation and it's not a dead character + // (which we don't support). If those are true, we can send it as UTF-8 text. + // + // NB: We need to be sure this happens before the getRepeatCount() check because + // UTF-8 events don't auto-repeat on the host side. + int unicodeChar = event.getUnicodeChar(); + if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0 && (unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK) != 0) { + conn.sendUtf8Text(""+(char)unicodeChar); + return true; + } + + return false; } - conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, modifiers, + + // Eat repeat down events + if (event.getRepeatCount() > 0) { + return true; + } + + conn.sendKeyboardInput(translated, KeyboardPacket.KEY_DOWN, getModifierState(event), keyboardTranslator.hasNormalizedMapping(event.getKeyCode(), event.getDeviceId()) ? 0 : MoonBridge.SS_KBE_FLAG_NON_NORMALIZED); } @@ -1370,13 +1380,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, handled = controllerHandler.handleButtonUp(event); } + // Try the keyboard handler if it wasn't handled as a game controller if (!handled) { - // Try the keyboard handler - short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId()); - if (translated == 0) { - return false; - } - if (handleSpecialKeys(event.getKeyCode(), false)) { return true; } @@ -1386,17 +1391,43 @@ public class Game extends Activity implements SurfaceHolder.Callback, return false; } - byte modifiers = getModifierState(event); - if (KeyboardTranslator.needsShift(event.getKeyCode())) { - modifiers |= KeyboardPacket.MODIFIER_SHIFT; + short translated = keyboardTranslator.translate(event.getKeyCode(), event.getDeviceId()); + if (translated == 0) { + // If we sent this event as UTF-8 on key down, also report that it was handled + // when we get the key up event for it. + int unicodeChar = event.getUnicodeChar(); + return (unicodeChar & KeyCharacterMap.COMBINING_ACCENT) == 0 && (unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK) != 0; } - conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, modifiers, + + conn.sendKeyboardInput(translated, KeyboardPacket.KEY_UP, getModifierState(event), keyboardTranslator.hasNormalizedMapping(event.getKeyCode(), event.getDeviceId()) ? 0 : MoonBridge.SS_KBE_FLAG_NON_NORMALIZED); } return true; } + @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return handleKeyMultiple(event) || super.onKeyMultiple(keyCode, repeatCount, event); + } + + private boolean handleKeyMultiple(KeyEvent event) { + // We can receive keys from a software keyboard that don't correspond to any existing + // KEYCODE value. Android will give those to us as an ACTION_MULTIPLE KeyEvent. + // + // Despite the fact that the Android docs say this is unused since API level 29, these + // events are still sent as of Android 13 for the above case. + // + // For other cases of ACTION_MULTIPLE, we will not report those as handled so hopefully + // they will be passed to us again as regular singular key events. + if (event.getKeyCode() != KeyEvent.KEYCODE_UNKNOWN || event.getCharacters() == null) { + return false; + } + + conn.sendUtf8Text(event.getCharacters()); + return true; + } + private TouchContext getTouchContext(int actionIndex) { if (actionIndex < touchContextMap.length) { @@ -2270,14 +2301,10 @@ public class Game extends Activity implements SurfaceHolder.Callback, return handleKeyDown(keyEvent); case KeyEvent.ACTION_UP: return handleKeyUp(keyEvent); + case KeyEvent.ACTION_MULTIPLE: + return handleKeyMultiple(keyEvent); default: return false; } } - - @Override - public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { - conn.sendUtf8Text(event.getCharacters()); - return super.onKeyMultiple(keyCode, repeatCount, event); - } } 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 c15eb3dc..b9d47f09 100644 --- a/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java +++ b/app/src/main/java/com/limelight/binding/input/KeyboardTranslator.java @@ -104,20 +104,6 @@ public class KeyboardTranslator implements InputManager.InputDeviceListener { } } - public static boolean needsShift(int keycode) { - switch (keycode) - { - case KeyEvent.KEYCODE_AT: - case KeyEvent.KEYCODE_POUND: - case KeyEvent.KEYCODE_PLUS: - case KeyEvent.KEYCODE_STAR: - return true; - - default: - return false; - } - } - public boolean hasNormalizedMapping(int keycode, int deviceId) { if (deviceId >= 0) { KeyboardMapping mapping = keyboardMappings.get(deviceId); @@ -359,20 +345,7 @@ public class KeyboardTranslator implements InputManager.InputDeviceListener { translated = 0x6E; break; - case KeyEvent.KEYCODE_AT: - translated = 2 + VK_0; - break; - - case KeyEvent.KEYCODE_POUND: - translated = 3 + VK_0; - break; - - case KeyEvent.KEYCODE_STAR: - translated = 8 + VK_0; - break; - default: - System.out.println("No key for "+keycode); return 0; } }