@@ -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