From 465b3ebbfee6fab789aa98ac2dbdbe868a0f6eb8 Mon Sep 17 00:00:00 2001 From: Mis012 Date: Mon, 13 Jan 2025 15:54:21 +0100 Subject: [PATCH] View: support multitouch properly seems to work, but there might still be some edge cases --- src/api-impl-jni/defines.h | 9 - .../android_view_MotionEvent.h | 8 + src/api-impl-jni/util.c | 5 +- src/api-impl-jni/util.h | 1 + src/api-impl-jni/views/android_view_View.c | 157 ++++++++++++++---- src/api-impl/android/view/MotionEvent.java | 74 +++++---- 6 files changed, 182 insertions(+), 72 deletions(-) diff --git a/src/api-impl-jni/defines.h b/src/api-impl-jni/defines.h index ef98b6de..34acebbf 100644 --- a/src/api-impl-jni/defines.h +++ b/src/api-impl-jni/defines.h @@ -51,13 +51,4 @@ #define JAVA_ENUM(name) \ name = JOIN3(JAVA_ENUM_CLASS, _, name) -// this really doesn't belong here, should probably put this in Java and deal with ugly name convention of autogenerated headers - -#define MOTION_EVENT_ACTION_DOWN 0 -#define MOTION_EVENT_ACTION_UP 1 -#define MOTION_EVENT_ACTION_MOVE 2 -#define MOTION_EVENT_ACTION_CANCEL 3 -#define MOTION_EVENT_ACTION_SCROLL 8 - - #endif diff --git a/src/api-impl-jni/generated_headers/android_view_MotionEvent.h b/src/api-impl-jni/generated_headers/android_view_MotionEvent.h index b69d86aa..6229759b 100644 --- a/src/api-impl-jni/generated_headers/android_view_MotionEvent.h +++ b/src/api-impl-jni/generated_headers/android_view_MotionEvent.h @@ -181,6 +181,14 @@ extern "C" { #define android_view_MotionEvent_HISTORY_CURRENT -2147483648L #undef android_view_MotionEvent_MAX_RECYCLED #define android_view_MotionEvent_MAX_RECYCLED 10L +#undef android_view_MotionEvent_X_OFFSET +#define android_view_MotionEvent_X_OFFSET 0L +#undef android_view_MotionEvent_Y_OFFSET +#define android_view_MotionEvent_Y_OFFSET 1L +#undef android_view_MotionEvent_RAW_X_OFFSET +#define android_view_MotionEvent_RAW_X_OFFSET 2L +#undef android_view_MotionEvent_RAW_Y_OFFSET +#define android_view_MotionEvent_RAW_Y_OFFSET 3L /* * Class: android_view_MotionEvent * Method: nativeInitialize diff --git a/src/api-impl-jni/util.c b/src/api-impl-jni/util.c index f008c942..e60f035d 100644 --- a/src/api-impl-jni/util.c +++ b/src/api-impl-jni/util.c @@ -94,7 +94,8 @@ void set_up_handle_cache(JNIEnv *env) handle_cache.paint.getColor = _METHOD(handle_cache.paint.class, "getColor", "()I"); handle_cache.motion_event.class = _REF((*env)->FindClass(env, "android/view/MotionEvent")); - handle_cache.motion_event.constructor = _METHOD(handle_cache.motion_event.class, "", "(IIFFJFF)V"); + handle_cache.motion_event.constructor = _METHOD(handle_cache.motion_event.class, "", "(IIJ[I[F)V"); + handle_cache.motion_event.constructor_single = _METHOD(handle_cache.motion_event.class, "", "(IIJFFFF)V"); handle_cache.sensor_event.class = _REF((*env)->FindClass(env, "android/hardware/SensorEvent")); handle_cache.sensor_event.constructor = _METHOD(handle_cache.sensor_event.class, "", "([FLandroid/hardware/Sensor;)V"); @@ -193,7 +194,7 @@ static int fallback_verbose_log(int prio, const char *tag, const char *fmt, va_l pthread_mutex_lock(&mutex); static char buf[1024]; ret = vsnprintf(buf, sizeof(buf), fmt, ap); - fprintf(stderr, "%lu: %s\n", pthread_self(), buf); + fprintf(stderr, "%w64u: %s\n", (uint64_t)pthread_self(), buf); pthread_mutex_unlock(&mutex); return ret; diff --git a/src/api-impl-jni/util.h b/src/api-impl-jni/util.h index a57d21cd..5ede5d79 100644 --- a/src/api-impl-jni/util.h +++ b/src/api-impl-jni/util.h @@ -39,6 +39,7 @@ struct handle_cache { struct { jclass class; jmethodID constructor; + jmethodID constructor_single; } motion_event; struct { jclass class; diff --git a/src/api-impl-jni/views/android_view_View.c b/src/api-impl-jni/views/android_view_View.c index 8d9b4469..49e6889c 100644 --- a/src/api-impl-jni/views/android_view_View.c +++ b/src/api-impl-jni/views/android_view_View.c @@ -10,22 +10,59 @@ #include "../views/AndroidLayout.h" #include "../generated_headers/android_view_View.h" +#include "../generated_headers/android_view_MotionEvent.h" + +#define JAVA_ENUM_CLASS android_view_MotionEvent +enum { + JAVA_ENUM(ACTION_DOWN), + JAVA_ENUM(ACTION_UP), + JAVA_ENUM(ACTION_MOVE), + JAVA_ENUM(ACTION_CANCEL), + + JAVA_ENUM(ACTION_POINTER_DOWN), + JAVA_ENUM(ACTION_POINTER_UP), + + JAVA_ENUM(ACTION_SCROLL), +}; +#undef JAVA_ENUM_CLASS #define SOURCE_TOUCHSCREEN 0x1002 +#define MAX_POINTERS 50 + +struct pointer { + int id; + int index; + float coord_x; + float coord_y; + float raw_x; + float raw_y; +}; + +GPtrArray *pointer_indices = NULL; + static GdkEvent *canceled_event = NULL; static WrapperWidget *cancel_triggerer = NULL; -static bool call_ontouch_callback(WrapperWidget *wrapper, int action, double x, double y, GtkPropagationPhase phase, guint32 timestamp, GdkEvent *event) +static struct pointer pointers[MAX_POINTERS] = {}; + +static bool call_ontouch_callback(WrapperWidget *wrapper, int action, struct pointer pointers[MAX_POINTERS], GPtrArray *pointer_indices, GtkPropagationPhase phase, guint32 timestamp, GdkEvent *event) { bool ret; - double raw_x; - double raw_y; JNIEnv *env = get_jni_env(); jobject this = wrapper->jobj; - gdk_event_get_position(event, &raw_x, &raw_y); - jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_TOUCHSCREEN, action, (float)x, (float)y, (long)timestamp, (float)raw_x, (float)raw_y); + int num_pointers = pointer_indices->len; + jintArray ids = (*env)->NewIntArray(env, num_pointers); + jfloatArray coords = (*env)->NewFloatArray(env, num_pointers * 4); + for (int i = 0; i < num_pointers; i++) { + struct pointer *pointer = (struct pointer *)g_ptr_array_index(pointer_indices, i); + (*env)->SetIntArrayRegion(env, ids, i, 1, &pointer->id); + /* put in all four float values starting at coord_x */ + (*env)->SetFloatArrayRegion(env, coords, 4 * i, 4, &pointer->coord_x); + } + + jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_TOUCHSCREEN, action, (long)timestamp, ids, coords); if (wrapper->custom_dispatch_touch) { ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.dispatchTouchEvent, motion_event); @@ -46,7 +83,7 @@ static bool call_ontouch_callback(WrapperWidget *wrapper, int action, double x, (*env)->DeleteLocalRef(env, motion_event); - if (action == MOTION_EVENT_ACTION_UP) + if (action == ACTION_UP) wrapper->intercepting_touch = false; return ret; } @@ -68,49 +105,104 @@ static void gdk_event_get_widget_relative_position(GdkEvent *event, GtkWidget *w *y = p.y; } +void remove_pointer_fast(GPtrArray *pointer_indices, struct pointer *pointer) +{ + g_ptr_array_remove_index_fast(pointer_indices, pointer->index); + if(pointer->index != pointer_indices->len) { + /* update the index field of the pointer that was moved in to fill the empty space */ + struct pointer *replacement_pointer = (struct pointer *)g_ptr_array_index(pointer_indices, pointer->index); + replacement_pointer->index = pointer->index; + } + /* mark as empty, other fields don't matter */ + pointer->id = 0; +} + // TODO: find a way to reconcile this with libandroid/input.c? static gboolean on_event(GtkEventControllerLegacy *event_controller, GdkEvent *event, gpointer user_data) { double x; double y; + uintptr_t id = (uintptr_t)gdk_event_get_event_sequence(event); + + /* FIXME: this will clash with touchscreen */ + if(id == 0) + id = 1; + + int pointer_index; + + if(pointers[id].id) { + pointer_index = pointers[id].index; + } else { + /* index of a hypothetical next appended element */ + pointer_index = pointer_indices->len; + } + + int action; + GdkEventType event_type = gdk_event_get_event_type(event); + switch(event_type) { + case GDK_BUTTON_PRESS: + case GDK_TOUCH_BEGIN: + action = pointer_index > 0 ? (ACTION_POINTER_DOWN | (pointer_index << 8)) : ACTION_DOWN; + break; + case GDK_BUTTON_RELEASE: + case GDK_TOUCH_END: + action = pointer_index > 0 ? (ACTION_POINTER_UP | (pointer_index << 8)) : ACTION_UP; + break; + case GDK_MOTION_NOTIFY: + if (!(gdk_event_get_modifier_state(event) & GDK_BUTTON1_MASK)) + return false; + case GDK_TOUCH_UPDATE: + action = ACTION_MOVE; + break; + default: // not a touch or mouse event, nothing to do here + return false; + } + GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(event_controller)); WrapperWidget *wrapper = WRAPPER_WIDGET(widget); GtkPropagationPhase phase = gtk_event_controller_get_propagation_phase(GTK_EVENT_CONTROLLER(event_controller)); guint32 timestamp = gdk_event_get_time(event); - // TODO: this doesn't work for multitouch + if (id >= MAX_POINTERS) { + fprintf(stderr, "exiting - event sequence %w64d larger than %d, did Gtk implementation change?", id, MAX_POINTERS); + exit(1); + } + + double raw_x; + double raw_y; + gdk_event_get_position(event, &raw_x, &raw_y); + gdk_event_get_widget_relative_position(event, widget, &x, &y); + + if(!pointers[id].id) { + /* if this is a new sequence, add a slot for it */ + g_ptr_array_add(pointer_indices, &pointers[id]); + /* we appended a single element, so it must be at pointer_index */ + pointers[id].index = pointer_index; + pointers[id].id = id; + } + + pointers[id].coord_x = x; + pointers[id].coord_y = y; + pointers[id].raw_x = x; + pointers[id].raw_y = y; + + // TODO: does this work properly with multitouch? if (cancel_triggerer == wrapper) { // cancel done canceled_event = NULL; cancel_triggerer = NULL; } else if (event == canceled_event) { gdk_event_get_widget_relative_position(event, widget, &x, &y); - call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_CANCEL, x, y, phase, timestamp, event); + call_ontouch_callback(wrapper, ACTION_CANCEL, pointers, pointer_indices, phase, timestamp, event); + remove_pointer_fast(pointer_indices, &pointers[id]); return false; } - switch(gdk_event_get_event_type(event)) { - case GDK_BUTTON_PRESS: - case GDK_TOUCH_BEGIN: - gdk_event_get_widget_relative_position(event, widget, &x, &y); - return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_DOWN, x, y, phase, timestamp, event); - break; - case GDK_BUTTON_RELEASE: - case GDK_TOUCH_END: - gdk_event_get_widget_relative_position(event, widget, &x, &y); - return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_UP, x, y, phase, timestamp, event); - break; - case GDK_MOTION_NOTIFY: - if (!(gdk_event_get_modifier_state(event) & GDK_BUTTON1_MASK)) - break; - case GDK_TOUCH_UPDATE: - gdk_event_get_widget_relative_position(event, widget, &x, &y); - return call_ontouch_callback(wrapper, MOTION_EVENT_ACTION_MOVE, x, y, phase, timestamp, event); - break; - default: - break; - } - return false; + gboolean ret = call_ontouch_callback(wrapper, action, pointers, pointer_indices, phase, timestamp, event); + if (event_type == GDK_BUTTON_RELEASE || event_type == GDK_TOUCH_END) { + remove_pointer_fast(pointer_indices, &pointers[id]); + } + return ret; } static void on_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) @@ -138,7 +230,7 @@ static gboolean scroll_cb(GtkEventControllerScroll* self, gdouble dx, gdouble dy dx /= MAGIC_SCROLL_FACTOR; dy /= MAGIC_SCROLL_FACTOR; } - jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor, SOURCE_CLASS_POINTER, MOTION_EVENT_ACTION_SCROLL, dx, -dy, (long)0, 0.f, 0.f); + jobject motion_event = (*env)->NewObject(env, handle_cache.motion_event.class, handle_cache.motion_event.constructor_single, SOURCE_CLASS_POINTER, ACTION_SCROLL, 0, dx, -dy, 0.f, 0.f); gboolean ret = (*env)->CallBooleanMethod(env, this, handle_cache.view.onGenericMotionEvent, motion_event); if((*env)->ExceptionCheck(env)) @@ -153,6 +245,9 @@ void _setOnTouchListener(JNIEnv *env, jobject this, GtkWidget *widget) if(old_controller) return; + if(!pointer_indices) + pointer_indices = g_ptr_array_new_full(20, NULL); + GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_event_controller_legacy_new()); gtk_event_controller_set_propagation_phase(controller, GTK_PHASE_BUBBLE); diff --git a/src/api-impl/android/view/MotionEvent.java b/src/api-impl/android/view/MotionEvent.java index 7876cf80..640f360b 100644 --- a/src/api-impl/android/view/MotionEvent.java +++ b/src/api-impl/android/view/MotionEvent.java @@ -1365,25 +1365,31 @@ public final class MotionEvent extends InputEvent { private static native void nativeScale(int nativePtr, float scale); private static native void nativeTransform(int nativePtr, Matrix matrix); + private static final int X_OFFSET = 0; + private static final int Y_OFFSET = 1; + private static final int RAW_X_OFFSET = 2; + private static final int RAW_Y_OFFSET = 3; + int source; int action; - float coord_x; - float coord_y; long eventTime; - float raw_x; - float raw_y; + int[] ids; + float[] coords; private MotionEvent() { } - public MotionEvent(int source, int action, float coord_x, float coord_y, long eventTime, float raw_x, float raw_y) { + public MotionEvent(int source, int action, long eventTime, float x, float y, float raw_x, float raw_y) { + this(source, action, eventTime, new int[]{0}, new float[]{x, y, raw_x, raw_y}); + } + + public MotionEvent(int source, int action, long eventTime, int[] ids, float[] coords) { this.source = source; this.action = action; - this.coord_x = coord_x; - this.coord_y = coord_y; this.eventTime = eventTime; - this.raw_x = raw_x; - this.raw_y = raw_y; + + this.ids = ids; + this.coords = coords; } @Override @@ -1537,7 +1543,7 @@ public final class MotionEvent extends InputEvent { MotionEvent ev = obtain(); synchronized (gSharedTempLock) { ensureSharedTempPointerCapacity(1); - final PointerProperties[] pp = gSharedTempPointerProperties; + /*final PointerProperties[] pp = gSharedTempPointerProperties; pp[0].clear(); pp[0].id = 0; @@ -1546,7 +1552,7 @@ public final class MotionEvent extends InputEvent { pc[0].x = x; pc[0].y = y; pc[0].pressure = pressure; - pc[0].size = size; + pc[0].size = size;*/ // ev.mNativePtr = nativeInitialize(ev.mNativePtr, // deviceId, /*InputDevice.SOURCE_UNKNOWN*/ 0, action, 0, edgeFlags, metaState, 0, @@ -1554,8 +1560,8 @@ public final class MotionEvent extends InputEvent { // downTime * NS_PER_MS, eventTime * NS_PER_MS, // 1, pp, pc); ev.action = action; - ev.coord_x = x; - ev.coord_y = y; + ev.ids = new int[]{1}; + ev.coords = new float[] {x, y, 0, 0}; ev.eventTime = eventTime; return ev; } @@ -1634,11 +1640,10 @@ public final class MotionEvent extends InputEvent { MotionEvent ev = obtain(); ev.source = other.source; ev.action = other.action; - ev.coord_x = other.coord_x; - ev.coord_y = other.coord_y; - ev.raw_x = other.raw_x; - ev.raw_y = other.raw_y; + ev.ids = other.ids.clone(); + ev.coords = other.coords.clone(); ev.eventTime = other.eventTime; + return ev; } @@ -1755,7 +1760,7 @@ public final class MotionEvent extends InputEvent { * @return The index associated with the action. */ public final int getActionIndex() { - return 0; // FIXME + return (action & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT; } /** @@ -1856,7 +1861,7 @@ public final class MotionEvent extends InputEvent { * @see #AXIS_X */ public final float getX() { - return coord_x; + return coords[0 + X_OFFSET]; } /** @@ -1866,7 +1871,7 @@ public final class MotionEvent extends InputEvent { * @see #AXIS_Y */ public final float getY() { - return coord_y; + return coords[0 + Y_OFFSET]; } /** @@ -1951,9 +1956,9 @@ public final class MotionEvent extends InputEvent { */ public final float getAxisValue(int axis) { if (axis == AXIS_HSCROLL) - return coord_x; + return coords[0 + X_OFFSET]; else if (axis == AXIS_VSCROLL) - return coord_y; + return coords[0 + Y_OFFSET]; return nativeGetAxisValue(mNativePtr, axis, 0, HISTORY_CURRENT); } @@ -1962,7 +1967,7 @@ public final class MotionEvent extends InputEvent { * >= 1. */ public final int getPointerCount() { - return 1; // TODO: implement this properly + return ids.length; } /** @@ -1974,7 +1979,7 @@ public final class MotionEvent extends InputEvent { * (the first pointer that is down) to {@link #getPointerCount()}-1. */ public final int getPointerId(int pointerIndex) { - return pointerIndex; + return ids[pointerIndex]; } /** @@ -2020,7 +2025,7 @@ public final class MotionEvent extends InputEvent { * @see #AXIS_X */ public final float getX(int pointerIndex) { - return coord_x; + return coords[4*pointerIndex + X_OFFSET]; } /** @@ -2035,7 +2040,15 @@ public final class MotionEvent extends InputEvent { * @see #AXIS_Y */ public final float getY(int pointerIndex) { - return coord_y; + return coords[4*pointerIndex + Y_OFFSET]; + } + + public final float getRawX(int pointerIndex) { + return coords[4*pointerIndex + RAW_X_OFFSET]; + } + + public final float getRawY(int pointerIndex) { + return coords[4*pointerIndex + RAW_Y_OFFSET]; } /** @@ -2243,7 +2256,7 @@ public final class MotionEvent extends InputEvent { * @see #AXIS_X */ public final float getRawX() { - return raw_x; + return coords[0 + RAW_X_OFFSET]; } /** @@ -2256,7 +2269,7 @@ public final class MotionEvent extends InputEvent { * @see #AXIS_Y */ public final float getRawY() { - return raw_y; + return coords[0 + RAW_Y_OFFSET]; } /** @@ -2739,8 +2752,9 @@ public final class MotionEvent extends InputEvent { public final void offsetLocation(float deltaX, float deltaY) { if (deltaX != 0.0f || deltaY != 0.0f) { // nativeOffsetLocation(mNativePtr, deltaX, deltaY); - this.coord_x += deltaX; - this.coord_y += deltaY; + /* FIXME: loop for all pointers? */ + coords[0 + X_OFFSET] += deltaX; + coords[0 + Y_OFFSET] += deltaY; } }