diff --git a/src/api-impl-jni/android_view_Window.c b/src/api-impl-jni/android_view_Window.c index 7103522b..e0254c9c 100644 --- a/src/api-impl-jni/android_view_Window.c +++ b/src/api-impl-jni/android_view_Window.c @@ -10,6 +10,12 @@ JNIEXPORT void JNICALL Java_android_view_Window_set_1widget_1as_1root(JNIEnv *en gtk_window_set_child(GTK_WINDOW(_PTR(window)), gtk_widget_get_parent(GTK_WIDGET(_PTR(widget)))); } +// FIXME put this in a header file +struct input_queue { + int fd; + GtkEventController *controller; +}; + JNIEXPORT void JNICALL Java_android_view_Window_take_1input_1queue(JNIEnv *env, jobject this, jlong native_window, jobject callback, jobject queue) { GtkWidget *window = _PTR(native_window); @@ -18,7 +24,11 @@ JNIEXPORT void JNICALL Java_android_view_Window_take_1input_1queue(JNIEnv *env, GtkEventController *controller = GTK_EVENT_CONTROLLER(gtk_event_controller_legacy_new()); gtk_widget_add_controller(window, controller); - _SET_LONG_FIELD(queue, "native_ptr", _INTPTR(controller)); + struct input_queue *input_queue = malloc(sizeof(struct input_queue)); + input_queue->fd = -1; + input_queue->controller = controller; + + _SET_LONG_FIELD(queue, "native_ptr", _INTPTR(input_queue)); // we need to keep these for later, so they can be called after OnCreate finishes g_object_set_data(G_OBJECT(window), "input_queue_callback", (gpointer)_REF(callback)); diff --git a/src/libandroid/input.c b/src/libandroid/input.c index 25597972..c0e234ef 100644 --- a/src/libandroid/input.c +++ b/src/libandroid/input.c @@ -1,5 +1,7 @@ +#include #include #include +#include #include @@ -60,6 +62,8 @@ enum { AMOTION_EVENT_ACTION_BUTTON_RELEASE = 12 }; +// TODO: since we have to shove this struct through pipes, we might want to use different structs +// for each event and have an event type field consistent between them so we know what to cast to struct AInputEvent { double x; double y; @@ -153,71 +157,89 @@ struct android_poll_source { void (*process)(struct android_app* app, struct android_poll_source* source); }; -// ugly; if this is < 0, there are no events -// we return this from AInputQueue_getEvent, -// and we don't want to have an actual queue -static int32_t fixme_ugly_are_there_events = -1; - +// TODO: malloc on getEvent and free on finishEvent? malloc isn't very fast though, and events can in principle be pretty frequent struct AInputEvent fixme_ugly_current_event; -static gboolean on_event(GtkEventControllerLegacy* self, GdkEvent* event, struct android_poll_source *poll_source) +static inline void make_touch_event(GdkEvent* event, GtkEventControllerLegacy* event_controller, struct AInputEvent *ainput_event) { - double x; - double y; + GtkWidget *window = gtk_event_controller_get_widget(event_controller); + GtkWidget *child; + + gdk_event_get_position(event, &ainput_event->x, &ainput_event->y); + + // the window's coordinate system starts at the top left of the header bar, which is not ideal + // apps expect it to start at the top left of the area where child widgets get placed, so that + // the top left of the window is the same as the top left of a single widget filling the entire window + // while it's quite hacky, the following should realistically work for most if not all cases + if(child = gtk_window_get_child(GTK_WINDOW(window))) + gtk_widget_translate_coordinates(window, child, ainput_event->x, ainput_event->y, &ainput_event->x, &ainput_event->y); - // TODO: this doesn't work for multitouch switch(gdk_event_get_event_type(event)) { case GDK_BUTTON_PRESS: - gdk_event_get_position(event, &x, &y); - fixme_ugly_current_event.x = x; - fixme_ugly_current_event.y = y; - fixme_ugly_current_event.action = AMOTION_EVENT_ACTION_DOWN; - fixme_ugly_are_there_events = 0; // not < 0, so there are events - poll_source->process(poll_source->app, poll_source); - if(fixme_ugly_are_there_events != -1) - fprintf(stderr, "sus: poll_source->callback finished, but 'fixme_ugly_are_there_events' is not -1 (it's %d)\n" - "this should *probably* not happen?\n", fixme_ugly_are_there_events); + case GDK_TOUCH_BEGIN: + ainput_event->action = AMOTION_EVENT_ACTION_DOWN; break; case GDK_BUTTON_RELEASE: - gdk_event_get_position(event, &x, &y); - fixme_ugly_current_event.x = x; - fixme_ugly_current_event.y = y; - fixme_ugly_current_event.action = AMOTION_EVENT_ACTION_UP; - fixme_ugly_are_there_events = 0; // not < 0, so there are events - poll_source->process(poll_source->app, poll_source); - if(fixme_ugly_are_there_events != -1) - fprintf(stderr, "sus: poll_source->callback finished, but 'fixme_ugly_are_there_events' is not -1 (it's %d)\n" - "this should *probably* not happen?\n", fixme_ugly_are_there_events); + case GDK_TOUCH_END: + ainput_event->action = AMOTION_EVENT_ACTION_UP; break; case GDK_MOTION_NOTIFY: - gdk_event_get_position(event, &x, &y); - fixme_ugly_current_event.x = x; - fixme_ugly_current_event.y = y; - fixme_ugly_current_event.action = AMOTION_EVENT_ACTION_MOVE; - fixme_ugly_are_there_events = 0; // not < 0, so there are events - poll_source->process(poll_source->app, poll_source); - if(fixme_ugly_are_there_events != -1) - fprintf(stderr, "sus: poll_source->callback finished, but 'fixme_ugly_are_there_events' is not -1 (it's %d)\n" - "this should *probably* not happen?\n", fixme_ugly_are_there_events); + case GDK_TOUCH_UPDATE: + ainput_event->action = AMOTION_EVENT_ACTION_MOVE; + break; + default: + fprintf(stderr, "%s: %s: passed in GdkEvent is not a touch event or equivalent\n", __FILE__, __func__); break; } } -void AInputQueue_attachLooper(AInputQueue* queue, struct ALooper* looper, int ident, Looper_callbackFunc callback, void* data) +static gboolean on_event(GtkEventControllerLegacy* self, GdkEvent* event, int input_queue_pipe_fd) +{ + struct AInputEvent ainput_event; + + // TODO: this doesn't work for multitouch + switch(gdk_event_get_event_type(event)) { + // mouse click/move (currently we convert these to touch events) + case GDK_BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + case GDK_MOTION_NOTIFY: + // touchscreen + case GDK_TOUCH_BEGIN: + case GDK_TOUCH_END: + case GDK_TOUCH_UPDATE: + make_touch_event(event, self, &ainput_event); + write(input_queue_pipe_fd, &ainput_event, sizeof(struct AInputEvent)); + break; + } +} + +// FIXME put this in a header file +struct input_queue { + int fd; + GtkEventController *controller; +}; + +void AInputQueue_attachLooper(struct input_queue* queue, struct ALooper* looper, int ident, Looper_callbackFunc callback, void* data) { struct android_poll_source *poll_source = (struct android_poll_source *)data; printf("AInputQueue_attachLooper called: queue: %p, looper: %p, ident: %d, callback %p, data: %p, process_func: %p\n", queue, looper, ident, callback, poll_source, poll_source->process); - GtkEventController *controller = (GtkEventController *)queue; // TODO: is there a saner thing to pass here? - - g_signal_connect(controller, "event", G_CALLBACK(on_event), (gpointer)poll_source); + int input_queue_pipe[2]; + if (pipe(input_queue_pipe)) { + fprintf(stderr, "could not create pipe: %s", strerror(errno)); + return; + } + fcntl(input_queue_pipe[0], F_SETFL, O_NONBLOCK); + ALooper_addFd(looper, input_queue_pipe[0], ident, (1 << 0)/*? ALOOPER_EVENT_INPUT*/, callback, data); + g_signal_connect(queue->controller, "event", G_CALLBACK(on_event), (gpointer)input_queue_pipe[1]); + queue->fd = input_queue_pipe[0]; } -int32_t AInputQueue_getEvent(AInputQueue* queue, struct AInputEvent** outEvent) +int32_t AInputQueue_getEvent(struct input_queue *queue, struct AInputEvent** outEvent) { - if(fixme_ugly_are_there_events == 0) { + if(read(queue->fd, &fixme_ugly_current_event, sizeof(struct AInputEvent)) == sizeof(struct AInputEvent)) { *outEvent = &fixme_ugly_current_event; - return fixme_ugly_are_there_events; + return 0; } else { return -1; // no events or error } @@ -230,5 +252,5 @@ int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, struct AInputEvent* eve void AInputQueue_finishEvent(AInputQueue* queue, struct AInputEvent* event, int handled) { - fixme_ugly_are_there_events = -1; + // should we do something here? }