From: Po Lu Date: Sat, 1 Jun 2024 07:41:54 +0000 (+0800) Subject: Implement touch screen events on PGTK X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=7c2327549485eb0abf9befbca20d8573729d60c3;p=emacs.git Implement touch screen events on PGTK * etc/NEWS: Better qualify entry for touch screen events. * lisp/loadup.el (featurep 'pgtk): Load touch-screen.el. * lisp/touch-screen.el: Revise list of systems where touch screen events are reported. * src/gtkutil.c (xg_create_frame_widgets): Request GDK_TOUCH_MASK. * src/pgtkfns.c (pgtk_frame_parm_handlers, tip_window): Pacify compiler warning. * src/pgtkterm.c (pgtk_free_frame_resources): Free touch points linked to this frame. (pgtk_link_touch_point, pgtk_unlink_touch_point) (pgtk_unlink_touch_points, pgtk_find_touch_point): New functions, ported from X. (touch_event_cb): New event callback. (pgtk_set_event_handler): Register `touch_event_cb' as handler for `touch-event'. (pgtk_delete_display): Free residual touch points on this display. * src/pgtkterm.h (struct pgtk_touch_point): New structure. (struct pgtk_display_info) : New field. (cherry picked from commit 2b7056db424ab0f8bf9e96b5a3c6aa12a3debf48) --- diff --git a/etc/NEWS b/etc/NEWS index 2b28d3c12a5..b97f8219317 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -507,10 +507,11 @@ function call. +++ ** Emacs now has better support for touchscreen devices. -Many touch screen gestures are now implemented and translated into -mouse or gesture events, and support for tapping tool bar buttons and -opening menus has been written. Countless packages, such as Dired and -Custom have been adjusted to better understand touch screen input. +On systems that understand them, many touch screen gestures are now +implemented and translated into mouse or gesture events, and support for +tapping tool bar buttons and opening menus has been written. Countless +packages, such as Dired and Custom have been adjusted to better +understand touch screen input. --- ** On X, Emacs now supports input methods which perform "string conversion". diff --git a/lisp/loadup.el b/lisp/loadup.el index b8844ea042c..eea87d4b940 100644 --- a/lisp/loadup.el +++ b/lisp/loadup.el @@ -369,6 +369,7 @@ (if (featurep 'pgtk) (progn (load "pgtk-dnd") + (load "touch-screen") (load "term/common-win") (load "term/pgtk-win"))) (if (fboundp 'x-create-frame) diff --git a/lisp/touch-screen.el b/lisp/touch-screen.el index 436b8d0954c..b77f3a6e07d 100644 --- a/lisp/touch-screen.el +++ b/lisp/touch-screen.el @@ -23,8 +23,8 @@ ;;; Commentary: ;; This file provides code to recognize simple touch screen gestures. -;; It is used on X and Android, currently the only systems where Emacs -;; supports touch input. +;; It is used on X, PGTK, and Android, currently the only systems where +;; Emacs supports touch input. ;; ;; See (elisp)Touchscreen Events for a description of the details of ;; touch events. diff --git a/src/gtkutil.c b/src/gtkutil.c index 7de8eba0aa1..d57627f152f 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -1669,6 +1669,7 @@ xg_create_frame_widgets (struct frame *f) #ifdef HAVE_PGTK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK + | GDK_TOUCH_MASK #endif | GDK_VISIBILITY_NOTIFY_MASK); diff --git a/src/pgtkfns.c b/src/pgtkfns.c index 6a8efb6d0bf..bdc6c5836fa 100644 --- a/src/pgtkfns.c +++ b/src/pgtkfns.c @@ -945,6 +945,7 @@ unless TYPE is `png'. */) return pgtk_cr_export_frames (frames, surface_type); } +extern frame_parm_handler pgtk_frame_parm_handlers[]; frame_parm_handler pgtk_frame_parm_handlers[] = { gui_set_autoraise, /* generic OK */ @@ -2619,7 +2620,7 @@ static Lisp_Object tip_frame; /* The window-system window corresponding to the frame of the currently visible tooltip. */ -GtkWidget *tip_window; +static GtkWidget *tip_window; /* A timer that hides or deletes the currently visible tooltip when it fires. */ diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 8d9a47b932f..886f115c391 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -450,6 +450,8 @@ pgtk_frame_raise_lower (struct frame *f, bool raise_flag) /* Free X resources of frame F. */ +static void pgtk_unlink_touch_points (struct frame *); + void pgtk_free_frame_resources (struct frame *f) { @@ -462,6 +464,7 @@ pgtk_free_frame_resources (struct frame *f) block_input (); + pgtk_unlink_touch_points (f); #ifdef HAVE_XWIDGETS kill_frame_xwidget_views (f); #endif @@ -6524,6 +6527,230 @@ drag_drop (GtkWidget *widget, GdkDragContext *context, return TRUE; } + + +/* Touch screen events. */ + +/* Record a touch sequence with the identifier DETAIL from the given + FRAME on the specified DPYINFO. Round X and Y and record them as its + current position, assign an identifier to the touch sequence suitable + for reporting to Lisp, and return the same. */ + +static EMACS_INT +pgtk_link_touch_point (struct pgtk_display_info *dpyinfo, + GdkEventSequence *detail, gdouble x, + gdouble y, struct frame *frame) +{ + struct pgtk_touch_point *touchpoint; + static EMACS_INT local_detail; + + /* Assign an identifier suitable for reporting to Lisp. On builds + with 64-bit Lisp_Object, this is largely a theoretical problem, but + CARD32s easily overflow 32-bit systems, as they are not specific to + X clients (e.g. Emacs) but grow uniformly across all of them. */ + + if (FIXNUM_OVERFLOW_P (local_detail)) + local_detail = 0; + + touchpoint = xmalloc (sizeof *touchpoint); + touchpoint->next = dpyinfo->touchpoints; + touchpoint->x = lrint (x); + touchpoint->y = lrint (y); + touchpoint->number = detail; + touchpoint->local_detail = local_detail++; + touchpoint->frame = frame; + dpyinfo->touchpoints = touchpoint; + return touchpoint->local_detail; +} + +/* Free and remove the touch sequence with the identifier DETAIL. + DPYINFO is the display in which the touch sequence should be + recorded. If such a touch sequence exists, return its local + identifier in *LOCAL_DETAIL. + + Value is 0 if no touch sequence by that identifier exists inside + DPYINFO, or 1 if a touch sequence has been found. */ + +static int +pgtk_unlink_touch_point (GdkEventSequence *detail, + struct pgtk_display_info *dpyinfo, + EMACS_INT *local_detail) +{ + struct pgtk_touch_point *last, *tem; + + for (last = NULL, tem = dpyinfo->touchpoints; tem; + last = tem, tem = tem->next) + { + if (tem->number == detail) + { + if (!last) + dpyinfo->touchpoints = tem->next; + else + last->next = tem->next; + + *local_detail = tem->local_detail; + xfree (tem); + + return 1; + } + } + + return 0; +} + +/* Unlink all touch points associated with the frame F. This is done + upon destroying F's window (or its being destroyed), because touch + point delivery after that point is undefined. */ + +static void +pgtk_unlink_touch_points (struct frame *f) +{ + struct pgtk_touch_point **next, *last; + struct pgtk_display_info *dpyinfo; + + /* Now unlink all touch points on F's display matching F. */ + + dpyinfo = FRAME_DISPLAY_INFO (f); + for (next = &dpyinfo->touchpoints; (last = *next);) + { + if (last->frame == f) + { + *next = last->next; + xfree (last); + } + else + next = &last->next; + } +} + +/* Return the data associated with a touch sequence DETAIL recorded by + `pgtk_link_touch_point' from DPYINFO, or NULL if it can't be + found. */ + +static struct pgtk_touch_point * +pgtk_find_touch_point (struct pgtk_display_info *dpyinfo, + GdkEventSequence *detail) +{ + struct pgtk_touch_point *point; + + for (point = dpyinfo->touchpoints; point; point = point->next) + { + if (point->number == detail) + return point; + } + + return NULL; +} + +static bool +touch_event_cb (GtkWidget *self, GdkEvent *event, gpointer user_data) +{ + struct pgtk_display_info *dpyinfo; + struct frame *f; + EMACS_INT local_detail; + union buffered_input_event inev; + struct pgtk_touch_point *touchpoint; + Lisp_Object arg = Qnil; + int state; + + EVENT_INIT (inev.ie); + + f = pgtk_any_window_to_frame (gtk_widget_get_window (self)); + eassert (f); + dpyinfo = FRAME_DISPLAY_INFO (f); + switch (event->type) + { + case GDK_TOUCH_BEGIN: + + /* Verify that no touch point with this identifier is already at + large. */ + if (pgtk_find_touch_point (dpyinfo, event->touch.sequence)) + break; + + /* Record this in the display structure. */ + local_detail = pgtk_link_touch_point (dpyinfo, event->touch.sequence, + event->touch.x, event->touch.y, + f); + /* Generate the input event. */ + inev.ie.kind = TOUCHSCREEN_BEGIN_EVENT; + inev.ie.timestamp = event->touch.time; + XSETFRAME (inev.ie.frame_or_window, f); + XSETINT (inev.ie.x, lrint (event->touch.x)); + XSETINT (inev.ie.y, lrint (event->touch.y)); + XSETINT (inev.ie.arg, local_detail); + break; + + case GDK_TOUCH_UPDATE: + touchpoint = pgtk_find_touch_point (dpyinfo, + event->touch.sequence); + + if (!touchpoint + /* Don't send this event if nothing has changed + either. */ + || (touchpoint->x == lrint (event->touch.x) + && touchpoint->y == lrint (event->touch.y))) + break; + + /* Construct the input event. */ + touchpoint->x = lrint (event->touch.x); + touchpoint->y = lrint (event->touch.y); + inev.ie.kind = TOUCHSCREEN_UPDATE_EVENT; + inev.ie.timestamp = event->touch.time; + XSETFRAME (inev.ie.frame_or_window, f); + + for (touchpoint = dpyinfo->touchpoints; + touchpoint; touchpoint = touchpoint->next) + { + if (touchpoint->frame == f) + arg = Fcons (list3i (touchpoint->x, touchpoint->y, + touchpoint->local_detail), + arg); + } + + inev.ie.arg = arg; + break; + + case GDK_TOUCH_END: + case GDK_TOUCH_CANCEL: + /* Remove this touch point's record, also establishing its + existence. */ + state = pgtk_unlink_touch_point (event->touch.sequence, + dpyinfo, &local_detail); + /* If it did exist... */ + if (state) + { + /* ... generate a suitable event. */ + inev.ie.kind = TOUCHSCREEN_END_EVENT; + inev.ie.timestamp = event->touch.time; + inev.ie.modifiers = (event->type != GDK_TOUCH_END); + + XSETFRAME (inev.ie.frame_or_window, f); + XSETINT (inev.ie.x, lrint (event->touch.x)); + XSETINT (inev.ie.y, lrint (event->touch.y)); + XSETINT (inev.ie.arg, local_detail); + } + break; + + default: + break; + } + + /* If the above produced a workable event, report the name of the + device that gave rise to it. */ + + if (inev.ie.kind != NO_EVENT) + { + inev.ie.device = pgtk_get_device_for_event (dpyinfo, event); + evq_enqueue (&inev); + } + + return inev.ie.kind != NO_EVENT; +} + + + +/* Callbacks for sundries. */ + static void pgtk_monitors_changed_cb (GdkScreen *screen, gpointer user_data) { @@ -6540,6 +6767,8 @@ pgtk_monitors_changed_cb (GdkScreen *screen, gpointer user_data) static gboolean pgtk_selection_event (GtkWidget *, GdkEvent *, gpointer); + + void pgtk_set_event_handler (struct frame *f) { @@ -6609,6 +6838,8 @@ pgtk_set_event_handler (struct frame *f) G_CALLBACK (pgtk_selection_event), NULL); g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "selection-notify-event", G_CALLBACK (pgtk_selection_event), NULL); + g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "touch-event", + G_CALLBACK (touch_event_cb), NULL); g_signal_connect (G_OBJECT (FRAME_GTK_WIDGET (f)), "event", G_CALLBACK (pgtk_handle_event), NULL); } @@ -7028,6 +7259,7 @@ static void pgtk_delete_display (struct pgtk_display_info *dpyinfo) { struct terminal *t; + struct pgtk_touch_point *last, *tem; /* Close all frames and delete the generic struct terminal for this X display. */ @@ -7049,6 +7281,15 @@ pgtk_delete_display (struct pgtk_display_info *dpyinfo) tail->next = tail->next->next; } + /* Free remaining touchpoints. */ + tem = dpyinfo->touchpoints; + while (tem) + { + last = tem; + tem = tem->next; + xfree (last); + } + pgtk_free_devices (dpyinfo); xfree (dpyinfo); } diff --git a/src/pgtkterm.h b/src/pgtkterm.h index 8072d963691..90ca2aa22d4 100644 --- a/src/pgtkterm.h +++ b/src/pgtkterm.h @@ -50,13 +50,38 @@ struct pgtk_bitmap_record struct pgtk_device_t { + /* Lisp name of the device. */ + Lisp_Object name; + + /* Seat to which this device appertains. */ GdkSeat *seat; + + /* Pointer to this device's GdkDevice object. */ GdkDevice *device; - Lisp_Object name; + /* Next device in this chain. */ struct pgtk_device_t *next; }; +struct pgtk_touch_point +{ + /* The detail code reported to Lisp. */ + EMACS_INT local_detail; + + /* The frame associated with this touch point. */ + struct frame *frame; + + /* The next touch point in this list. */ + struct pgtk_touch_point *next; + + /* The touchpoint detail. This purports to be a pointer, but is a + number. */ + GdkEventSequence *number; + + /* The last known rounded X and Y positions of the touchpoint. */ + int x, y; +}; + #define RGB_TO_ULONG(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #define ARGB_TO_ULONG(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) @@ -131,10 +156,13 @@ struct pgtk_display_info /* This says how to access this display through GDK. */ GdkDisplay *gdpy; - /* An alias defined to make porting X code easier. */ + /* An alias defined to facilitate porting X code. */ GdkDisplay *display; }; + /* List of active touch-points. */ + struct pgtk_touch_point *touchpoints; + /* This is a cons cell of the form (NAME . FONT-LIST-CACHE). */ Lisp_Object name_list_element;