參考資訊:
https://github.com/steward-fu/website/releases/tag/q25
termux-x11_mod_key_commit-6d62752.diff
diff --git a/app/build.gradle b/app/build.gradle
index daef0bc..65c8209 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,7 +26,7 @@ android {
enable true
reset()
- include "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
+ include "arm64-v8a" //"x86", "x86_64", "armeabi-v7a", "arm64-v8a"
universalApk true
}
diff --git a/app/src/main/cpp/lorie/activity.c b/app/src/main/cpp/lorie/activity.c
index 8a51c11..f55b90d 100644
--- a/app/src/main/cpp/lorie/activity.c
+++ b/app/src/main/cpp/lorie/activity.c
@@ -22,6 +22,11 @@
#pragma ide diagnostic ignored "ConstantFunctionResult"
#define log(prio, ...) __android_log_print(ANDROID_LOG_ ## prio, "LorieNative", __VA_ARGS__)
+static struct {
+ jfloat x;
+ jfloat y;
+} mouse;
+
extern volatile int conn_fd; // The only variable from shared with X server code.
static struct {
@@ -299,6 +304,10 @@ static void sendMouseEvent(__unused JNIEnv* env, __unused jobject cls, jfloat x,
if (which_button > 0)
(*env)->CallVoidMethod(env, globalThiz, MainActivity.resetIme);
lorieEvent e = { .mouse = { .t = EVENT_MOUSE, .x = x, .y = y, .detail = which_button, .down = button_down, .relative = relative } };
+
+ mouse.x = x;
+ mouse.y = y;
+ log(DEBUG, "Sending mouse: %d (x:%f, y:%f, down:%d, relative:%d)", which_button, x, y, button_down, relative);
write(conn_fd, &e, sizeof(e));
}
}
@@ -306,6 +315,7 @@ static void sendMouseEvent(__unused JNIEnv* env, __unused jobject cls, jfloat x,
static void sendTouchEvent(__unused JNIEnv* env, __unused jobject cls, jint action, jint id, jint x, jint y) {
if (conn_fd != -1 && action != -1) {
lorieEvent e = { .touch = { .t = EVENT_TOUCH, .type = action, .id = id, .x = x, .y = y } };
+ log(DEBUG, "Sending touch: %d (x:%d, y:%d, id:%d)", action, x, y, id);
write(conn_fd, &e, sizeof(e));
}
}
@@ -327,12 +337,414 @@ static void requestStylusEnabled(__unused JNIEnv *env, __unused jclass clazz, jb
}
}
+static jboolean sendMySpecialKey(jint s, jint code, jboolean key_down)
+{
+ lorieEvent t = { .key = { .t = EVENT_KEY, .key = code + 8, .state = key_down } };
+
+ if (key_down) {
+ t.key.key = s + 8;
+ t.key.state = 1;
+ t.key.t = EVENT_KEY;
+ write(conn_fd, &t, sizeof(t));
+
+ t.key.key = code + 8;
+ write(conn_fd, &t, sizeof(t));
+ }
+ else {
+ t.key.key = code + 8;
+ t.key.state = 0;
+ t.key.t = EVENT_KEY;
+ write(conn_fd, &t, sizeof(t));
+
+ t.key.key = s + 8;
+ t.key.state = 0;
+ write(conn_fd, &t, sizeof(t));
+ }
+
+ return true;
+}
+
static jboolean sendKeyEvent(__unused JNIEnv* env, __unused jobject cls, jint scan_code, jint key_code, jboolean key_down) {
if (conn_fd != -1) {
+ #define MOUSE_MOV_3 3
+ #define MOUSE_MOV_5 5
+ static int alt_press = 0;
+ static int iso3_press = 0;
+ static int ctrl_press = 0;
+ static int shift_press = 0;
+ static int mouse_mode = 0;
+ static int mouse_toggle = 0;
+
int code = (scan_code) ?: android_to_linux_keycode[key_code];
+ lorieEvent em = {
+ .mouse = {
+ .t = EVENT_MOUSE,
+ .x = 0,
+ .y = 0,
+ .detail = 3,
+ .down = !!key_down,
+ .relative = 1
+ }
+ };
log(DEBUG, "Sending key: %d (%d %d %d)", code + 8, scan_code, key_code, key_down);
- lorieEvent e = { .key = { .t = EVENT_KEY, .key = code + 8, .state = key_down } };
- write(conn_fd, &e, sizeof(e));
+
+ switch (code + 8) {
+ case 111: // Up
+ if (mouse_mode) {
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = 0;
+ em.mouse.y = -MOUSE_MOV_5;
+ em.mouse.detail = 0;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 116: // Down
+ if (mouse_mode) {
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = 0;
+ em.mouse.y = MOUSE_MOV_5;
+ em.mouse.detail = 0;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 113: // Left
+ if (mouse_mode) {
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = -MOUSE_MOV_5;
+ em.mouse.y = 0;
+ em.mouse.detail = 0;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 114: // Right
+ if (mouse_mode) {
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = MOUSE_MOV_5;
+ em.mouse.y = 0;
+ em.mouse.detail = 0;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 588: // APPSwitch
+ code = (alt_press || iso3_press) ? KEY_PAGEUP : KEY_UP;
+ if (mouse_mode) {
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = 0;
+ em.mouse.y = -MOUSE_MOV_3;
+ em.mouse.detail = 0;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 166: // Back
+ code = (alt_press || iso3_press) ? KEY_PAGEDOWN : KEY_DOWN;
+ if (mouse_mode) {
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = 0;
+ em.mouse.y = MOUSE_MOV_3;
+ em.mouse.detail = 0;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 239: // Call
+ code = (alt_press || iso3_press) ? KEY_HOME : KEY_LEFT;
+ if (mouse_mode) {
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = -MOUSE_MOV_3;
+ em.mouse.y = 0;
+ em.mouse.detail = 0;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 180: // EndCall
+ code = (alt_press || iso3_press) ? KEY_END : KEY_RIGHT;
+ if (mouse_mode) {
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = MOUSE_MOV_3;
+ em.mouse.y = 0;
+ em.mouse.detail = 0;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 64: // ALT
+ alt_press = !!key_down;
+
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTALT, KEY_TAB, key_down);
+ }
+ return true;
+ case 50: // LSHIFT
+ shift_press = !!key_down;
+ code = KEY_LEFTSHIFT;
+ break;
+ case 108: // SYM
+ ctrl_press = !!key_down;
+ code = KEY_RIGHTCTRL;
+ break;
+ case 62: // RSHIFT
+ iso3_press = !!key_down;
+ return true;
+ case 24: // Q
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_3, key_down);
+ }
+ break;
+ case 25: // W
+ if (alt_press) {
+ code = KEY_1;
+ }
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_BACKSLASH, key_down);
+ }
+ break;
+ case 26: // E
+ if (alt_press) {
+ code = KEY_2;
+ }
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_5, key_down);
+ }
+ break;
+ case 27: // R
+ if (alt_press) {
+ code = KEY_3;
+ }
+ if (iso3_press) {
+ code = KEY_BACKSLASH;
+ }
+ break;
+ case 28: // T
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_9, key_down);
+ }
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_LEFTBRACE, key_down);
+ }
+ break;
+ case 29: // Y
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_0, key_down);
+ }
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_RIGHTBRACE, key_down);
+ }
+ break;
+ case 30: // U
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_MINUS, key_down);
+ }
+ if (iso3_press) {
+ code = KEY_EQUAL;
+ }
+ break;
+ case 31: // I
+ if (alt_press) {
+ code = KEY_MINUS;
+ }
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_GRAVE, key_down);
+ }
+ break;
+ case 32: // O
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_EQUAL, key_down);
+ }
+ break;
+ case 33: // P
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_2, key_down);
+ }
+ break;
+ case 38: // A
+ if (alt_press || iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_8, key_down);
+ }
+ break;
+ case 39: // S
+ if (alt_press) {
+ code = KEY_4;
+ }
+ if (iso3_press) {
+ mouse_toggle = 0;
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = 0;
+ em.mouse.y = 0;
+ em.mouse.detail = 1;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 40: // D
+ if (alt_press) {
+ code = KEY_5;
+ }
+ if (iso3_press && key_down) {
+ mouse_toggle ^= 1;
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = 0;
+ em.mouse.y = 0;
+ em.mouse.detail = 1;
+ em.mouse.down = !!mouse_toggle;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 41: // F
+ if (alt_press) {
+ code = KEY_6;
+ }
+ if (iso3_press) {
+ mouse_toggle = 0;
+ em.mouse.t = EVENT_MOUSE;
+ em.mouse.x = 0;
+ em.mouse.y = 0;
+ em.mouse.detail = 3;
+ em.mouse.down = !!key_down;
+ em.mouse.relative = 1;
+ write(conn_fd, &em, sizeof(em));
+ return true;
+ }
+ break;
+ case 42: // G
+ if (alt_press) {
+ code = KEY_SLASH;
+ }
+ if (iso3_press) {
+ code = KEY_LEFTBRACE;
+ }
+ break;
+ case 43: // H
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_SEMICOLON, key_down);
+ }
+ if (iso3_press) {
+ code = KEY_RIGHTBRACE;
+ }
+ break;
+ case 44: // J
+ if (alt_press) {
+ code = KEY_SEMICOLON;
+ }
+ break;
+ case 45: // K
+ if (alt_press) {
+ code = KEY_APOSTROPHE;
+ }
+ if (iso3_press) {
+ code = KEY_GRAVE;
+ }
+ break;
+ case 46: // L
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_APOSTROPHE, key_down);
+ }
+ break;
+ case 22: // BACKSPACE
+ if (alt_press) {
+ code = KEY_TAB;
+ }
+ break;
+ case 52: // Z
+ if (alt_press || iso3_press) {
+ code = KEY_7;
+ }
+ break;
+ case 53: // X
+ if (alt_press) {
+ code = KEY_8;
+ }
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_6, key_down);
+ }
+ break;
+ case 54: // C
+ if (alt_press) {
+ code = KEY_9;
+ }
+ if (iso3_press) {
+ code = KEY_F9;
+ }
+ break;
+ case 55: // V
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_SLASH, key_down);
+ }
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_COMMA, key_down);
+ }
+ break;
+ case 56: // B
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_1, key_down);
+ }
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_DOT, key_down);
+ }
+ break;
+ case 57: // N
+ if (alt_press) {
+ code = KEY_COMMA;
+ }
+ break;
+ case 58: // M
+ if (alt_press) {
+ code = KEY_DOT;
+ }
+ break;
+ case 49: // $
+ if (alt_press) {
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_7, key_down);
+ }
+
+ return sendMySpecialKey(KEY_LEFTSHIFT, KEY_4, key_down);
+ case 36: // Enter
+ if (alt_press) {
+ code = KEY_ESC;
+ }
+ break;
+ case 19: // 0
+ code = KEY_0;
+ if (iso3_press && key_down) {
+ mouse_mode ^= 1;
+ }
+ break;
+ case 65: // Space
+ code = KEY_SPACE;
+
+ if (iso3_press) {
+ return sendMySpecialKey(KEY_LEFTALT, KEY_SPACE, key_down);
+ }
+ break;
+ }
+
+ lorieEvent ek = { .key = { .t = EVENT_KEY, .key = code + 8, .state = key_down } };
+ write(conn_fd, &ek, sizeof(ek));
}
return true;
編譯步驟:
$ cd $ wget https://download.oracle.com/java/24/latest/jdk-24_linux-x64_bin.tar.gz $ tar xvf jdk-24_linux-x64_bin.tar.gz -C ~/ $ export JAVA_HOME=~/jdk-24.0.1 $ export CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar $ export PATH=$JAVA_HOME/bin:$PATH $ cd $ wget https://dl.google.com/dl/android/studio/ide-zips/2025.2.2.8/android-studio-2025.2.2.8-linux.tar.gz $ tar xvf android-studio-2025.2.2.8-linux.tar.gz $ cd android-studio $ wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip $ unzip commandlinetools-linux-11076708_latest.zip $ mv cmdline-tools/ latest $ mkdir cmdline-tools $ mv latest/ cmdline-tools/ $ yes | ./cmdline-tools/latest/bin/sdkmanager --licenses $ export ANDROID_HOME=`pwd`/android-studio/ $ cd $ git clone --recurse-submodules https://github.com/termux/termux-x11 $ cd termux-x11 $ git checkout 6d62752 $ wget https://github.com/steward-fu/website/releases/download/q25/termux-x11_mod_key_commit-6d62752.diff $ git apply termux-x11_mod_key_commit-6d62752.diff $ ./gradlew build
P.S. 編譯後,安裝app-arm64-v8a-debug.apk即可
安裝後,修改如下設定:
Settings => Accessibility => Termux:X11 KeyInterceptor => On Termux-X11 => Keyboard => Enforce char based input => On Termux-X11 => Keyboard => Prefer scancodes when possible => On
| Q25按鍵 | Debian按鍵 | +MYALT | +MYSHIFT |
|---|---|---|---|
| Call | LEFT | HOME | HOME/Mouse (Modes) |
| AppSwitch | UP | PAGEUP | PAGEUP/Mouse (Modes) |
| Back | DOWN | PAGEDOWN | PAGEDOWN/Mouse (Modes) |
| EndCall | RIGHT | END | END/Mouse (Modes) |
| Trackball | Keyboard | Mouse/Keyboard (Modes) | |
| Q | q | # | |
| W | w | 1 | | |
| E | e | 2 | % |
| R | r | 3 | \ |
| T | t | ( | { |
| Y | y | ) | } |
| U | u | _ | = |
| I | i | - | ~ |
| O | o | + | |
| P | p | @ | |
| A | a | * | * |
| S | s | 4 | Mouse Left Button |
| D | d | 5 | Mouse Left Button (Toggle) |
| F | f | 6 | Mouse Right Button |
| G | g | / | [ |
| H | h | : | ] |
| J | j | ; | |
| K | k | ' | ` |
| L | l | " | |
| Backspace | Backspace | Tab | |
| alt | MYALT | Alt+Tab | |
| Z | z | 7 | |
| X | x | 8 | |
| C | c | 9 | |
| V | v | ? | |
| B | b | ! | |
| N | n | , | |
| M | m | . | |
| $ | $ | & | |
| Enter | Enter | Escape | |
| Shift | Shift | ||
| 0 | 0 | Mouse/Keyboard Modes | |
| Space | Space | Alt+Space | |
| sym | Ctrl | ||
| Shift | MYSHIFT |