From 748fb3f001138a414d1336b87861781ad31ed35c Mon Sep 17 00:00:00 2001 From: Twaik Yont Date: Tue, 4 Jun 2024 13:55:05 +0300 Subject: [PATCH] Experimental full stylus event support Fixes extra key bar behaviour on some devices Implementing dialog with nested preferences for Preferences activity --- app/src/main/cpp/lorie/InitInput.c | 106 +++++++++++-- app/src/main/cpp/lorie/android.c | 129 +++++++++++++--- app/src/main/cpp/lorie/dri3.c | 6 +- app/src/main/cpp/lorie/lorie.h | 2 +- .../termux/extrakeys/ExtraKeysView.java | 10 +- .../java/com/termux/x11/LoriePreferences.java | 83 ++++++++-- .../main/java/com/termux/x11/LorieView.java | 5 + .../java/com/termux/x11/MainActivity.java | 87 +++++------ .../termux/x11/input/InputEventSender.java | 7 + .../java/com/termux/x11/input/InputStub.java | 2 + .../termux/x11/input/TouchInputHandler.java | 142 +++++++++++++++++- .../termux/x11/utils/TermuxX11ExtraKeys.java | 14 +- .../termux/x11/utils/X11ToolbarViewPager.java | 7 +- app/src/main/res/layout/preference.xml | 113 ++++++++++++++ app/src/main/res/values/styles.xml | 39 ++++- app/src/main/res/xml/preferences.xml | 78 ++++++---- 16 files changed, 682 insertions(+), 148 deletions(-) create mode 100644 app/src/main/res/layout/preference.xml diff --git a/app/src/main/cpp/lorie/InitInput.c b/app/src/main/cpp/lorie/InitInput.c index 6b3254860..6b509ff8b 100644 --- a/app/src/main/cpp/lorie/InitInput.c +++ b/app/src/main/cpp/lorie/InitInput.c @@ -31,15 +31,17 @@ from The Open Group. #include "scrnintstr.h" #include "inputstr.h" #include +#include #include "mipointer.h" #include "xkbsrv.h" #include "xserver-properties.h" #include "exevents.h" #include "renderer.h" -#define unused __attribute__((unused)) +#define XI_PEN "TERMUX-X11 PEN" +#define XI_ERASER "TERMUX-X11 ERASER" -unused DeviceIntPtr lorieMouse, lorieTouch, lorieKeyboard; +__unused DeviceIntPtr lorieMouse, lorieTouch, lorieKeyboard, loriePen, lorieEraser; void ProcessInputEvents(void) { @@ -47,7 +49,7 @@ ProcessInputEvents(void) { } void -DDXRingBell(unused int volume, unused int pitch, unused int duration) {} +DDXRingBell(__unused int volume, __unused int pitch, __unused int duration) {} static int lorieKeybdProc(DeviceIntPtr pDevice, int onoff) { @@ -72,8 +74,7 @@ lorieKeybdProc(DeviceIntPtr pDevice, int onoff) { } static Bool -lorieInitPointerButtons(DeviceIntPtr device) -{ +lorieInitPointerButtons(DeviceIntPtr device) { #define NBUTTONS 10 BYTE map[NBUTTONS + 1]; int i; @@ -98,9 +99,8 @@ lorieInitPointerButtons(DeviceIntPtr device) #undef NBUTTONS } -unused static int -lorieMouseProc(DeviceIntPtr device, int what) -{ +__unused static int +lorieMouseProc(DeviceIntPtr device, int what) { #define NAXES 4 Atom axes_labels[NAXES] = { 0 }; @@ -142,8 +142,7 @@ lorieMouseProc(DeviceIntPtr device, int what) } static int -lorieTouchProc(DeviceIntPtr device, int what) -{ +lorieTouchProc(DeviceIntPtr device, int what) { #define NTOUCHPOINTS 20 #define NBUTTONS 1 #define NAXES 2 @@ -185,8 +184,90 @@ lorieTouchProc(DeviceIntPtr device, int what) #undef NTOUCHPOINTS } +static int +lorieStylusProc(DeviceIntPtr device, int what) { +#define NBUTTONS 3 +#define NAXES 6 + Atom btn_labels[NBUTTONS] = { 0 }; + Atom axes_labels[NAXES] = { 0 }; + BYTE map[NBUTTONS + 1] = { 0 }; + int i; + + switch (what) { + case DEVICE_INIT: + device->public.on = FALSE; + + for (i = 1; i <= NBUTTONS; i++) + map[i] = i; + + axes_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_X); + axes_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_Y); + axes_labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_PRESSURE); + axes_labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_TILT_X); + axes_labels[4] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_TILT_Y); + axes_labels[5] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_WHEEL); + + /* Valuators - match the xf86-input-wacom ranges */ + if (!InitValuatorClassDeviceStruct(device, NAXES, axes_labels, GetMotionHistorySize(), Absolute) + || !InitValuatorAxisStruct(device, 0, axes_labels[0], 0, 0x3FFFF, 10000, 0, 10000, Absolute) + || !InitValuatorAxisStruct(device, 1, axes_labels[1], 0, 0x3FFFF, 10000, 0, 10000, Absolute) + || !InitValuatorAxisStruct(device, 2, axes_labels[2], 0, 0xFFFF, 1, 0, 1, Absolute) // pressure + || !InitValuatorAxisStruct(device, 3, axes_labels[3], -64, 63, 57, 0, 57, Absolute) // tilt x + || !InitValuatorAxisStruct(device, 4, axes_labels[4], -64, 63, 57, 0, 57, Absolute) // tilt y + || !InitValuatorAxisStruct(device, 5, axes_labels[5], -900, 899, 1, 0, 1, Absolute) // abs wheel (airbrush) or rotation (artpen) + || !InitPtrFeedbackClassDeviceStruct(device, (PtrCtrlProcPtr) NoopDDA) + || !InitButtonClassDeviceStruct(device, NBUTTONS, btn_labels, map)) + return BadValue; + + return Success; + + case DEVICE_ON: + device->public.on = TRUE; + return Success; + + case DEVICE_OFF: + case DEVICE_CLOSE: + device->public.on = FALSE; + return Success; + } + + return BadMatch; +#undef NAXES +#undef NBUTTONS +} + +void +lorieSetStylusEnabled(Bool enabled) { + __android_log_print(ANDROID_LOG_DEBUG, "LorieNative", "Requested stylus: %d, current loriePen %p, current lorieEraser %p\n", enabled, loriePen, lorieEraser); + if (enabled) { + if (loriePen == NULL) { + loriePen = AddInputDevice(serverClient, lorieStylusProc, TRUE); + AssignTypeAndName(loriePen, MakeAtom(XI_PEN, sizeof(XI_PEN) - 1, TRUE), "Lorie pen"); + ActivateDevice(loriePen, FALSE); + EnableDevice(loriePen, TRUE); + AttachDevice(NULL, loriePen, inputInfo.pointer); + } + if (lorieEraser == NULL) { + lorieEraser = AddInputDevice(serverClient, lorieStylusProc, TRUE); + AssignTypeAndName(lorieEraser, MakeAtom(XI_ERASER, sizeof(XI_ERASER) - 1, TRUE), "Lorie eraser"); + ActivateDevice(lorieEraser, FALSE); + EnableDevice(lorieEraser, TRUE); + AttachDevice(NULL, lorieEraser, inputInfo.pointer); + } + } else { + if (loriePen != NULL) { + RemoveDevice(loriePen, TRUE); + loriePen = NULL; + } + if (lorieEraser != NULL) { + RemoveDevice(lorieEraser, TRUE); + lorieEraser = NULL; + } + } +} + void -InitInput(unused int argc, unused char *argv[]) { +InitInput(__unused int argc, __unused char *argv[]) { lorieMouse = AddInputDevice(serverClient, lorieMouseProc, TRUE); lorieTouch = AddInputDevice(serverClient, lorieTouchProc, TRUE); lorieKeyboard = AddInputDevice(serverClient, lorieKeybdProc, TRUE); @@ -197,7 +278,6 @@ InitInput(unused int argc, unused char *argv[]) { } void -CloseInput(void) -{ +CloseInput(void) { mieqFini(); } diff --git a/app/src/main/cpp/lorie/android.c b/app/src/main/cpp/lorie/android.c index f6aeaee8f..2e4527b39 100644 --- a/app/src/main/cpp/lorie/android.c +++ b/app/src/main/cpp/lorie/android.c @@ -29,7 +29,7 @@ static int argc = 0; static char** argv = NULL; static int conn_fd = -1; extern char *__progname; // NOLINT(bugprone-reserved-identifier) -extern DeviceIntPtr lorieMouse, lorieTouch, lorieKeyboard; +extern DeviceIntPtr lorieMouse, lorieTouch, lorieKeyboard, loriePen, lorieEraser; extern ScreenPtr pScreenPtr; extern int ucs2keysym(long ucs); void lorieKeysymKeyboardEvent(KeySym keysym, int down); @@ -42,6 +42,8 @@ typedef enum { EVENT_TOUCH, EVENT_MOUSE, EVENT_KEY, + EVENT_STYLUS, + EVENT_STYLUS_ENABLE, EVENT_UNICODE, EVENT_CLIPBOARD_ENABLE, EVENT_CLIPBOARD_ANNOUNCE, @@ -68,6 +70,17 @@ typedef union { uint16_t key; uint8_t state; } key; + struct { + uint8_t t; + float x, y; + uint16_t pressure; + int8_t tilt_x, tilt_y; + int16_t orientation; + uint8_t buttons, eraser; + } stylus; + struct { + uint8_t t, enable; + } stylusEnable; struct { uint8_t t; uint32_t code; @@ -93,7 +106,7 @@ static struct { jmethodID toString; } CharBuffer = {0}; -static void* startServer(unused void* cookie) { +static void* startServer(__unused void* cookie) { lorieSetVM((JavaVM*) cookie); char* envp[] = { NULL }; exit(dix_main(argc, (char**) argv, envp)); @@ -127,7 +140,7 @@ static jclass FindMethodOrDie(JNIEnv *env, jclass clazz, const char* name, const } JNIEXPORT jboolean JNICALL -Java_com_termux_x11_CmdEntryPoint_start(JNIEnv *env, unused jclass cls, jobjectArray args) { +Java_com_termux_x11_CmdEntryPoint_start(JNIEnv *env, __unused jclass cls, jobjectArray args) { pthread_t t; JavaVM* vm = NULL; // execv's argv array is a bit incompatible with Java's String[], so we do some converting here... @@ -250,7 +263,7 @@ Java_com_termux_x11_CmdEntryPoint_start(JNIEnv *env, unused jclass cls, jobjectA } JNIEXPORT void JNICALL -Java_com_termux_x11_CmdEntryPoint_windowChanged(JNIEnv *env, unused jobject cls, jobject surface, jstring jname) { +Java_com_termux_x11_CmdEntryPoint_windowChanged(JNIEnv *env, __unused jobject cls, jobject surface, jstring jname) { const char *name = !jname ? NULL : (*env)->GetStringUTFChars(env, jname, JNI_FALSE); QueueWorkProc(lorieChangeScreenName, NULL, name ? strndup(name, 1024) : strdup("screen")); if (name) @@ -259,7 +272,7 @@ Java_com_termux_x11_CmdEntryPoint_windowChanged(JNIEnv *env, unused jobject cls, QueueWorkProc(lorieChangeWindow, NULL, surface ? (*env)->NewGlobalRef(env, surface) : NULL); } -static Bool sendConfigureNotify(unused ClientPtr pClient, void *closure) { +static Bool sendConfigureNotify(__unused ClientPtr pClient, void *closure) { // This must be done only on X server thread. lorieEvent* e = closure; __android_log_print(ANDROID_LOG_ERROR, "tx11-request", "window changed: %d %d", e->screenSize.width, e->screenSize.height); @@ -268,19 +281,20 @@ static Bool sendConfigureNotify(unused ClientPtr pClient, void *closure) { return TRUE; } -static Bool handleClipboardAnnounce(unused ClientPtr pClient, __unused void *closure) { +static Bool handleClipboardAnnounce(__unused ClientPtr pClient, __unused void *closure) { // This must be done only on X server thread. lorieHandleClipboardAnnounce(); return TRUE; } -static Bool handleClipboardData(unused ClientPtr pClient, void *closure) { +static Bool handleClipboardData(__unused ClientPtr pClient, void *closure) { // This must be done only on X server thread. lorieHandleClipboardData(closure); return TRUE; } void handleLorieEvents(int fd, __unused int ready, __unused void *ignored) { + DrawablePtr screenDrawable = &pScreenPtr->GetScreenPixmap(pScreenPtr)->drawable; ValuatorMask mask; lorieEvent e = {0}; valuator_mask_zero(&mask); @@ -306,8 +320,11 @@ void handleLorieEvents(int fd, __unused int ready, __unused void *ignored) { double x, y; DDXTouchPointInfoPtr touch = TouchFindByDDXID(lorieTouch, e.touch.id, FALSE); - x = (float) e.touch.x * 0xFFFF / (float) pScreenPtr->GetScreenPixmap(pScreenPtr)->drawable.width; - y = (float) e.touch.y * 0xFFFF / (float) pScreenPtr->GetScreenPixmap(pScreenPtr)->drawable.height; + x = (float) e.touch.x * 0xFFFF / (float) screenDrawable->width; + y = (float) e.touch.y * 0xFFFF / (float) screenDrawable->height; + + x = max(min(x, screenDrawable->width), 0); + y = max(min(y, screenDrawable->height), 0); // Avoid duplicating events if (touch && touch->active) { @@ -326,17 +343,61 @@ void handleLorieEvents(int fd, __unused int ready, __unused void *ignored) { if (e.touch.type == XI_TouchEnd && (!touch || !touch->active)) break; - __android_log_print(ANDROID_LOG_ERROR, "tx11-request", "touch event: %d %d %d %d", e.touch.type, e.touch.id, e.touch.x, e.touch.y); valuator_mask_set_double(&mask, 0, x); valuator_mask_set_double(&mask, 1, y); QueueTouchEvents(lorieTouch, e.touch.type, e.touch.id, 0, &mask); break; } + case EVENT_STYLUS: { + int button; + static int buttons_prev = 0; + uint32_t released, pressed, diff; + DeviceIntPtr device = e.stylus.eraser ? lorieEraser : loriePen; + if (!device) { + __android_log_print(ANDROID_LOG_DEBUG, "LorieNative", "got stylus event but device is not requested\n"); + break; + } + __android_log_print(ANDROID_LOG_DEBUG, "LorieNative", "got stylus event %f %f %d %d %d %d %p\n", e.stylus.x, e.stylus.y, e.stylus.pressure, e.stylus.tilt_x, e.stylus.tilt_y, e.stylus.orientation, device); + + valuator_mask_set_double(&mask, 0, max(min(e.stylus.x, screenDrawable->width), 0)); + valuator_mask_set_double(&mask, 1, max(min(e.stylus.y, screenDrawable->height), 0)); + valuator_mask_set_double(&mask, 2, e.stylus.pressure); + valuator_mask_set_double(&mask, 3, e.stylus.tilt_x); + valuator_mask_set_double(&mask, 4, e.stylus.tilt_y); + valuator_mask_set_double(&mask, 5, e.stylus.orientation); + QueuePointerEvents(device, MotionNotify, 0, POINTER_ABSOLUTE | POINTER_DESKTOP, &mask); + + diff = buttons_prev ^ e.stylus.buttons; + released = diff & ~e.stylus.buttons; + pressed = diff & e.stylus.buttons; + + button = 1; + for (int i=0; i<3; i++) { + if (released & 0x1) + QueuePointerEvents(device, ButtonRelease, button, POINTER_RELATIVE, NULL); + if (pressed & 0x1) + QueuePointerEvents(device, ButtonPress, button, POINTER_RELATIVE, NULL); + button++; + released >>= 1; + pressed >>= 1; + } + buttons_prev = e.stylus.buttons; + + break; + } + case EVENT_STYLUS_ENABLE: { + lorieSetStylusEnabled(e.stylusEnable.enable); + break; + } case EVENT_MOUSE: { int flags; switch(e.mouse.detail) { case 0: // BUTTON_UNDEFINED flags = (e.mouse.relative) ? POINTER_RELATIVE | POINTER_ACCELERATE : POINTER_ABSOLUTE | POINTER_SCREEN | POINTER_NORAW; + if (!e.mouse.relative) { + e.mouse.x = max(0, min(e.mouse.x, screenDrawable->width)); + e.mouse.y = max(0, min(e.mouse.y, screenDrawable->height)); + } valuator_mask_set_double(&mask, 0, (double) e.mouse.x); valuator_mask_set_double(&mask, 1, (double) e.mouse.y); QueuePointerEvents(lorieMouse, MotionNotify, 0, flags, &mask); @@ -407,14 +468,14 @@ void lorieRequestClipboard(void) { } } -static Bool addFd(unused ClientPtr pClient, void *closure) { +static Bool addFd(__unused ClientPtr pClient, void *closure) { InputThreadRegisterDev((int) (int64_t) closure, handleLorieEvents, NULL); conn_fd = (int) (int64_t) closure; return TRUE; } JNIEXPORT jobject JNICALL -Java_com_termux_x11_CmdEntryPoint_getXConnection(JNIEnv *env, unused jobject cls) { +Java_com_termux_x11_CmdEntryPoint_getXConnection(JNIEnv *env, __unused jobject cls) { int client[2]; jclass ParcelFileDescriptorClass = (*env)->FindClass(env, "android/os/ParcelFileDescriptor"); jmethodID adoptFd = (*env)->GetStaticMethodID(env, ParcelFileDescriptorClass, "adoptFd", "(I)Landroid/os/ParcelFileDescriptor;"); @@ -435,7 +496,7 @@ void* logcatThread(void *arg) { } JNIEXPORT jobject JNICALL -Java_com_termux_x11_CmdEntryPoint_getLogcatOutput(JNIEnv *env, unused jobject cls) { +Java_com_termux_x11_CmdEntryPoint_getLogcatOutput(JNIEnv *env, __unused jobject cls) { jclass ParcelFileDescriptorClass = (*env)->FindClass(env, "android/os/ParcelFileDescriptor"); jmethodID adoptFd = (*env)->GetStaticMethodID(env, ParcelFileDescriptorClass, "adoptFd", "(I)Landroid/os/ParcelFileDescriptor;"); const char *debug = getenv("TERMUX_X11_DEBUG"); @@ -474,7 +535,7 @@ static inline void checkConnection(JNIEnv* env) { } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_connect(unused JNIEnv* env, unused jobject cls, jint fd) { +Java_com_termux_x11_LorieView_connect(__unused JNIEnv* env, __unused jobject cls, jint fd) { if (!Charset.self) { // Init clipboard-related JNI stuff Charset.self = FindClassOrDie(env, "java/nio/charset/Charset"); @@ -529,7 +590,7 @@ Java_com_termux_x11_LorieView_handleXEvents(JNIEnv *env, jobject thiz) { } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_startLogcat(JNIEnv *env, unused jobject cls, jint fd) { +Java_com_termux_x11_LorieView_startLogcat(JNIEnv *env, __unused jobject cls, jint fd) { log(DEBUG, "Starting logcat with output to given fd"); switch(fork()) { @@ -549,7 +610,7 @@ Java_com_termux_x11_LorieView_startLogcat(JNIEnv *env, unused jobject cls, jint } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_setClipboardSyncEnabled(unused JNIEnv* env, unused jobject cls, jboolean enable, __unused jboolean ignored) { +Java_com_termux_x11_LorieView_setClipboardSyncEnabled(__unused JNIEnv* env, __unused jobject cls, jboolean enable, __unused jboolean ignored) { if (conn_fd != -1) { lorieEvent e = { .clipboardEnable = { .t = EVENT_CLIPBOARD_ENABLE, .enable = enable } }; write(conn_fd, &e, sizeof(e)); @@ -567,7 +628,7 @@ Java_com_termux_x11_LorieView_sendClipboardAnnounce(JNIEnv *env, __unused jobjec } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_sendClipboardEvent(JNIEnv *env, unused jobject thiz, jbyteArray text) { +Java_com_termux_x11_LorieView_sendClipboardEvent(JNIEnv *env, __unused jobject thiz, jbyteArray text) { if (conn_fd != -1 && text) { jsize length = (*env)->GetArrayLength(env, text); jbyte* str = (*env)->GetByteArrayElements(env, text, NULL); @@ -580,7 +641,7 @@ Java_com_termux_x11_LorieView_sendClipboardEvent(JNIEnv *env, unused jobject thi } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_sendWindowChange(unused JNIEnv* env, unused jobject cls, jint width, jint height, jint framerate) { +Java_com_termux_x11_LorieView_sendWindowChange(__unused JNIEnv* env, __unused jobject cls, jint width, jint height, jint framerate) { if (conn_fd != -1) { lorieEvent e = { .screenSize = { .t = EVENT_SCREEN_SIZE, .width = width, .height = height, .framerate = framerate } }; write(conn_fd, &e, sizeof(e)); @@ -589,7 +650,7 @@ Java_com_termux_x11_LorieView_sendWindowChange(unused JNIEnv* env, unused jobjec } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_sendMouseEvent(unused JNIEnv* env, unused jobject cls, jfloat x, jfloat y, jint which_button, jboolean button_down, jboolean relative) { +Java_com_termux_x11_LorieView_sendMouseEvent(__unused JNIEnv* env, __unused jobject cls, jfloat x, jfloat y, jint which_button, jboolean button_down, jboolean relative) { if (conn_fd != -1) { lorieEvent e = { .mouse = { .t = EVENT_MOUSE, .x = x, .y = y, .detail = which_button, .down = button_down, .relative = relative } }; write(conn_fd, &e, sizeof(e)); @@ -598,7 +659,7 @@ Java_com_termux_x11_LorieView_sendMouseEvent(unused JNIEnv* env, unused jobject } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_sendTouchEvent(unused JNIEnv* env, unused jobject cls, jint action, jint id, jint x, jint y) { +Java_com_termux_x11_LorieView_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 } }; write(conn_fd, &e, sizeof(e)); @@ -606,8 +667,28 @@ Java_com_termux_x11_LorieView_sendTouchEvent(unused JNIEnv* env, unused jobject } } +JNIEXPORT void JNICALL +Java_com_termux_x11_LorieView_sendStylusEvent(JNIEnv *env, __unused jobject thiz, jfloat x, jfloat y, + jint pressure, jint tilt_x, jint tilt_y, + jint orientation, jint buttons, jboolean eraser) { + if (conn_fd != -1) { + lorieEvent e = { .stylus = { .t = EVENT_STYLUS, .x = x, .y = y, .pressure = pressure, .tilt_x = tilt_x, .tilt_y = tilt_y, .orientation = orientation, .buttons = buttons, .eraser = eraser } }; + write(conn_fd, &e, sizeof(e)); + checkConnection(env); + } +} + +JNIEXPORT void JNICALL +Java_com_termux_x11_LorieView_requestStylusEnabled(JNIEnv *env, __unused jclass clazz, jboolean enabled) { + if (conn_fd != -1) { + lorieEvent e = { .stylusEnable = { .t = EVENT_STYLUS_ENABLE, .enable = enabled } }; + write(conn_fd, &e, sizeof(e)); + checkConnection(env); + } +} + JNIEXPORT jboolean JNICALL -Java_com_termux_x11_LorieView_sendKeyEvent(unused JNIEnv* env, unused jobject cls, jint scan_code, jint key_code, jboolean key_down) { +Java_com_termux_x11_LorieView_sendKeyEvent(__unused JNIEnv* env, __unused jobject cls, jint scan_code, jint key_code, jboolean key_down) { if (conn_fd != -1) { int code = (scan_code) ?: android_to_linux_keycode[key_code]; log(DEBUG, "Sending key: %d (%d %d %d)", code + 8, scan_code, key_code, key_down); @@ -620,7 +701,7 @@ Java_com_termux_x11_LorieView_sendKeyEvent(unused JNIEnv* env, unused jobject cl } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_sendTextEvent(JNIEnv *env, unused jobject thiz, jbyteArray text) { +Java_com_termux_x11_LorieView_sendTextEvent(JNIEnv *env, __unused jobject thiz, jbyteArray text) { if (conn_fd != -1 && text) { jsize length = (*env)->GetArrayLength(env, text); jbyte *str = (*env)->GetByteArrayElements(env, text, NULL); @@ -655,7 +736,7 @@ Java_com_termux_x11_LorieView_sendTextEvent(JNIEnv *env, unused jobject thiz, jb } JNIEXPORT void JNICALL -Java_com_termux_x11_LorieView_sendUnicodeEvent(JNIEnv *env, unused jobject thiz, jint code) { +Java_com_termux_x11_LorieView_sendUnicodeEvent(JNIEnv *env, __unused jobject thiz, jint code) { if (conn_fd != -1) { log(DEBUG, "Sending unicode event: %lc (U+%X)", code, code); lorieEvent e = { .unicode = { .t = EVENT_UNICODE, .code = code } }; @@ -675,7 +756,7 @@ void exit(int code) { #if 1 // It is needed to redirect stderr to logcat -static void* stderrToLogcatThread(unused void* cookie) { +static void* stderrToLogcatThread(__unused void* cookie) { FILE *fp; int p[2]; size_t len; diff --git a/app/src/main/cpp/lorie/dri3.c b/app/src/main/cpp/lorie/dri3.c index f508528c5..81594d953 100644 --- a/app/src/main/cpp/lorie/dri3.c +++ b/app/src/main/cpp/lorie/dri3.c @@ -431,7 +431,7 @@ lorieDestroyPixmap(PixmapPtr pPixmap) { } static PixmapPtr loriePixmapFromFds(ScreenPtr screen, CARD8 num_fds, const int *fds, CARD16 width, CARD16 height, - const CARD32 *strides, const CARD32 *offsets, CARD8 depth, unused CARD8 bpp, CARD64 modifier) { + const CARD32 *strides, const CARD32 *offsets, CARD8 depth, __unused CARD8 bpp, CARD64 modifier) { const CARD64 AHARDWAREBUFFER_SOCKET_FD = 1255; const CARD64 RAW_MMAPPABLE_FD = 1274; PixmapPtr pixmap = NullPixmap; @@ -535,13 +535,13 @@ static PixmapPtr loriePixmapFromFds(ScreenPtr screen, CARD8 num_fds, const int * return NULL; } -static int lorieGetFormats(unused ScreenPtr screen, CARD32 *num_formats, CARD32 **formats) { +static int lorieGetFormats(__unused ScreenPtr screen, CARD32 *num_formats, CARD32 **formats) { *num_formats = 0; *formats = NULL; return TRUE; } -static int lorieGetModifiers(unused ScreenPtr screen, unused uint32_t format, uint32_t *num_modifiers, uint64_t **modifiers) { +static int lorieGetModifiers(__unused ScreenPtr screen, __unused uint32_t format, uint32_t *num_modifiers, uint64_t **modifiers) { *num_modifiers = 0; *modifiers = NULL; return TRUE; diff --git a/app/src/main/cpp/lorie/lorie.h b/app/src/main/cpp/lorie/lorie.h index 6e3d4c788..865864068 100644 --- a/app/src/main/cpp/lorie/lorie.h +++ b/app/src/main/cpp/lorie/lorie.h @@ -3,7 +3,6 @@ #include #include #include "linux/input-event-codes.h" -#define unused __attribute__((unused)) void lorieSetVM(JavaVM* vm); Bool lorieChangeScreenName(ClientPtr pClient, void *closure); @@ -16,6 +15,7 @@ void lorieRequestClipboard(void); void lorieHandleClipboardAnnounce(void); void lorieHandleClipboardData(const char* data); Bool lorieInitDri3(ScreenPtr pScreen); +void lorieSetStylusEnabled(Bool enabled); static int android_to_linux_keycode[304] = { [ 4 /* ANDROID_KEYCODE_BACK */] = KEY_ESC, diff --git a/app/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeysView.java b/app/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeysView.java index 9fda1b3a7..aae449d1a 100644 --- a/app/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeysView.java +++ b/app/src/main/java/com/termux/shared/termux/extrakeys/ExtraKeysView.java @@ -34,6 +34,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.termux.x11.utils.TermuxX11ExtraKeys; + /** * A {@link View} showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft * keyboards. @@ -280,13 +282,11 @@ public Map getDefaultSpecialButtons(ExtraKeys /** * Reload this instance of {@link ExtraKeysView} with the info passed in {@code extraKeysInfo}. - * - * @param extraKeysInfo The {@link ExtraKeysInfo} that defines the necessary info for the extra keys. - * @param heightPx The height in pixels of the parent surrounding the {@link ExtraKeysView}. It must - * be a single child. */ @SuppressLint("ClickableViewAccessibility") - public void reload(ExtraKeysInfo extraKeysInfo, float heightPx) { + public void reload() { + TermuxX11ExtraKeys.setExtraKeys(); + ExtraKeysInfo extraKeysInfo = TermuxX11ExtraKeys.getExtraKeysInfo(); if (extraKeysInfo == null) return; diff --git a/app/src/main/java/com/termux/x11/LoriePreferences.java b/app/src/main/java/com/termux/x11/LoriePreferences.java index 532ba0896..f54de0821 100644 --- a/app/src/main/java/com/termux/x11/LoriePreferences.java +++ b/app/src/main/java/com/termux/x11/LoriePreferences.java @@ -39,12 +39,17 @@ import android.text.method.LinkMovementMethod; import android.util.Log; +import android.view.InputDevice; +import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; +import androidx.fragment.app.DialogFragment; + import com.termux.x11.utils.KeyInterceptor; import com.termux.x11.utils.SamsungDexUtils; import com.termux.x11.utils.TermuxX11ExtraKeys; @@ -52,13 +57,13 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; - import java.io.IOException; import java.io.StringWriter; import java.io.PrintWriter; import java.util.Arrays; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.PatternSyntaxException; @SuppressWarnings("deprecation") @@ -83,7 +88,7 @@ public void onReceive(Context context, Intent intent) { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - loriePreferenceFragment = new LoriePreferenceFragment(); + loriePreferenceFragment = new LoriePreferenceFragment(null); getSupportFragmentManager().beginTransaction().replace(android.R.id.content, loriePreferenceFragment).commit(); ActionBar actionBar = getSupportActionBar(); @@ -121,6 +126,16 @@ public boolean onOptionsItemSelected(MenuItem item) { } public static class LoriePreferenceFragment extends PreferenceFragmentCompat implements OnPreferenceChangeListener, Preference.OnPreferenceClickListener { + final String preference; + /** @noinspection unused*/ // Used by `androidx.fragment.app.Fragment.instantiate`... + public LoriePreferenceFragment() { + this(null); + } + + public LoriePreferenceFragment(String preference) { + this.preference = preference; + } + @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { SharedPreferences p = getPreferenceManager().getSharedPreferences(); @@ -132,6 +147,23 @@ public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable S } addPreferencesFromResource(R.xml.preferences); + findPreference("showAdditionalKbd").setLayoutResource(R.layout.preference); + + // Hide what user should not see in this instance + PreferenceGroup g = getPreferenceScreen(); + if (preference != null) { + for (int i=0; i < g.getPreferenceCount(); i++) { + if (g.getPreference(i) instanceof PreferenceGroup) { + g.getPreference(i).setVisible(g.getPreference(i).getKey().contentEquals(preference)); + } + } + } else { + for (int i=0; i < g.getPreferenceCount(); i++) { + if (g.getPreference(i) instanceof PreferenceGroup) { + g.getPreference(i).setVisible(!g.getPreference(i).getKey().contentEquals("ekbar")); + } + } + } } @SuppressWarnings("ConstantConditions") @@ -180,6 +212,21 @@ void updatePreferencesLayout() { findPreference("scaleTouchpad").setVisible("1".equals(p.getString("touchMode", "1")) && !"native".equals(p.getString("displayResolutionMode", "native"))); findPreference("showMouseHelper").setEnabled("1".equals(p.getString("touchMode", "1"))); + AtomicBoolean stylusAvailable = new AtomicBoolean(false); + Arrays.stream(InputDevice.getDeviceIds()) + .mapToObj(InputDevice::getDevice) + .filter(Objects::nonNull) + .forEach((device) -> { + //noinspection DataFlowIssue + if (device.supportsSource(InputDevice.SOURCE_STYLUS)) + stylusAvailable.set(true); + }); + + findPreference("showStylusClickOverride").setVisible(stylusAvailable.get()); + findPreference("stylusIsMouse").setVisible(stylusAvailable.get()); + findPreference("stylusButtonContactModifierMode").setEnabled(!p.getBoolean("stylusIsMouse", false)); + findPreference("stylusButtonContactModifierMode").setVisible(stylusAvailable.get()); + boolean requestNotificationPermissionVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(requireContext(), POST_NOTIFICATIONS) == PERMISSION_DENIED; @@ -205,20 +252,25 @@ void setListeners(PreferenceGroup g) { for (int i=0; i < g.getPreferenceCount(); i++) { g.getPreference(i).setOnPreferenceChangeListener(this); g.getPreference(i).setOnPreferenceClickListener(this); - g.getPreference(i).setSingleLineTitle(false); if (g.getPreference(i) instanceof PreferenceGroup) setListeners((PreferenceGroup) g.getPreference(i)); } } - @Override - public boolean onPreferenceClick(@NonNull Preference preference) { - if ("enableAccessibilityService".contentEquals(preference.getKey())) { - Intent intent = new Intent(android.provider.Settings.ACTION_ACCESSIBILITY_SETTINGS); - startActivityForResult(intent, 0); + void setMultilineTitle(PreferenceGroup g) { + for (int i=0; i < g.getPreferenceCount(); i++) { + if (g.getPreference(i) instanceof PreferenceGroup) + setListeners((PreferenceGroup) g.getPreference(i)); + else { + g.getPreference(i).onDependencyChanged(g.getPreference(i), true); + g.getPreference(i).onDependencyChanged(g.getPreference(i), false); + } } + } + @Override + public boolean onPreferenceClick(@NonNull Preference preference) { if ("extra_keys_config".contentEquals(preference.getKey())) { @SuppressLint("InflateParams") View view = getLayoutInflater().inflate(R.layout.extra_keys_config, null, false); @@ -343,7 +395,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { intent.setPackage("com.termux.x11"); requireContext().sendBroadcast(intent); - handler.postAtTime(this::updatePreferencesLayout, 100); + setMultilineTitle(getPreferenceScreen()); return true; } } @@ -616,4 +668,17 @@ else if (context.checkSelfPermission(WRITE_SECURE_SETTINGS) != PERMISSION_GRANTE } static Handler handler = new Handler(); + + public void onClick(View view) { + new NestedPreferenceFragment().show(getSupportFragmentManager(), "ekbar"); + } + + public static class NestedPreferenceFragment extends DialogFragment { + @Nullable @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + getChildFragmentManager().beginTransaction().replace(android.R.id.content, new LoriePreferenceFragment("ekbar")).commit(); + + return null; + } + } } diff --git a/app/src/main/java/com/termux/x11/LorieView.java b/app/src/main/java/com/termux/x11/LorieView.java index d3ec9b278..65be57ea7 100644 --- a/app/src/main/java/com/termux/x11/LorieView.java +++ b/app/src/main/java/com/termux/x11/LorieView.java @@ -24,6 +24,7 @@ import androidx.annotation.NonNull; import com.termux.x11.input.InputStub; +import com.termux.x11.input.TouchInputHandler; import java.nio.charset.StandardCharsets; import java.util.regex.PatternSyntaxException; @@ -272,6 +273,8 @@ public void onWindowFocusChanged(boolean hasFocus) { checkForClipboardChange(); } else clipboard.removePrimaryClipChangedListener(clipboardListener); + + TouchInputHandler.refreshInputDevices(); } static native void connect(int fd); @@ -283,6 +286,8 @@ public void onWindowFocusChanged(boolean hasFocus) { static native void sendWindowChange(int width, int height, int framerate); public native void sendMouseEvent(float x, float y, int whichButton, boolean buttonDown, boolean relative); public native void sendTouchEvent(int action, int id, int x, int y); + public native void sendStylusEvent(float x, float y, int pressure, int tiltX, int tiltY, int orientation, int buttons, boolean eraser); + static public native void requestStylusEnabled(boolean enabled); public native boolean sendKeyEvent(int scanCode, int keyCode, boolean keyDown); public native void sendTextEvent(byte[] text); public native void sendUnicodeEvent(int code); diff --git a/app/src/main/java/com/termux/x11/MainActivity.java b/app/src/main/java/com/termux/x11/MainActivity.java index 10cfbe96a..7174e149d 100644 --- a/app/src/main/java/com/termux/x11/MainActivity.java +++ b/app/src/main/java/com/termux/x11/MainActivity.java @@ -606,64 +606,52 @@ public ViewPager getTerminalToolbarViewPager() { } private void setTerminalToolbarView() { - final ViewPager terminalToolbarViewPager = getTerminalToolbarViewPager(); - - terminalToolbarViewPager.setAdapter(new X11ToolbarViewPager.PageAdapter(this, (v, k, e) -> mInputHandler.sendKeyEvent(e))); - terminalToolbarViewPager.addOnPageChangeListener(new X11ToolbarViewPager.OnPageChangeListener(this, terminalToolbarViewPager)); + final ViewPager pager = getTerminalToolbarViewPager(); + ViewGroup parent = (ViewGroup) pager.getParent(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean enabled = preferences.getBoolean("showAdditionalKbd", true); - boolean showNow = enabled && preferences.getBoolean("additionalKbdVisible", true); + boolean showNow = mClientConnected && + preferences.getBoolean("showAdditionalKbd", true) && + preferences.getBoolean("additionalKbdVisible", true); - terminalToolbarViewPager.setVisibility(showNow ? View.VISIBLE : View.GONE); - findViewById(R.id.terminal_toolbar_view_pager).requestFocus(); + pager.setVisibility(showNow ? View.VISIBLE : View.INVISIBLE); - handler.postDelayed(() -> { - if (mExtraKeys != null) { - ViewGroup.LayoutParams layoutParams = terminalToolbarViewPager.getLayoutParams(); - layoutParams.height = Math.round(37.5f * getResources().getDisplayMetrics().density * - (mExtraKeys.getExtraKeysInfo() == null ? 0 : mExtraKeys.getExtraKeysInfo().getMatrix().length)); - terminalToolbarViewPager.setLayoutParams(layoutParams); - } - frm.setPadding(0, 0, 0, preferences.getBoolean("adjustHeightForEK", false) && terminalToolbarViewPager.getVisibility() == View.VISIBLE ? terminalToolbarViewPager.getHeight() : 0); - }, 200); + if (showNow) { + pager.setAdapter(new X11ToolbarViewPager.PageAdapter(this, (v, k, e) -> mInputHandler.sendKeyEvent(e))); + pager.clearOnPageChangeListeners(); + pager.addOnPageChangeListener(new X11ToolbarViewPager.OnPageChangeListener(this, pager)); + pager.bringToFront(); + } else { + parent.removeView(pager); + parent.addView(pager, 0); + if (mExtraKeys != null) + mExtraKeys.unsetSpecialKeys(); + } + + ViewGroup.LayoutParams layoutParams = pager.getLayoutParams(); + layoutParams.height = Math.round(37.5f * getResources().getDisplayMetrics().density * + (TermuxX11ExtraKeys.getExtraKeysInfo() == null ? 0 : TermuxX11ExtraKeys.getExtraKeysInfo().getMatrix().length)); + pager.setLayoutParams(layoutParams); + + frm.setPadding(0, 0, 0, preferences.getBoolean("adjustHeightForEK", false) && showNow ? layoutParams.height : 0); + getLorieView().requestFocus(); } public void toggleExtraKeys(boolean visible, boolean saveState) { - runOnUiThread(() -> { - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean enabled = preferences.getBoolean("showAdditionalKbd", true); - ViewPager pager = getTerminalToolbarViewPager(); - ViewGroup parent = (ViewGroup) pager.getParent(); - boolean show = enabled && mClientConnected && visible; - - if (show) { - setTerminalToolbarView(); - getTerminalToolbarViewPager().bringToFront(); - } else { - parent.removeView(pager); - parent.addView(pager, 0); - if (mExtraKeys != null) - mExtraKeys.unsetSpecialKeys(); - } - frm.setPadding(0, 0, 0, preferences.getBoolean("adjustHeightForEK", false) && show ? pager.getHeight() : 0); - - if (enabled && saveState) { - SharedPreferences.Editor edit = preferences.edit(); - edit.putBoolean("additionalKbdVisible", show); - edit.commit(); - } + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean enabled = preferences.getBoolean("showAdditionalKbd", true); - pager.setVisibility(show ? View.VISIBLE : View.INVISIBLE); + if (enabled && mClientConnected && saveState) { + SharedPreferences.Editor edit = preferences.edit(); + edit.putBoolean("additionalKbdVisible", visible); + edit.commit(); + } - getLorieView().requestFocus(); - }); + setTerminalToolbarView(); } public void toggleExtraKeys() { - int visibility = getTerminalToolbarViewPager().getVisibility(); - toggleExtraKeys(visibility != View.VISIBLE, true); - getLorieView().requestFocus(); + toggleExtraKeys(getTerminalToolbarViewPager().getVisibility() != View.VISIBLE, true); } public boolean handleKey(KeyEvent e) { @@ -821,7 +809,10 @@ public void onUserLeaveHint() { @Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, @NonNull Configuration newConfig) { - toggleExtraKeys(!isInPictureInPictureMode, false); + if (isInPictureInPictureMode) + toggleExtraKeys(false, false); + else + setTerminalToolbarView(); super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); } @@ -849,7 +840,7 @@ void clientConnectedStateChanged(boolean connected) { runOnUiThread(()-> { SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(this); mClientConnected = connected; - toggleExtraKeys(connected && p.getBoolean("additionalKbdVisible", true), true); + setTerminalToolbarView(); findViewById(R.id.mouse_buttons).setVisibility(p.getBoolean("showMouseHelper", false) && "1".equals(p.getString("touchMode", "1")) && mClientConnected ? View.VISIBLE : View.GONE); findViewById(R.id.stub).setVisibility(connected?View.INVISIBLE:View.VISIBLE); getLorieView().setVisibility(connected?View.VISIBLE:View.INVISIBLE); diff --git a/app/src/main/java/com/termux/x11/input/InputEventSender.java b/app/src/main/java/com/termux/x11/input/InputEventSender.java index 3a1c9f4f8..2ec8e6d11 100644 --- a/app/src/main/java/com/termux/x11/input/InputEventSender.java +++ b/app/src/main/java/com/termux/x11/input/InputEventSender.java @@ -37,6 +37,8 @@ public final class InputEventSender { public float capturedPointerSpeedFactor = 100; public boolean dexMetaKeyCapture = false; public boolean pauseKeyInterceptingWithEsc = false; + public boolean stylusIsMouse = false; + public boolean stylusButtonContactModifierMode = false; /** Set of pressed keys for which we've sent TextEvent. */ private final TreeSet mPressedTextKeys; @@ -55,6 +57,11 @@ public void sendMouseEvent(PointF pos, int button, boolean down, boolean relativ mInjector.sendMouseEvent(pos != null ? (int) pos.x : 0, pos != null ? (int) pos.y : 0, button, down, relative); } + public void sendStylusEvent(float x, float y, int pressure, int tiltX, int tiltY, int orientation, int buttons, boolean eraser) { + mInjector.sendStylusEvent(x, y, pressure, tiltX, tiltY, orientation, buttons, eraser); + android.util.Log.d("STYLUS_EVENT", "transformed x " + x + " y " + y + " pressure " + pressure + " tiltX " + tiltX + " tiltY " + tiltY + " orientation " + orientation + " buttons " + buttons + " eraser " + eraser); + } + public void sendMouseDown(int button, boolean relative) { if (!buttons.contains(button)) return; diff --git a/app/src/main/java/com/termux/x11/input/InputStub.java b/app/src/main/java/com/termux/x11/input/InputStub.java index 79f7b67e7..a354f68db 100644 --- a/app/src/main/java/com/termux/x11/input/InputStub.java +++ b/app/src/main/java/com/termux/x11/input/InputStub.java @@ -41,4 +41,6 @@ public interface InputStub { /** Sends an event, not flushing connection. */ void sendTouchEvent(int action, int pointerId, int x, int y); + + void sendStylusEvent(float x, float y, int pressure, int tiltX, int tiltY, int orientation, int buttons, boolean eraser); } diff --git a/app/src/main/java/com/termux/x11/input/TouchInputHandler.java b/app/src/main/java/com/termux/x11/input/TouchInputHandler.java index 830728093..4089f37ec 100644 --- a/app/src/main/java/com/termux/x11/input/TouchInputHandler.java +++ b/app/src/main/java/com/termux/x11/input/TouchInputHandler.java @@ -8,6 +8,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.graphics.PointF; +import android.hardware.input.InputManager; import android.os.Handler; import android.os.Build; import android.util.DisplayMetrics; @@ -21,11 +22,16 @@ import androidx.annotation.IntDef; import androidx.core.math.MathUtils; +import com.termux.x11.LorieView; import com.termux.x11.MainActivity; import com.termux.x11.utils.SamsungDexUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; /** * This class is responsible for handling Touch input from the user. Touch events which manipulate @@ -141,12 +147,46 @@ private TouchInputHandler(MainActivity activity, RenderData renderData, RenderSt setInputMode(InputMode.TRACKPAD); mDexListener = new DexListener(activity); mTouchpadHandler = isTouchpad ? null : new TouchInputHandler(activity, mRenderData, renderStub, injector, true); + + refreshInputDevices(); + ((InputManager) mActivity.getSystemService(Context.INPUT_SERVICE)).registerInputDeviceListener(new InputManager.InputDeviceListener() { + @Override + public void onInputDeviceAdded(int deviceId) { + refreshInputDevices(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + refreshInputDevices(); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + refreshInputDevices(); + } + }, null); + } public TouchInputHandler(MainActivity activity, RenderStub renderStub, final InputEventSender injector) { this(activity, null, renderStub, injector, false); } + static public void refreshInputDevices() { + AtomicBoolean stylusAvailable = new AtomicBoolean(false); + Arrays.stream(InputDevice.getDeviceIds()) + .mapToObj(InputDevice::getDevice) + .filter(Objects::nonNull) + .forEach((device) -> { + //noinspection DataFlowIssue + android.util.Log.d("STYLUS", "found device " + device.getName() + " sources " + device.getSources()); + if (device.supportsSource(InputDevice.SOURCE_STYLUS)) + stylusAvailable.set(true); + }); + android.util.Log.d("STYLUS", "requesting stylus " + stylusAvailable.get()); + LorieView.requestStylusEnabled(stylusAvailable.get()); + } + boolean isDexEvent(MotionEvent event) { int SOURCE_DEX = InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN; return ((event.getSource() & SOURCE_DEX) == SOURCE_DEX) @@ -174,7 +214,8 @@ public boolean handleTouchEvent(View view0, View view, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) setCapturingEnabled(true); - if (event.getToolType(event.getActionIndex()) == MotionEvent.TOOL_TYPE_STYLUS) + if (event.getToolType(event.getActionIndex()) == MotionEvent.TOOL_TYPE_STYLUS + || event.getToolType(event.getActionIndex()) == MotionEvent.TOOL_TYPE_ERASER) return mStylusListener.onTouch(event); if (!isDexEvent(event) && (event.getToolType(event.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE @@ -295,6 +336,8 @@ public void reloadPreferences(SharedPreferences p) { mInjector.scaleTouchpad = p.getBoolean("scaleTouchpad", true); mInjector.capturedPointerSpeedFactor = ((float) p.getInt("capturedPointerSpeedFactor", 100))/100; mInjector.dexMetaKeyCapture = p.getBoolean("dexMetaKeyCapture", false); + mInjector.stylusIsMouse = p.getBoolean("stylusIsMouse", false); + mInjector.stylusButtonContactModifierMode = p.getBoolean("stylusButtonContactModifierMode", false); mInjector.pauseKeyInterceptingWithEsc = p.getBoolean("pauseKeyInterceptingWithEsc", false); switch (p.getString("transformCapturedPointer", "no")) { case "c": @@ -592,8 +635,105 @@ private class StylusListener { private static final int ACTION_PRIMARY_DOWN = 0xd3; private static final int ACTION_PRIMARY_UP = 0xd4; + private float x = 0, y = 0, pressure = 0, tilt = 0, orientation = 0; + private int buttons = 0; + + private int convertOrientation(float value) { + int newValue = (int) (((value * 180 / Math.PI) + 360) % 360); + if (newValue > 180) + newValue = (newValue - 360) % 360; + return newValue; + } + + private boolean hasButton(MotionEvent e, int button) { + return (e.getButtonState() & button) == button; + } + + int extractButtons(MotionEvent e) { + if (mInjector.stylusButtonContactModifierMode) { + if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_MOVE) { + if (hasButton(e, MotionEvent.BUTTON_STYLUS_SECONDARY)) + return (1 << 1); + if (hasButton(e, MotionEvent.BUTTON_STYLUS_PRIMARY)) + return (1 << 2); + else + return STYLUS_INPUT_HELPER_MODE; + } else return 0; + } else { + int buttons = 0; + if (e.getAction() == MotionEvent.ACTION_DOWN || e.getAction() == MotionEvent.ACTION_MOVE) + buttons = STYLUS_INPUT_HELPER_MODE; + if (hasButton(e, MotionEvent.BUTTON_STYLUS_SECONDARY)) + buttons |= (1 << 1); + if (hasButton(e, MotionEvent.BUTTON_STYLUS_PRIMARY)) + buttons |= (1 << 2); + + return buttons; + } + } + + public boolean isExternal(InputDevice d) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + return d.isExternal(); + + try { + // isExternal is a hidden method that is not accessible through the SDK_INT before Android Q + //noinspection DataFlowIssue + return (Boolean) InputDevice.class.getMethod("isExternal").invoke(d); + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return false; + } + } + @SuppressLint("ClickableViewAccessibility") boolean onTouch(MotionEvent e) { + if (mInjector.stylusIsMouse) + return onTouchMouse(e); + + int action = e.getAction(); + int tiltX = 0, tiltY = 0; + int newButtons = extractButtons(e); + float newX = e.getX(e.getActionIndex()), newY = e.getY(e.getActionIndex()); + InputDevice.MotionRange rangeX = e.getDevice().getMotionRange(MotionEvent.AXIS_X); + InputDevice.MotionRange rangeY = e.getDevice().getMotionRange(MotionEvent.AXIS_Y); + + if (MainActivity.getInstance().getLorieView().hasPointerCapture() && + isExternal(e.getDevice()) && rangeX != null && rangeY != null) { + newX *= mRenderData.imageWidth / rangeX.getMax(); + newY *= mRenderData.imageHeight / rangeY.getMax(); + } else { + newX *= mRenderData.scale.x; + newY *= mRenderData.scale.y; + } + + if (x == newX && y == newY && pressure == e.getPressure() && tilt == e.getAxisValue(MotionEvent.AXIS_TILT) && + orientation == e.getAxisValue(MotionEvent.AXIS_ORIENTATION) && buttons == newButtons) + return true; + + if (e.getDevice().getMotionRange(MotionEvent.AXIS_TILT) != null && + e.getDevice().getMotionRange(MotionEvent.AXIS_ORIENTATION) != null) { + orientation = e.getAxisValue(MotionEvent.AXIS_ORIENTATION); + tilt = e.getAxisValue(MotionEvent.AXIS_TILT); + tiltX = (int) Math.round((float) Math.asin(-Math.sin(orientation) * Math.sin(tilt)) * 63.5 - 0.5); + tiltY = (int) Math.round((float) Math.asin( Math.cos(orientation) * Math.sin(tilt)) * 63.5 - 0.5); + } + + android.util.Log.d("STYLUS_EVENT", "action " + action + " x " + newX + " y " + newY + " pressure " + e.getPressure() + " tilt " + e.getAxisValue(MotionEvent.AXIS_TILT) + " orientation " + e.getAxisValue(MotionEvent.AXIS_ORIENTATION)); + mInjector.sendStylusEvent( + x = newX, + y = newY, + (int) ((pressure = e.getPressure()) * 65535), + tiltX, + tiltY, + convertOrientation(orientation), + buttons = newButtons, + e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_ERASER); + + return true; + } + + @SuppressLint("ClickableViewAccessibility") + boolean onTouchMouse(MotionEvent e) { int action = e.getAction(); float scaledX = e.getX(e.getActionIndex()) * mRenderData.scale.x, scaledY = e.getY(e.getActionIndex()) * mRenderData.scale.y; if (mRenderData.setCursorPosition(scaledX, scaledY)) diff --git a/app/src/main/java/com/termux/x11/utils/TermuxX11ExtraKeys.java b/app/src/main/java/com/termux/x11/utils/TermuxX11ExtraKeys.java index 03988fa98..74a0169cb 100644 --- a/app/src/main/java/com/termux/x11/utils/TermuxX11ExtraKeys.java +++ b/app/src/main/java/com/termux/x11/utils/TermuxX11ExtraKeys.java @@ -31,11 +31,11 @@ public class TermuxX11ExtraKeys implements ExtraKeysView.IExtraKeysView { @SuppressWarnings("FieldCanBeLocal") - private final String LOG_TAG = "TermuxX11ExtraKeys"; + private static final String LOG_TAG = "TermuxX11ExtraKeys"; private final View.OnKeyListener mEventListener; private final MainActivity mActivity; private final ExtraKeysView mExtraKeysView; - private ExtraKeysInfo mExtraKeysInfo; + static private ExtraKeysInfo mExtraKeysInfo; private boolean ctrlDown; private boolean altDown; @@ -204,9 +204,9 @@ public void onLorieExtraKeyButtonClick(View view, String key, boolean ctrlDown, * Set the terminal extra keys and style. */ @SuppressWarnings("deprecation") - private void setExtraKeys() { + public static void setExtraKeys() { mExtraKeysInfo = null; - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mActivity); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(MainActivity.getInstance()); try { // The mMap stores the extra key and style string values while loading properties @@ -215,20 +215,20 @@ private void setExtraKeys() { String extrakeys = preferences.getString("extra_keys_config", TermuxX11ExtraKeys.DEFAULT_IVALUE_EXTRA_KEYS); mExtraKeysInfo = new ExtraKeysInfo(extrakeys, "extra-keys-style", ExtraKeysConstants.CONTROL_CHARS_ALIASES); } catch (JSONException e) { - Toast.makeText(mActivity, "Could not load and set the \"extra-keys\" property from the properties file: " + e, Toast.LENGTH_LONG).show(); + Toast.makeText(MainActivity.getInstance(), "Could not load and set the \"extra-keys\" property from the properties file: " + e, Toast.LENGTH_LONG).show(); Log.e(LOG_TAG, "Could not load and set the \"extra-keys\" property from the properties file: ", e); try { mExtraKeysInfo = new ExtraKeysInfo(TermuxX11ExtraKeys.DEFAULT_IVALUE_EXTRA_KEYS, "default", ExtraKeysConstants.CONTROL_CHARS_ALIASES); } catch (JSONException e2) { - Toast.makeText(mActivity, "Can't create default extra keys", Toast.LENGTH_LONG).show(); + Toast.makeText(MainActivity.getInstance(), "Can't create default extra keys", Toast.LENGTH_LONG).show(); Log.e(LOG_TAG, "Could create default extra keys: ", e); mExtraKeysInfo = null; } } } - public ExtraKeysInfo getExtraKeysInfo() { + public static ExtraKeysInfo getExtraKeysInfo() { if (mExtraKeysInfo == null) setExtraKeys(); return mExtraKeysInfo; diff --git a/app/src/main/java/com/termux/x11/utils/X11ToolbarViewPager.java b/app/src/main/java/com/termux/x11/utils/X11ToolbarViewPager.java index 2199f6807..d5b12f326 100644 --- a/app/src/main/java/com/termux/x11/utils/X11ToolbarViewPager.java +++ b/app/src/main/java/com/termux/x11/utils/X11ToolbarViewPager.java @@ -54,10 +54,7 @@ public Object instantiateItem(@NonNull ViewGroup collection, int position) { layout = inflater.inflate(R.layout.view_terminal_toolbar_extra_keys, collection, false); ExtraKeysView extraKeysView = (ExtraKeysView) layout; mActivity.mExtraKeys = new TermuxX11ExtraKeys(mEventListener, mActivity, extraKeysView); - int mTerminalToolbarDefaultHeight = mActivity.getTerminalToolbarViewPager().getLayoutParams().height; - int height = mTerminalToolbarDefaultHeight * - ((mActivity.mExtraKeys.getExtraKeysInfo() == null) ? 0 : mActivity.mExtraKeys.getExtraKeysInfo().getMatrix().length); - extraKeysView.reload(mActivity.mExtraKeys.getExtraKeysInfo(), height); + extraKeysView.reload(); extraKeysView.setExtraKeysViewClient(mActivity.mExtraKeys); extraKeysView.setOnHoverListener((v, e) -> true); extraKeysView.setOnGenericMotionListener((v, e) -> true); @@ -68,7 +65,7 @@ public Object instantiateItem(@NonNull ViewGroup collection, int position) { editText.setOnEditorActionListener((v, actionId, event) -> { String textToSend = editText.getText().toString(); - if (textToSend.length() == 0) textToSend = "\r"; + if (textToSend.isEmpty()) textToSend = "\r"; KeyEvent e = new KeyEvent(0, textToSend, KeyCharacterMap.VIRTUAL_KEYBOARD, 0); mEventListener.onKey(mActivity.getLorieView(), 0, e); diff --git a/app/src/main/res/layout/preference.xml b/app/src/main/res/layout/preference.xml new file mode 100644 index 000000000..54c83d0d4 --- /dev/null +++ b/app/src/main/res/layout/preference.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a0928f0b4..2534947c3 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,8 +1,45 @@ - + + + + + + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 900bbb2d2..bda144919 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,6 +1,6 @@ - + - - + + + + @@ -137,17 +143,6 @@ android:defaultValue="true" android:key="captureVolumeKeys"/> - - - - + android:key="enableAccessibilityService"> + + - - - - @@ -215,4 +202,33 @@ android:title="Request notification permission" android:key="requestNotificationPermission" /> + + + + + + + + + + + +