Skip to content

Commit 994df1c

Browse files
Fixed|Added: Fix extra-keys shift key not uppercasing for all soft keyboards and added docs for keyboard key characters mapping
1 parent 63504f0 commit 994df1c

File tree

1 file changed

+106
-3
lines changed

1 file changed

+106
-3
lines changed

terminal-view/src/main/java/com/termux/view/TerminalView.java

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
271271
// Some keyboards seems do not reset the internal state on TYPE_NULL.
272272
// Affects mostly Samsung stock keyboards.
273273
// https://github.com/termux/termux-app/issues/686
274+
// However, this is not a valid value as per AOSP since `InputType.TYPE_CLASS_*` is
275+
// not set and it logs a warning:
276+
// W/InputAttributes: Unexpected input class: inputType=0x00080090 imeOptions=0x02000000
277+
// https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/InputAttributes.java;l=79
274278
outAttrs.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
275279
} else {
276280
// Using InputType.NULL is the most correct input type and avoids issues with other hacks.
@@ -346,6 +350,10 @@ void sendTextToTerminal(CharSequence text) {
346350
codePoint = firstChar;
347351
}
348352

353+
// Check onKeyDown() for details.
354+
if (mClient.readShiftKey())
355+
codePoint = Character.toUpperCase(codePoint);
356+
349357
boolean ctrlHeld = false;
350358
if (codePoint <= 31 && codePoint != 27) {
351359
if (codePoint == '\n') {
@@ -576,6 +584,102 @@ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
576584
return super.onKeyPreIme(keyCode, event);
577585
}
578586

587+
/**
588+
* Key presses in software keyboards will generally NOT trigger this listener, although some
589+
* may elect to do so in some situations. Do not rely on this to catch software key presses.
590+
* Gboard calls this when shouldEnforceCharBasedInput() is disabled (InputType.TYPE_NULL) instead
591+
* of calling commitText(), with deviceId=-1. However, Hacker's Keyboard, OpenBoard, LG Keyboard
592+
* call commitText().
593+
*
594+
* This function may also be called directly without android calling it, like by
595+
* `TerminalExtraKeys` which generates a KeyEvent manually which uses {@link KeyCharacterMap#VIRTUAL_KEYBOARD}
596+
* as the device (deviceId=-1), as does Gboard. That would normally use mappings defined in
597+
* `/system/usr/keychars/Virtual.kcm`. You can run `dumpsys input` to find the `KeyCharacterMapFile`
598+
* used by virtual keyboard or hardware keyboard. Note that virtual keyboard device is not the
599+
* same as software keyboard, like Gboard, etc. Its a fake device used for generating events and
600+
* for testing.
601+
*
602+
* We handle shift key in `commitText()` to convert codepoint to uppercase case there with a
603+
* call to {@link Character#toUpperCase(int)}, but here we instead rely on getUnicodeChar() for
604+
* conversion of keyCode, for both hardware keyboard shift key (via effectiveMetaState) and
605+
* `mClient.readShiftKey()`, based on value in kcm files.
606+
* This may result in different behaviour depending on keyboard and android kcm files set for the
607+
* InputDevice for the event passed to this function. This will likely be an issue for non-english
608+
* languages since `Virtual.kcm` in english only by default or at least in AOSP. For both hardware
609+
* shift key (via effectiveMetaState) and `mClient.readShiftKey()`, `getUnicodeChar()` is used
610+
* for shift specific behaviour which usually is to uppercase.
611+
*
612+
* For fn key on hardware keyboard, android checks kcm files for hardware keyboards, which is
613+
* `Generic.kcm` by default, unless a vendor specific one is defined. The event passed will have
614+
* {@link KeyEvent#META_FUNCTION_ON} set. If the kcm file only defines a single character or unicode
615+
* code point `\\uxxxx`, then only one event is passed with that value. However, if kcm defines
616+
* a `fallback` key for fn or others, like `key DPAD_UP { ... fn: fallback PAGE_UP }`, then
617+
* android will first pass an event with original key `DPAD_UP` and {@link KeyEvent#META_FUNCTION_ON}
618+
* set. But this function will not consume it and android will pass another event with `PAGE_UP`
619+
* and {@link KeyEvent#META_FUNCTION_ON} not set, which will be consumed.
620+
*
621+
* Now there are some other issues as well, firstly ctrl and alt flags are not passed to
622+
* `getUnicodeChar()`, so modified key values in kcm are not used. Secondly, if the kcm file
623+
* for other modifiers like shift or fn define a non-alphabet, like { fn: '\u0015' } to act as
624+
* DPAD_LEFT, the `getUnicodeChar()` will correctly return `21` as the code point but action will
625+
* not happen because the `handleKeyCode()` function that transforms DPAD_LEFT to `\033[D`
626+
* escape sequence for the terminal to perform the left action would not be called since its
627+
* called before `getUnicodeChar()` and terminal will instead get `21 0x15 Negative Acknowledgement`.
628+
* The solution to such issues is calling `getUnicodeChar()` before the call to `handleKeyCode()`
629+
* if user has defined a custom kcm file, like done in POC mentioned in #2237. Note that
630+
* Hacker's Keyboard calls `commitText()` so don't test fn/shift with it for this function.
631+
* https://github.com/termux/termux-app/pull/2237
632+
* https://github.com/agnostic-apollo/termux-app/blob/terminal-code-point-custom-mapping/terminal-view/src/main/java/com/termux/view/TerminalView.java
633+
*
634+
* Key Character Map (kcm) and Key Layout (kl) files info:
635+
* https://source.android.com/devices/input/key-character-map-files
636+
* https://source.android.com/devices/input/key-layout-files
637+
* https://source.android.com/devices/input/keyboard-devices
638+
* AOSP kcm and kl files:
639+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/data/keyboards
640+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/packages/InputDevices/res/raw
641+
*
642+
* KeyCodes:
643+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/view/KeyEvent.java
644+
* https://cs.android.com/android/platform/superproject/+/master:frameworks/native/include/android/keycodes.h
645+
*
646+
* `dumpsys input`:
647+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/services/inputflinger/reader/EventHub.cpp;l=1917
648+
*
649+
* Loading of keymap:
650+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/services/inputflinger/reader/EventHub.cpp;l=1644
651+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/Keyboard.cpp;l=41
652+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/InputDevice.cpp
653+
* OVERLAY keymaps for hardware keyboards may be combined as well:
654+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=165
655+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=831
656+
*
657+
* Parse kcm file:
658+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=727
659+
* Parse key value:
660+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=981
661+
*
662+
* `KeyEvent.getUnicodeChar()`
663+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/view/KeyEvent.java;l=2716
664+
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/KeyCharacterMap.java;l=368
665+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/jni/android_view_KeyCharacterMap.cpp;l=117
666+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=231
667+
*
668+
* Keyboard layouts advertised by applications, like for hardware keyboards via #ACTION_QUERY_KEYBOARD_LAYOUTS
669+
* Config is stored in `/data/system/input-manager-state.xml`
670+
* https://github.com/ris58h/custom-keyboard-layout
671+
* Loading from apps:
672+
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=1221
673+
* Set:
674+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/hardware/input/InputManager.java;l=89
675+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/hardware/input/InputManager.java;l=543
676+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/Settings/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java;l=167
677+
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=1385
678+
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/PersistentDataStore.java
679+
* Get overlay keyboard layout
680+
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=2158
681+
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp;l=616
682+
*/
579683
@Override
580684
public boolean onKeyDown(int keyCode, KeyEvent event) {
581685
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
@@ -599,7 +703,6 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
599703
final boolean controlDown = event.isCtrlPressed() || mClient.readControlKey();
600704
final boolean leftAltDown = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0 || mClient.readAltKey();
601705
final boolean shiftDown = event.isShiftPressed() || mClient.readShiftKey();
602-
final boolean fnDown = event.isFunctionPressed() || mClient.readFnKey();
603706
final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
604707

605708
int keyMod = 0;
@@ -608,7 +711,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
608711
if (shiftDown) keyMod |= KeyHandler.KEYMOD_SHIFT;
609712
if (event.isNumLockOn()) keyMod |= KeyHandler.KEYMOD_NUM_LOCK;
610713
// https://github.com/termux/termux-app/issues/731
611-
if (!fnDown && handleKeyCode(keyCode, keyMod)) {
714+
if (!event.isFunctionPressed() && handleKeyCode(keyCode, keyMod)) {
612715
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) mClient.logInfo(LOG_TAG, "handleKeyCode() took key event");
613716
return true;
614717
}
@@ -624,7 +727,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
624727
int effectiveMetaState = event.getMetaState() & ~bitsToClear;
625728

626729
if (shiftDown) effectiveMetaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
627-
if (fnDown) effectiveMetaState |= KeyEvent.META_FUNCTION_ON;
730+
if (mClient.readFnKey()) effectiveMetaState |= KeyEvent.META_FUNCTION_ON;
628731

629732
int result = event.getUnicodeChar(effectiveMetaState);
630733
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)

0 commit comments

Comments
 (0)