unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
       [not found] <87zgq4uvh0.fsf.ref@yahoo.com>
@ 2021-11-16 12:38 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-16 13:39   ` Robert Pluim
  0 siblings, 1 reply; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-16 12:38 UTC (permalink / raw)
  To: 51891

[-- Attachment #1: Type: text/plain, Size: 868 bytes --]

I've implemented support for exposing the pixel delta of XInput 2 wheel
events to Lisp code.  With GC turned off, utilizing a modified
pixel-scroll.el with this support results in a quite pleasant
experience, but pixel-scroll still GCs too much to be useful.

Pixel based scrolling is a popular feature in modern X applications, and
does make life a lot easier in many cases.  The existing support is
inadequate as it cannot utilize the detailed delta information available
from modern touch-based scroll wheels, which this patch enables.

The XInput 2 support isn't the best from a code duplication POV, but the
behavior of the XInput 2 code often differs in subtle ways from that of
the Core Input code, and trying to combine the two together would result
in a more catastrophic mess than leaving them apart.

Obviously, here is the original XInput 2 support patch:

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-support-for-event-processing-via-XInput-2.patch --]
[-- Type: text/x-patch, Size: 57750 bytes --]

From 39458cca1d83a9fcd9e4ced05cef67468d7cd21c Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Sat, 16 Oct 2021 13:15:36 +0800
Subject: [PATCH 1/2] Add support for event processing via XInput 2

* configure.ac: Add an option to use XInput 2 if available.
* src/Makefile.in (XINPUT_LIBS, XINPUT_CFLAGS): New variables.
(EMACS_CFLAGS): Add Xinput CFLAGS.
(LIBES): Add XInput libs.
* src/xmenu.c (popup_activated_flag): Expose flag if
XInput 2 is available.
* src/xfns.c (x_window): Set XInput 2 event mask.
(setup_xi_event_mask): New function.
(syms_of_xfns): Provide XInput 2 feature.
* src/xterm.c (x_detect_focus_change): Handle XInput 2
GenericEvents.
(handle_one_xevent): Handle XInput 2 events.
(x_term_init): Ask the server for XInput 2 support and set
xkb_desc if available.
(x_delete_terminal): Free XKB kb desc if it exists, and free
XI2 devices if they exist.
(xi_grab_or_ungrab_device)
(xi_reset_scroll_valuators_for_device_id)
(x_free_xi_devices, x_init_master_valuators): New functions.
(x_get_scroll_valuator_delta): New function.
(init_xterm): Don't tell GTK to only use Core Input when built
with XInput 2 support.
* src/xterm.h (struct x_display_info): Add fields for XKB
and XI2 support.
* src/gtkutil.c (xg_event_is_for_menubar): Handle
XIDeviceEvents.
(xg_is_menu_window): New function.
(xg_event_is_for_scrollbar): Handle XIDeviceEvents.
* etc/NEWS: Document changes.

* lisp/mwheel.el (mouse-wheel-down-alternate-event)
(mouse-wheel-up-alternate-event)
(mouse-wheel-left-alternate-event)
(mouse-wheel-right-alternate-event): New user options.

(mouse-wheel-text-scale)
(mwheel-scroll): Test for alternate events.
(mouse-wheel--setup-bindings): Set up bindings for alternate
buttons.
---
 configure.ac    |   22 +
 etc/NEWS        |   17 +
 lisp/mwheel.el  |   66 ++-
 src/Makefile.in |    7 +-
 src/gtkutil.c   |   72 ++-
 src/gtkutil.h   |    4 +
 src/xfns.c      |   50 +++
 src/xmenu.c     |    4 +
 src/xterm.c     | 1107 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/xterm.h     |   44 ++
 10 files changed, 1372 insertions(+), 21 deletions(-)

diff --git a/configure.ac b/configure.ac
index c231c2ceae..239bf72f71 100644
--- a/configure.ac
+++ b/configure.ac
@@ -487,6 +487,7 @@ AC_DEFUN
 OPTION_DEFAULT_ON([threads],[don't compile with elisp threading support])
 OPTION_DEFAULT_OFF([native-compilation],[compile with Emacs Lisp native compiler support])
 OPTION_DEFAULT_OFF([cygwin32-native-compilation],[use native compilation on 32-bit Cygwin])
+OPTION_DEFAULT_OFF([xinput2],[use version 2.0 the X Input Extension for input])
 
 AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB],
  [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, w32, no)])],
@@ -4237,6 +4238,26 @@ AC_DEFUN
 AC_SUBST(XFIXES_CFLAGS)
 AC_SUBST(XFIXES_LIBS)
 
+## Use XInput 2.0 if available
+HAVE_XINPUT2=no
+if test "${HAVE_X11}" = "yes" && test "${with_xinput2}" != "no"; then
+   EMACS_CHECK_MODULES([XINPUT], [xi])
+   if test $HAVE_XINPUT = yes; then
+     # Now check for XInput2.h
+     AC_CHECK_HEADER(X11/extensions/XInput2.h,
+       [AC_CHECK_LIB(Xi, XIGrabButton, HAVE_XINPUT2=yes)])
+   fi
+   if test $HAVE_XINPUT2 = yes; then
+     AC_DEFINE(HAVE_XINPUT2, 1, [Define to 1 if the X Input Extension version 2.0 is present.])
+     if test "$USE_GTK_TOOLKIT" = "GTK2"; then
+       AC_MSG_WARN([You are building Emacs with GTK+ 2 and the X Input Extension version 2.
+This might lead to problems if your version of GTK+ is not built with support for XInput 2.])
+     fi
+   fi
+fi
+AC_SUBST(XINPUT_CFLAGS)
+AC_SUBST(XINPUT_LIBS)
+
 ### Use Xdbe (-lXdbe) if available
 HAVE_XDBE=no
 if test "${HAVE_X11}" = "yes"; then
@@ -6011,6 +6032,7 @@ AC_DEFUN
   Does Emacs support legacy unexec dumping?               ${with_unexec}
   Which dumping strategy does Emacs use?                  ${with_dumping}
   Does Emacs have native lisp compiler?                   ${HAVE_NATIVE_COMP}
+  Does Emacs use version 2 of the the X Input Extension?  ${HAVE_XINPUT2}
 "])
 
 if test -n "${EMACSDATA}"; then
diff --git a/etc/NEWS b/etc/NEWS
index 80be6c0e49..ca6b946843 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -30,6 +30,14 @@ The file is typically installed using a file name akin to
 If a constant file name is required, the file can be renamed to
 "emacs.pdmp", and Emacs will find it during startup anyway.
 
+** Emacs now supports use of XInput 2 for input events.
+If your X server has support and you have the XInput 2 development headers
+installed, you can configure Emacs with the option '--with-xinput2' to enable
+this support.
+
+The named feature `xinput2' can be used to test for the presence of
+XInput 2 support from Lisp programs.
+
 \f
 * Startup Changes in Emacs 29.1
 
@@ -217,6 +225,15 @@ The user option 'comint-terminfo-terminal' and variable
 'system-uses-terminfo' can now be set as connection-local variables to
 change the terminal used on a remote host.
 
+** Mwheel
+
+---
+*** New user options for alternate wheel events.
+The options 'mouse-wheel-down-alternate-event', 'mouse-wheel-up-alternate-event',
+'mouse-wheel-left-alternate-event', and 'mouse-wheel-right-alternate-event' have
+been added to better support systems where two kinds of wheel events can be
+received.
+
 \f
 * Changes in Specialized Modes and Packages in Emacs 29.1
 
diff --git a/lisp/mwheel.el b/lisp/mwheel.el
index 51410e3ef4..3d0b8f07cb 100644
--- a/lisp/mwheel.el
+++ b/lisp/mwheel.el
@@ -63,6 +63,13 @@ mouse-wheel-down-event
   :type 'symbol
   :set 'mouse-wheel-change-button)
 
+(defcustom mouse-wheel-down-alternate-event
+  (when (featurep 'xinput2) 'wheel-up)
+  "Alternative wheel down event to consider."
+  :group 'mouse
+  :type 'symbol
+  :set 'mouse-wheel-change-button)
+
 (defcustom mouse-wheel-up-event
   (if (or (featurep 'w32-win) (featurep 'ns-win))
       'wheel-down
@@ -72,6 +79,13 @@ mouse-wheel-up-event
   :type 'symbol
   :set 'mouse-wheel-change-button)
 
+(defcustom mouse-wheel-up-alternate-event
+  (when (featurep 'xinput2) 'wheel-down)
+  "Alternative wheel up event to consider."
+  :group 'mouse
+  :type 'symbol
+  :set 'mouse-wheel-change-button)
+
 (defcustom mouse-wheel-click-event 'mouse-2
   "Event that should be temporarily inhibited after mouse scrolling.
 The mouse wheel is typically on the mouse-2 button, so it may easily
@@ -226,12 +240,20 @@ mouse-wheel-left-event
     'mouse-6)
   "Event used for scrolling left.")
 
+(defvar mouse-wheel-left-alternate-event
+  (when (featurep 'xinput2) 'wheel-left)
+  "Alternative wheel left event to consider.")
+
 (defvar mouse-wheel-right-event
   (if (or (featurep 'w32-win) (featurep 'ns-win))
       'wheel-right
     'mouse-7)
   "Event used for scrolling right.")
 
+(defvar mouse-wheel-right-alternate-event
+  (when (featurep 'xinput2) 'wheel-right)
+  "Alternative wheel right event to consider.")
+
 (defun mouse-wheel--get-scroll-window (event)
   "Return window for mouse wheel event EVENT.
 If `mouse-wheel-follow-mouse' is non-nil, return the window that
@@ -296,14 +318,16 @@ mwheel-scroll
     (condition-case nil
         (unwind-protect
 	    (let ((button (mwheel-event-button event)))
-              (cond ((and (eq amt 'hscroll) (eq button mouse-wheel-down-event))
+              (cond ((and (eq amt 'hscroll) (memq button (list mouse-wheel-down-event
+                                                               mouse-wheel-down-alternate-event)))
                      (when (and (natnump arg) (> arg 0))
                        (setq mouse-wheel-scroll-amount-horizontal arg))
                      (funcall (if mouse-wheel-flip-direction
                                   mwheel-scroll-left-function
                                 mwheel-scroll-right-function)
                               mouse-wheel-scroll-amount-horizontal))
-                    ((eq button mouse-wheel-down-event)
+                    ((memq button (list mouse-wheel-down-event
+                                        mouse-wheel-down-alternate-event))
                      (condition-case nil (funcall mwheel-scroll-down-function amt)
                        ;; Make sure we do indeed scroll to the beginning of
                        ;; the buffer.
@@ -318,23 +342,27 @@ mwheel-scroll
                           ;; for a reason that escapes me.  This problem seems
                           ;; to only affect scroll-down.  --Stef
                           (set-window-start (selected-window) (point-min))))))
-                    ((and (eq amt 'hscroll) (eq button mouse-wheel-up-event))
+                    ((and (eq amt 'hscroll) (memq button (list mouse-wheel-up-event
+                                                               mouse-wheel-up-alternate-event)))
                      (when (and (natnump arg) (> arg 0))
                        (setq mouse-wheel-scroll-amount-horizontal arg))
                      (funcall (if mouse-wheel-flip-direction
                                   mwheel-scroll-right-function
                                 mwheel-scroll-left-function)
                               mouse-wheel-scroll-amount-horizontal))
-                    ((eq button mouse-wheel-up-event)
+                    ((memq button (list mouse-wheel-up-event
+                                        mouse-wheel-up-alternate-event))
                      (condition-case nil (funcall mwheel-scroll-up-function amt)
                        ;; Make sure we do indeed scroll to the end of the buffer.
                        (end-of-buffer (while t (funcall mwheel-scroll-up-function)))))
-                    ((eq button mouse-wheel-left-event) ; for tilt scroll
+                    ((memq button (list mouse-wheel-left-event
+                                        mouse-wheel-left-alternate-event)) ; for tilt scroll
                      (when mouse-wheel-tilt-scroll
                        (funcall (if mouse-wheel-flip-direction
                                     mwheel-scroll-right-function
                                   mwheel-scroll-left-function) amt)))
-                    ((eq button mouse-wheel-right-event) ; for tilt scroll
+                    ((memq button (list mouse-wheel-right-event
+                                        mouse-wheel-right-alternate-event)) ; for tilt scroll
                      (when mouse-wheel-tilt-scroll
                        (funcall (if mouse-wheel-flip-direction
                                     mwheel-scroll-left-function
@@ -378,9 +406,11 @@ mouse-wheel-text-scale
         (button (mwheel-event-button event)))
     (select-window scroll-window 'mark-for-redisplay)
     (unwind-protect
-        (cond ((eq button mouse-wheel-down-event)
+        (cond ((memq button (list mouse-wheel-down-event
+                                  mouse-wheel-down-alternate-event))
                (text-scale-increase 1))
-              ((eq button mouse-wheel-up-event)
+              ((eq button (list mouse-wheel-up-event
+                                mouse-wheel-up-alternate-event))
                (text-scale-decrease 1)))
       (select-window selected-window))))
 
@@ -432,15 +462,23 @@ mouse-wheel--setup-bindings
     (cond
      ;; Bindings for changing font size.
      ((and (consp binding) (eq (cdr binding) 'text-scale))
-      (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event))
-        (mouse-wheel--add-binding `[,(list (caar binding) event)]
-                                  'mouse-wheel-text-scale)))
+      (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event
+                           mouse-wheel-down-alternate-event
+                           mouse-wheel-up-alternate-event))
+        (when event
+          (mouse-wheel--add-binding `[,(list (caar binding) event)]
+                                    'mouse-wheel-text-scale))))
      ;; Bindings for scrolling.
      (t
       (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event
-                           mouse-wheel-left-event mouse-wheel-right-event))
-        (dolist (key (mouse-wheel--create-scroll-keys binding event))
-          (mouse-wheel--add-binding key 'mwheel-scroll)))))))
+                           mouse-wheel-left-event mouse-wheel-right-event
+                           mouse-wheel-down-alternate-event
+                           mouse-wheel-up-alternate-event
+                           mouse-wheel-left-alternate-event
+                           mouse-wheel-right-alternate-event))
+        (when event
+          (dolist (key (mouse-wheel--create-scroll-keys binding event))
+            (mouse-wheel--add-binding key 'mwheel-scroll))))))))
 
 (when mouse-wheel-mode
   (mouse-wheel--setup-bindings))
diff --git a/src/Makefile.in b/src/Makefile.in
index 4c5535f8ad..0aaaf91d39 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -258,6 +258,9 @@ XINERAMA_CFLAGS =
 XFIXES_LIBS = @XFIXES_LIBS@
 XFIXES_CFLAGS = @XFIXES_CFLAGS@
 
+XINPUT_LIBS = @XINPUT_LIBS@
+XINPUT_CFLAGS = @XINPUT_CFLAGS@
+
 XDBE_LIBS = @XDBE_LIBS@
 XDBE_CFLAGS = @XDBE_CFLAGS@
 
@@ -374,7 +377,7 @@ EMACS_CFLAGS=
   $(GNUSTEP_CFLAGS) $(CFLAGS_SOUND) $(RSVG_CFLAGS) $(IMAGEMAGICK_CFLAGS) \
   $(PNG_CFLAGS) $(LIBXML2_CFLAGS) $(LIBGCCJIT_CFLAGS) $(DBUS_CFLAGS) \
   $(XRANDR_CFLAGS) $(XINERAMA_CFLAGS) $(XFIXES_CFLAGS) $(XDBE_CFLAGS) \
-  $(WEBKIT_CFLAGS) $(WEBP_CFLAGS) $(LCMS2_CFLAGS) \
+  $(XINPUT_CFLAGS) $(WEBP_CFLAGS) $(WEBKIT_CFLAGS) $(LCMS2_CFLAGS) \
   $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \
   $(HARFBUZZ_CFLAGS) $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \
   $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) \
@@ -524,7 +527,7 @@ LIBES =
    $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(HARFBUZZ_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \
    $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) $(LCMS2_LIBS) \
    $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) $(LIBSYSTEMD_LIBS) \
-   $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS)
+   $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS) $(XINPUT_LIBS)
 
 ## FORCE it so that admin/unidata can decide whether this file is
 ## up-to-date.  Although since charprop depends on bootstrap-emacs,
diff --git a/src/gtkutil.c b/src/gtkutil.c
index a9eabf47d8..9e676cd025 100644
--- a/src/gtkutil.c
+++ b/src/gtkutil.c
@@ -47,6 +47,10 @@ Copyright (C) 2003-2021 Free Software Foundation, Inc.
 
 #include <gdk/gdkkeysyms.h>
 
+#ifdef HAVE_XINPUT2
+#include <X11/extensions/XInput2.h>
+#endif
+
 #ifdef HAVE_XFT
 #include <X11/Xft/Xft.h>
 #endif
@@ -839,6 +843,23 @@ my_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
 }
 #endif
 
+#if defined HAVE_GTK3 && defined HAVE_XINPUT2
+bool
+xg_is_menu_window (Display *dpy, Window wdesc)
+{
+  GtkWidget *gwdesc = xg_win_to_widget (dpy, wdesc);
+
+  if (GTK_IS_WINDOW (gwdesc))
+    {
+      GtkWidget *fw = gtk_bin_get_child (GTK_BIN (gwdesc));
+      if (GTK_IS_MENU (fw))
+	return true;
+    }
+
+  return false;
+}
+#endif
+
 /* Make a geometry string and pass that to GTK.  It seems this is the
    only way to get geometry position right if the user explicitly
    asked for a position when starting Emacs.
@@ -3589,6 +3610,18 @@ xg_event_is_for_menubar (struct frame *f, const XEvent *event)
 
   if (! x->menubar_widget) return 0;
 
+#ifdef HAVE_XINPUT2
+  XIDeviceEvent *xev = (XIDeviceEvent *) event->xcookie.data;
+  if (event->type == GenericEvent) /* XI_ButtonPress or XI_ButtonRelease */
+    {
+      if (! (xev->event_x >= 0
+	     && xev->event_x < FRAME_PIXEL_WIDTH (f)
+	     && xev->event_y >= 0
+	     && xev->event_y < FRAME_MENUBAR_HEIGHT (f)))
+	return 0;
+    }
+  else
+#endif
   if (! (event->xbutton.x >= 0
          && event->xbutton.x < FRAME_PIXEL_WIDTH (f)
          && event->xbutton.y >= 0
@@ -3597,7 +3630,12 @@ xg_event_is_for_menubar (struct frame *f, const XEvent *event)
     return 0;
 
   gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
-  gw = gdk_x11_window_lookup_for_display (gdpy, event->xbutton.window);
+#ifdef HAVE_XINPUT2
+  if (event->type == GenericEvent)
+    gw = gdk_x11_window_lookup_for_display (gdpy, xev->event);
+  else
+#endif
+    gw = gdk_x11_window_lookup_for_display (gdpy, event->xbutton.window);
   if (! gw) return 0;
   gevent.any.window = gw;
   gevent.any.type = GDK_NOTHING;
@@ -4244,7 +4282,20 @@ xg_event_is_for_scrollbar (struct frame *f, const XEvent *event)
 {
   bool retval = 0;
 
+#ifdef HAVE_XINPUT2
+  XIDeviceEvent *xev = (XIDeviceEvent *) event->xcookie.data;
+  if (f && ((FRAME_DISPLAY_INFO (f)->supports_xi2
+	     && event->type == GenericEvent
+	     && (event->xgeneric.extension
+		 == FRAME_DISPLAY_INFO (f)->xi2_opcode)
+	     && ((event->xgeneric.evtype == XI_ButtonPress
+		  && xev->detail < 4)
+		 || (event->xgeneric.evtype == XI_Motion)))
+	    || (event->type == ButtonPress
+		&& event->xbutton.button < 4)))
+#else
   if (f && event->type == ButtonPress && event->xbutton.button < 4)
+#endif /* HAVE_XINPUT2 */
     {
       /* Check if press occurred outside the edit widget.  */
       GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
@@ -4262,10 +4313,29 @@ xg_event_is_for_scrollbar (struct frame *f, const XEvent *event)
       gwin = gdk_display_get_window_at_pointer (gdpy, NULL, NULL);
 #endif
       retval = gwin != gtk_widget_get_window (f->output_data.x->edit_widget);
+#ifdef HAVE_XINPUT2
+      GtkWidget *grab = gtk_grab_get_current ();
+      if (event->type == GenericEvent
+	  && event->xgeneric.evtype == XI_Motion)
+	retval = retval || (grab && GTK_IS_SCROLLBAR (grab));
+#endif
     }
+#ifdef HAVE_XINPUT2
+  else if (f && ((FRAME_DISPLAY_INFO (f)->supports_xi2
+		  && event->type == GenericEvent
+		  && (event->xgeneric.extension
+		      == FRAME_DISPLAY_INFO (f)->xi2_opcode)
+		  && ((event->xgeneric.evtype == XI_ButtonRelease
+		       && xev->detail < 4)
+		      || (event->xgeneric.evtype == XI_Motion)))
+		 || ((event->type == ButtonRelease
+		      && event->xbutton.button < 4)
+		     || event->type == MotionNotify)))
+#else
   else if (f
            && ((event->type == ButtonRelease && event->xbutton.button < 4)
                || event->type == MotionNotify))
+#endif /* HAVE_XINPUT2 */
     {
       /* If we are releasing or moving the scroll bar, it has the grab.  */
       GtkWidget *w = gtk_grab_get_current ();
diff --git a/src/gtkutil.h b/src/gtkutil.h
index 31a12cd5d3..95dd75b7fa 100644
--- a/src/gtkutil.h
+++ b/src/gtkutil.h
@@ -192,6 +192,10 @@ #define XG_ITEM_DATA "emacs_menuitem"
 extern void xg_print_frames_dialog (Lisp_Object);
 #endif
 
+#if defined HAVE_GTK3 && defined HAVE_XINPUT2
+extern bool xg_is_menu_window (Display *dpy, Window);
+#endif
+
 /* Mark all callback data that are Lisp_object:s during GC.  */
 extern void xg_mark_data (void);
 
diff --git a/src/xfns.c b/src/xfns.c
index 785ae3baca..b33b40b330 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -57,6 +57,10 @@ Copyright (C) 1989, 1992-2021 Free Software Foundation, Inc.
 #include <X11/extensions/Xdbe.h>
 #endif
 
+#ifdef HAVE_XINPUT2
+#include <X11/extensions/XInput2.h>
+#endif
+
 #ifdef USE_X_TOOLKIT
 #include <X11/Shell.h>
 
@@ -2912,6 +2916,37 @@ initial_set_up_x_back_buffer (struct frame *f)
   unblock_input ();
 }
 
+#if defined HAVE_XINPUT2 && !defined USE_GTK
+static void
+setup_xi_event_mask (struct frame *f)
+{
+  XIEventMask mask;
+  ptrdiff_t l = XIMaskLen (XI_LASTEVENT);
+  unsigned char *m;
+
+  mask.mask = m = alloca (l);
+  memset (m, 0, l);
+  mask.mask_len = l;
+  mask.deviceid = XIAllMasterDevices;
+
+  XISetMask (m, XI_ButtonPress);
+  XISetMask (m, XI_ButtonRelease);
+  XISetMask (m, XI_KeyPress);
+  XISetMask (m, XI_KeyRelease);
+  XISetMask (m, XI_Motion);
+  XISetMask (m, XI_Enter);
+  XISetMask (m, XI_Leave);
+  XISetMask (m, XI_FocusIn);
+  XISetMask (m, XI_FocusOut);
+  XISetMask (m, XI_PropertyEvent);
+  XISetMask (m, XI_HierarchyChanged);
+  XISetMask (m, XI_DeviceChanged);
+  XISelectEvents (FRAME_X_DISPLAY (f),
+		  FRAME_X_WINDOW (f),
+		  &mask, 1);
+}
+#endif
+
 #ifdef USE_X_TOOLKIT
 
 /* Create and set up the X widget for frame F.  */
@@ -3074,6 +3109,11 @@ x_window (struct frame *f, long window_prompting)
   class_hints.res_class = SSDATA (Vx_resource_class);
   XSetClassHint (FRAME_X_DISPLAY (f), XtWindow (shell_widget), &class_hints);
 
+#ifdef HAVE_XINPUT2
+  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
+    setup_xi_event_mask (f);
+#endif
+
 #ifdef HAVE_X_I18N
   FRAME_XIC (f) = NULL;
   if (use_xim)
@@ -3254,6 +3294,11 @@ x_window (struct frame *f)
     }
 #endif /* HAVE_X_I18N */
 
+#ifdef HAVE_XINPUT2
+  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
+    setup_xi_event_mask (f);
+#endif
+
   validate_x_resource_name ();
 
   class_hints.res_name = SSDATA (Vx_resource_name);
@@ -8038,6 +8083,11 @@ syms_of_xfns (void)
   /* Tell Emacs about this window system.  */
   Fprovide (Qx, Qnil);
 
+#ifdef HAVE_XINPUT2
+  DEFSYM (Qxinput2, "xinput2");
+  Fprovide (Qxinput2, Qnil);
+#endif
+
 #ifdef USE_X_TOOLKIT
   Fprovide (intern_c_string ("x-toolkit"), Qnil);
 #ifdef USE_MOTIF
diff --git a/src/xmenu.c b/src/xmenu.c
index ea2cbab203..07255911f9 100644
--- a/src/xmenu.c
+++ b/src/xmenu.c
@@ -105,7 +105,11 @@ Copyright (C) 1986, 1988, 1993-1994, 1996, 1999-2021 Free Software
 \f
 /* Flag which when set indicates a dialog or menu has been posted by
    Xt on behalf of one of the widget sets.  */
+#ifndef HAVE_XINPUT2
 static int popup_activated_flag;
+#else
+int popup_activated_flag;
+#endif
 
 \f
 #ifdef USE_X_TOOLKIT
diff --git a/src/xterm.c b/src/xterm.c
index 816b6dc5a8..63754a2cb6 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -42,6 +42,10 @@ Copyright (C) 1989, 1993-2021 Free Software Foundation, Inc.
 #include <X11/extensions/Xdbe.h>
 #endif
 
+#ifdef HAVE_XINPUT2
+#include <X11/extensions/XInput2.h>
+#endif
+
 /* Load sys/types.h if not already loaded.
    In some systems loading it twice is suicidal.  */
 #ifndef makedev
@@ -223,9 +227,15 @@ #define XtNinitialState "initialState"
 static void x_check_fullscreen (struct frame *);
 static void x_check_expected_move (struct frame *, int, int);
 static void x_sync_with_move (struct frame *, int, int, bool);
+#ifndef HAVE_XINPUT2
 static int handle_one_xevent (struct x_display_info *,
 			      const XEvent *, int *,
 			      struct input_event *);
+#else
+static int handle_one_xevent (struct x_display_info *,
+			      XEvent *, int *,
+			      struct input_event *);
+#endif
 #if ! (defined USE_X_TOOLKIT || defined USE_MOTIF) && defined USE_GTK
 static int x_dispatch_event (XEvent *, Display *);
 #endif
@@ -335,6 +345,224 @@ x_extension_initialize (struct x_display_info *dpyinfo)
   dpyinfo->ext_codes = ext_codes;
 }
 
+
+#ifdef HAVE_XINPUT2
+
+/* Free all XI2 devices on dpyinfo.  */
+static void
+x_free_xi_devices (struct x_display_info *dpyinfo)
+{
+  block_input ();
+
+  if (dpyinfo->num_devices)
+    {
+      for (int i = 0; i < dpyinfo->num_devices; ++i)
+	{
+	  XIUngrabDevice (dpyinfo->display, dpyinfo->devices[i].device_id,
+			  CurrentTime);
+	  xfree (dpyinfo->devices[i].valuators);
+	}
+
+      xfree (dpyinfo->devices);
+      dpyinfo->devices = NULL;
+      dpyinfo->num_devices = 0;
+    }
+
+  unblock_input ();
+}
+
+/* Setup valuator tracking for XI2 master devices on
+   DPYINFO->display.  */
+
+static void
+x_init_master_valuators (struct x_display_info *dpyinfo)
+{
+  int ndevices;
+  XIDeviceInfo *infos;
+
+  block_input ();
+  x_free_xi_devices (dpyinfo);
+  infos = XIQueryDevice (dpyinfo->display,
+			 XIAllMasterDevices,
+			 &ndevices);
+
+  if (!ndevices)
+    {
+      XIFreeDeviceInfo (infos);
+      unblock_input ();
+      return;
+    }
+
+  int actual_devices = 0;
+  dpyinfo->devices = xmalloc (sizeof *dpyinfo->devices * ndevices);
+
+  for (int i = 0; i < ndevices; ++i)
+    {
+      XIDeviceInfo *device = &infos[i];
+
+      if (device->enabled)
+	{
+	  int actual_valuator_count = 0;
+	  struct xi_device_t *xi_device = &dpyinfo->devices[actual_devices++];
+	  xi_device->device_id = device->deviceid;
+	  xi_device->grab = 0;
+	  xi_device->valuators =
+	    xmalloc (sizeof *xi_device->valuators * device->num_classes);
+
+	  for (int c = 0; c < device->num_classes; ++c)
+	    {
+	      switch (device->classes[c]->type)
+		{
+#ifdef XIScrollClass /* XInput 2.1 */
+		case XIScrollClass:
+		  {
+		    XIScrollClassInfo *info =
+		      (XIScrollClassInfo *) device->classes[c];
+		    struct xi_scroll_valuator_t *valuator =
+		      &xi_device->valuators[actual_valuator_count++];
+
+		    valuator->horizontal
+		      = (info->scroll_type == XIScrollTypeHorizontal);
+		    valuator->invalid_p = true;
+		    valuator->emacs_value = DBL_MIN;
+		    valuator->increment = info->increment;
+		    valuator->number = info->number;
+		    break;
+		  }
+#endif
+		default:
+		  break;
+		}
+	    }
+	  xi_device->scroll_valuator_count = actual_valuator_count;
+	}
+    }
+
+  dpyinfo->num_devices = actual_devices;
+  XIFreeDeviceInfo (infos);
+  unblock_input ();
+}
+
+/* Return the delta of the scroll valuator VALUATOR_NUMBER under
+   DEVICE_ID in the display DPYINFO with VALUE.  The valuator's
+   valuator will be set to VALUE afterwards.  In case no scroll
+   valuator is found, or if device_id is not known to Emacs, DBL_MAX
+   is returned.  Otherwise, the valuator is returned in
+   VALUATOR_RETURN.  */
+static double
+x_get_scroll_valuator_delta (struct x_display_info *dpyinfo, int device_id,
+			     int valuator_number, double value,
+			     struct xi_scroll_valuator_t **valuator_return)
+{
+  block_input ();
+
+  for (int i = 0; i < dpyinfo->num_devices; ++i)
+    {
+      struct xi_device_t *device = &dpyinfo->devices[i];
+
+      if (device->device_id == device_id)
+	{
+	  for (int j = 0; j < device->scroll_valuator_count; ++j)
+	    {
+	      struct xi_scroll_valuator_t *sv = &device->valuators[j];
+
+	      if (sv->number == valuator_number)
+		{
+		  if (sv->invalid_p)
+		    {
+		      sv->current_value = value;
+		      sv->invalid_p = false;
+		      *valuator_return = sv;
+
+		      unblock_input ();
+		      return 0.0;
+		    }
+		  else
+		    {
+		      double delta = (sv->current_value - value) / sv->increment;
+                      sv->current_value = value;
+		      *valuator_return = sv;
+
+		      unblock_input ();
+		      return delta;
+		    }
+		}
+	    }
+
+	  unblock_input ();
+	  return DBL_MAX;
+	}
+    }
+
+  unblock_input ();
+  return DBL_MAX;
+}
+
+static struct xi_device_t *
+xi_device_from_id (struct x_display_info *dpyinfo, int deviceid)
+{
+  for (int i = 0; i < dpyinfo->num_devices; ++i)
+    {
+      if (dpyinfo->devices[i].device_id == deviceid)
+	return &dpyinfo->devices[i];
+    }
+
+  return NULL;
+}
+
+static void
+xi_grab_or_ungrab_device (struct xi_device_t *device,
+			  struct x_display_info *dpyinfo,
+			  Window window)
+{
+  XIEventMask mask;
+  ptrdiff_t l = XIMaskLen (XI_LASTEVENT);
+  unsigned char *m;
+  mask.mask = m = alloca (l);
+  memset (m, 0, l);
+  mask.mask_len = l;
+
+  XISetMask (m, XI_ButtonPress);
+  XISetMask (m, XI_ButtonRelease);
+  XISetMask (m, XI_Motion);
+  XISetMask (m, XI_Enter);
+  XISetMask (m, XI_Leave);
+
+  if (device->grab)
+    {
+      XIGrabDevice (dpyinfo->display, device->device_id, window,
+		    CurrentTime, None, GrabModeAsync,
+		    GrabModeAsync, True, &mask);
+    }
+  else
+    {
+      XIUngrabDevice (dpyinfo->display, device->device_id, CurrentTime);
+    }
+}
+
+static void
+xi_reset_scroll_valuators_for_device_id (struct x_display_info *dpyinfo, int id)
+{
+  struct xi_device_t *device = xi_device_from_id (dpyinfo, id);
+  struct xi_scroll_valuator_t *valuator;
+
+  if (!device)
+    return;
+
+  if (!device->scroll_valuator_count)
+    return;
+
+  for (int i = 0; i < device->scroll_valuator_count; ++i)
+    {
+      valuator = &device->valuators[i];
+      valuator->invalid_p = true;
+    }
+
+  return;
+}
+
+#endif
+
 void
 x_cr_destroy_frame_context (struct frame *f)
 {
@@ -4768,7 +4996,16 @@ x_any_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
 x_menubar_window_to_frame (struct x_display_info *dpyinfo,
 			   const XEvent *event)
 {
-  Window wdesc = event->xany.window;
+  Window wdesc;
+#ifdef HAVE_XINPUT2
+  if (event->type == GenericEvent
+      && dpyinfo->supports_xi2
+      && (event->xcookie.evtype == XI_ButtonPress
+	  || event->xcookie.evtype == XI_ButtonRelease))
+    wdesc = ((XIDeviceEvent *) event->xcookie.data)->event;
+  else
+#endif
+    wdesc = event->xany.window;
   Lisp_Object tail, frame;
   struct frame *f;
   struct x_output *x;
@@ -4871,6 +5108,29 @@ x_detect_focus_change (struct x_display_info *dpyinfo, struct frame *frame,
       }
       break;
 
+#ifdef HAVE_XINPUT2
+    case GenericEvent:
+      {
+	XIEvent *xi_event = (XIEvent *) event;
+
+        struct frame *focus_frame = dpyinfo->x_focus_event_frame;
+        int focus_state
+          = focus_frame ? focus_frame->output_data.x->focus_state : 0;
+
+	if (!((xi_event->evtype == XI_Enter
+	       || xi_event->evtype == XI_Leave)
+	      && (focus_state & FOCUS_EXPLICIT)))
+	  x_focus_changed ((xi_event->evtype == XI_Enter
+			    || xi_event->evtype == XI_FocusIn
+			    ? FocusIn : FocusOut),
+			   (xi_event->evtype == XI_Enter
+			    || xi_event->evtype == XI_Leave
+			    ? FOCUS_IMPLICIT : FOCUS_EXPLICIT),
+			   dpyinfo, frame, bufp);
+	break;
+      }
+#endif
+
     case FocusIn:
     case FocusOut:
       /* Ignore transient focus events from hotkeys, window manager
@@ -7975,7 +8235,11 @@ mouse_or_wdesc_frame (struct x_display_info *dpyinfo, int wdesc)
 
 static int
 handle_one_xevent (struct x_display_info *dpyinfo,
+#ifndef HAVE_XINPUT2
 		   const XEvent *event,
+#else
+		   XEvent *event,
+#endif
 		   int *finish, struct input_event *hold_quit)
 {
   union buffered_input_event inev;
@@ -8001,7 +8265,14 @@ handle_one_xevent (struct x_display_info *dpyinfo,
   inev.ie.kind = NO_EVENT;
   inev.ie.arg = Qnil;
 
-  any = x_any_window_to_frame (dpyinfo, event->xany.window);
+#ifdef HAVE_XINPUT2
+  if (event->type != GenericEvent)
+#endif
+    any = x_any_window_to_frame (dpyinfo, event->xany.window);
+#ifdef HAVE_XINPUT2
+  else
+    any = NULL;
+#endif
 
   if (any && any->wait_event_type == event->type)
     any->wait_event_type = 0; /* Indicates we got it.  */
@@ -8480,6 +8751,10 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       goto OTHER;
 
     case MapNotify:
+#if defined HAVE_XINPUT2 && defined HAVE_GTK3
+      if (xg_is_menu_window (dpyinfo->display, event->xmap.window))
+	popup_activated_flag = 1;
+#endif
       /* We use x_top_window_to_frame because map events can
          come for sub-windows and they don't mean that the
          frame is visible.  */
@@ -9518,6 +9793,785 @@ handle_one_xevent (struct x_display_info *dpyinfo,
     case DestroyNotify:
       xft_settings_event (dpyinfo, event);
       break;
+#ifdef HAVE_XINPUT2
+    case GenericEvent:
+      {
+	if (!dpyinfo->supports_xi2)
+	  goto OTHER;
+	if (event->xgeneric.extension != dpyinfo->xi2_opcode)
+	  /* Not an XI2 event. */
+	  goto OTHER;
+	bool must_free_data = false;
+	XIEvent *xi_event = (XIEvent *) event->xcookie.data;
+	/* Sometimes the event is already claimed by GTK, which
+	   will free its data in due course. */
+	if (!xi_event && XGetEventData (dpyinfo->display, &event->xcookie))
+	  {
+	    must_free_data = true;
+	    xi_event = (XIEvent *) event->xcookie.data;
+	  }
+
+	XIDeviceEvent *xev = (XIDeviceEvent *) xi_event;
+	XILeaveEvent *leave = (XILeaveEvent *) xi_event;
+	XIEnterEvent *enter = (XIEnterEvent *) xi_event;
+	XIFocusInEvent *focusin = (XIFocusInEvent *) xi_event;
+	XIFocusOutEvent *focusout = (XIFocusOutEvent *) xi_event;
+	XIValuatorState *states;
+	double *values;
+	bool found_valuator = false;
+
+	/* A fake XMotionEvent for x_note_mouse_movement. */
+	XMotionEvent ev;
+	/* A fake XButtonEvent for x_construct_mouse_click. */
+	XButtonEvent bv;
+
+	if (!xi_event)
+	  {
+	    eassert (!must_free_data);
+	    goto OTHER;
+	  }
+
+	switch (event->xcookie.evtype)
+	  {
+	  case XI_FocusIn:
+	    any = x_any_window_to_frame (dpyinfo, focusin->event);
+#ifndef USE_GTK
+	    /* Some WMs (e.g. Mutter in Gnome Shell), don't unmap
+	       minimized/iconified windows; thus, for those WMs we won't get
+	       a MapNotify when unminimizing/deconifying.  Check here if we
+	       are deiconizing a window (Bug42655).
+
+	       But don't do that on GTK since it may cause a plain invisible
+	       frame get reported as iconified, compare
+	       https://lists.gnu.org/archive/html/emacs-devel/2017-02/msg00133.html.
+	       That is fixed above but bites us here again.  */
+	    f = any;
+	    if (f && FRAME_ICONIFIED_P (f))
+	      {
+		SET_FRAME_VISIBLE (f, 1);
+		SET_FRAME_ICONIFIED (f, false);
+		f->output_data.x->has_been_visible = true;
+		inev.ie.kind = DEICONIFY_EVENT;
+		XSETFRAME (inev.ie.frame_or_window, f);
+	      }
+#endif /* USE_GTK */
+	    x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+	    goto XI_OTHER;
+	  case XI_FocusOut:
+	    any = x_any_window_to_frame (dpyinfo, focusout->event);
+	    x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+	    goto XI_OTHER;
+	  case XI_Enter:
+	    any = x_any_window_to_frame (dpyinfo, enter->event);
+	    ev.x = lrint (enter->event_x);
+	    ev.y = lrint (enter->event_y);
+	    ev.window = leave->event;
+
+	    x_display_set_last_user_time (dpyinfo, xi_event->time);
+	    x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+	    xi_reset_scroll_valuators_for_device_id (dpyinfo, enter->deviceid);
+	    f = any;
+
+	    if (f && x_mouse_click_focus_ignore_position)
+	      ignore_next_mouse_click_timeout = xi_event->time + 200;
+
+	    /* EnterNotify counts as mouse movement,
+	       so update things that depend on mouse position.  */
+	    if (f && !f->output_data.x->hourglass_p)
+	      x_note_mouse_movement (f, &ev);
+#ifdef USE_GTK
+	    /* We may get an EnterNotify on the buttons in the toolbar.  In that
+	       case we moved out of any highlighted area and need to note this.  */
+	    if (!f && dpyinfo->last_mouse_glyph_frame)
+	      x_note_mouse_movement (dpyinfo->last_mouse_glyph_frame, &ev);
+#endif
+	    goto XI_OTHER;
+	  case XI_Leave:
+	    ev.x = lrint (leave->event_x);
+	    ev.y = lrint (leave->event_y);
+	    ev.window = leave->event;
+	    any = x_any_window_to_frame (dpyinfo, leave->event);
+
+	    x_display_set_last_user_time (dpyinfo, xi_event->time);
+	    x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+	    xi_reset_scroll_valuators_for_device_id (dpyinfo, leave->deviceid);
+
+	    f = x_top_window_to_frame (dpyinfo, leave->event);
+	    if (f)
+	      {
+		if (f == hlinfo->mouse_face_mouse_frame)
+		  {
+		    /* If we move outside the frame, then we're
+		       certainly no longer on any text in the frame.  */
+		    clear_mouse_face (hlinfo);
+		    hlinfo->mouse_face_mouse_frame = 0;
+		  }
+
+		/* Generate a nil HELP_EVENT to cancel a help-echo.
+		   Do it only if there's something to cancel.
+		   Otherwise, the startup message is cleared when
+		   the mouse leaves the frame.  */
+		if (any_help_event_p)
+		  do_help = -1;
+	      }
+#ifdef USE_GTK
+	    /* See comment in EnterNotify above */
+	    else if (dpyinfo->last_mouse_glyph_frame)
+	      x_note_mouse_movement (dpyinfo->last_mouse_glyph_frame, &ev);
+#endif
+	    goto XI_OTHER;
+	  case XI_Motion:
+	    /* First test if there is some kind of scroll event
+	       here! */
+	    states = &xev->valuators;
+	    values = states->values;
+
+	    x_display_set_last_user_time (dpyinfo, xi_event->time);
+
+	    for (int i = 0; i < states->mask_len * 8; i++)
+	      {
+		if (XIMaskIsSet (states->mask, i))
+		  {
+		    block_input ();
+
+		    struct xi_scroll_valuator_t *val;
+		    double delta;
+
+		    delta = x_get_scroll_valuator_delta (dpyinfo, xev->deviceid,
+							 i, *values, &val);
+
+		    if (delta != DBL_MAX)
+		      {
+			f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+			found_valuator = true;
+
+			if (signbit (delta) != signbit (val->emacs_value))
+			  val->emacs_value = 0;
+
+			val->emacs_value += delta;
+
+			if (!f)
+			  {
+			    f = x_any_window_to_frame (dpyinfo, xev->event);
+
+			    if (!f)
+			      {
+				unblock_input ();
+				goto XI_OTHER;
+			      }
+			  }
+
+			bool s = signbit (val->emacs_value);
+			inev.ie.kind = (val->horizontal
+					? HORIZ_WHEEL_EVENT
+					: WHEEL_EVENT);
+			inev.ie.timestamp = xev->time;
+
+			XSETINT (inev.ie.x, lrint (xev->event_x));
+			XSETINT (inev.ie.y, lrint (xev->event_y));
+			XSETFRAME (inev.ie.frame_or_window, f);
+
+			inev.ie.modifiers = !s ? up_modifier : down_modifier;
+			inev.ie.modifiers
+			  |= x_x_to_emacs_modifiers (dpyinfo,
+						     xev->mods.effective);
+			inev.ie.arg = Qnil;
+
+			kbd_buffer_store_event_hold (&inev.ie, hold_quit);
+
+			val->emacs_value = 0;
+		      }
+		    unblock_input ();
+		    values++;
+		  }
+
+		inev.ie.kind = NO_EVENT;
+	      }
+
+	    if (found_valuator)
+	      goto XI_OTHER;
+
+	    ev.x = lrint (xev->event_x);
+	    ev.y = lrint (xev->event_y);
+	    ev.window = xev->event;
+
+	    previous_help_echo_string = help_echo_string;
+	    help_echo_string = Qnil;
+
+	    if (hlinfo->mouse_face_hidden)
+	      {
+		hlinfo->mouse_face_hidden = false;
+		clear_mouse_face (hlinfo);
+	      }
+
+	    f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+
+#ifdef USE_GTK
+	    if (f && xg_event_is_for_scrollbar (f, event))
+	      f = 0;
+#endif
+	    if (f)
+	      {
+		/* Maybe generate a SELECT_WINDOW_EVENT for
+		   `mouse-autoselect-window' but don't let popup menus
+		   interfere with this (Bug#1261).  */
+		if (!NILP (Vmouse_autoselect_window)
+		    && !popup_activated ()
+		    /* Don't switch if we're currently in the minibuffer.
+		       This tries to work around problems where the
+		       minibuffer gets unselected unexpectedly, and where
+		       you then have to move your mouse all the way down to
+		       the minibuffer to select it.  */
+		    && !MINI_WINDOW_P (XWINDOW (selected_window))
+		    /* With `focus-follows-mouse' non-nil create an event
+		       also when the target window is on another frame.  */
+		    && (f == XFRAME (selected_frame)
+			|| !NILP (focus_follows_mouse)))
+		  {
+		    static Lisp_Object last_mouse_window;
+		    Lisp_Object window = window_from_coordinates (f, ev.x, ev.y, 0, false, false);
+
+		    /* A window will be autoselected only when it is not
+		       selected now and the last mouse movement event was
+		       not in it.  The remainder of the code is a bit vague
+		       wrt what a "window" is.  For immediate autoselection,
+		       the window is usually the entire window but for GTK
+		       where the scroll bars don't count.  For delayed
+		       autoselection the window is usually the window's text
+		       area including the margins.  */
+		    if (WINDOWP (window)
+			&& !EQ (window, last_mouse_window)
+			&& !EQ (window, selected_window))
+		      {
+			inev.ie.kind = SELECT_WINDOW_EVENT;
+			inev.ie.frame_or_window = window;
+		      }
+
+		    /* Remember the last window where we saw the mouse.  */
+		    last_mouse_window = window;
+		  }
+
+		if (!x_note_mouse_movement (f, &ev))
+		  help_echo_string = previous_help_echo_string;
+	      }
+	    else
+	      {
+#ifndef USE_TOOLKIT_SCROLL_BARS
+		struct scroll_bar *bar
+		  = x_window_to_scroll_bar (xi_event->display, xev->event, 2);
+
+		if (bar)
+		  x_scroll_bar_note_movement (bar, &ev);
+#endif /* USE_TOOLKIT_SCROLL_BARS */
+
+		/* If we move outside the frame, then we're
+		   certainly no longer on any text in the frame.  */
+		clear_mouse_face (hlinfo);
+	      }
+
+	    /* If the contents of the global variable help_echo_string
+	       has changed, generate a HELP_EVENT.  */
+	    if (!NILP (help_echo_string)
+		|| !NILP (previous_help_echo_string))
+	      do_help = 1;
+	    goto XI_OTHER;
+	  case XI_ButtonRelease:
+	  case XI_ButtonPress:
+	    {
+	      /* If we decide we want to generate an event to be seen
+		 by the rest of Emacs, we put it here.  */
+	      Lisp_Object tab_bar_arg = Qnil;
+	      bool tab_bar_p = false;
+	      bool tool_bar_p = false;
+	      struct xi_device_t *device;
+
+	      /* Ignore emulated scroll events when XI2 native
+		 scroll events are present.  */
+	      if (dpyinfo->xi2_version >= 1 && xev->detail >= 4
+		  && xev->detail <= 8)
+		goto XI_OTHER;
+
+	      device = xi_device_from_id (dpyinfo, xev->deviceid);
+
+	      bv.button = xev->detail;
+	      bv.type = xev->evtype == XI_ButtonPress ? ButtonPress : ButtonRelease;
+	      bv.x = lrint (xev->event_x);
+	      bv.y = lrint (xev->event_y);
+	      bv.window = xev->event;
+	      bv.state = xev->mods.base
+		| xev->mods.effective
+		| xev->mods.latched
+		| xev->mods.locked;
+
+	      memset (&compose_status, 0, sizeof (compose_status));
+	      dpyinfo->last_mouse_glyph_frame = NULL;
+	      x_display_set_last_user_time (dpyinfo, xev->time);
+
+	      f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+
+	      if (f && xev->evtype == XI_ButtonPress
+		  && !popup_activated ()
+		  && !x_window_to_scroll_bar (xev->display, xev->event, 2)
+		  && !FRAME_NO_ACCEPT_FOCUS (f))
+		{
+		  /* When clicking into a child frame or when clicking
+		     into a parent frame with the child frame selected and
+		     `no-accept-focus' is not set, select the clicked
+		     frame.  */
+		  struct frame *hf = dpyinfo->highlight_frame;
+
+		  if (FRAME_PARENT_FRAME (f) || (hf && frame_ancestor_p (f, hf)))
+		    {
+		      block_input ();
+		      XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f),
+				      RevertToParent, CurrentTime);
+		      if (FRAME_PARENT_FRAME (f))
+			XRaiseWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f));
+		      unblock_input ();
+		    }
+		}
+
+#ifdef USE_GTK
+	      if (f && xg_event_is_for_scrollbar (f, event))
+		f = 0;
+#endif
+
+	      if (f)
+		{
+		  /* Is this in the tab-bar?  */
+		  if (WINDOWP (f->tab_bar_window)
+		      && WINDOW_TOTAL_LINES (XWINDOW (f->tab_bar_window)))
+		    {
+		      Lisp_Object window;
+		      int x = bv.x;
+		      int y = bv.y;
+
+		      window = window_from_coordinates (f, x, y, 0, true, true);
+		      tab_bar_p = EQ (window, f->tab_bar_window);
+
+		      if (tab_bar_p)
+			tab_bar_arg = handle_tab_bar_click
+			  (f, x, y, xev->evtype == XI_ButtonPress,
+			   x_x_to_emacs_modifiers (dpyinfo, bv.state));
+		    }
+
+#if ! defined (USE_GTK)
+		  /* Is this in the tool-bar?  */
+		  if (WINDOWP (f->tool_bar_window)
+		      && WINDOW_TOTAL_LINES (XWINDOW (f->tool_bar_window)))
+		    {
+		      Lisp_Object window;
+		      int x = bv.x;
+		      int y = bv.y;
+
+		      window = window_from_coordinates (f, x, y, 0, true, true);
+		      tool_bar_p = EQ (window, f->tool_bar_window);
+
+		      if (tool_bar_p && xev->detail < 4)
+			handle_tool_bar_click
+			  (f, x, y, xev->evtype == XI_ButtonPress,
+			   x_x_to_emacs_modifiers (dpyinfo, bv.state));
+		    }
+#endif /* !USE_GTK */
+
+		  if (!(tab_bar_p && NILP (tab_bar_arg)) && !tool_bar_p)
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+		    if (! popup_activated ())
+#endif
+		      {
+			if (ignore_next_mouse_click_timeout)
+			  {
+			    if (xev->evtype == XI_ButtonPress
+				&& xev->time > ignore_next_mouse_click_timeout)
+			      {
+				ignore_next_mouse_click_timeout = 0;
+				x_construct_mouse_click (&inev.ie, &bv, f);
+			      }
+			    if (xev->evtype == XI_ButtonRelease)
+			      ignore_next_mouse_click_timeout = 0;
+			  }
+			else
+			  x_construct_mouse_click (&inev.ie, &bv, f);
+
+			if (!NILP (tab_bar_arg))
+			  inev.ie.arg = tab_bar_arg;
+		      }
+		  if (FRAME_X_EMBEDDED_P (f))
+		    xembed_send_message (f, xev->time,
+					 XEMBED_REQUEST_FOCUS, 0, 0, 0);
+		}
+
+	      if (xev->evtype == XI_ButtonPress)
+		{
+		  dpyinfo->grabbed |= (1 << xev->detail);
+		  device->grab |= (1 << xev->detail);
+		  dpyinfo->last_mouse_frame = f;
+		  if (f && !tab_bar_p)
+		    f->last_tab_bar_item = -1;
+#if ! defined (USE_GTK)
+		  if (f && !tool_bar_p)
+		    f->last_tool_bar_item = -1;
+#endif /* not USE_GTK */
+
+		}
+	      else
+		{
+		  dpyinfo->grabbed &= ~(1 << xev->detail);
+		  device->grab &= ~(1 << xev->detail);
+		}
+
+	      xi_grab_or_ungrab_device (device, dpyinfo, xev->event);
+
+	      if (f)
+		f->mouse_moved = false;
+
+#if defined (USE_GTK)
+	      /* No Xt toolkit currently available has support for XI2.
+	         So the code here assumes use of GTK.  */
+	      f = x_menubar_window_to_frame (dpyinfo, event);
+	      if (f /* Gtk+ menus only react to the first three buttons. */
+		  && xev->detail < 3)
+		{
+		  /* What is done with Core Input ButtonPressed is not
+		     possible here, because GenericEvents cannot be saved.  */
+		  bool was_waiting_for_input = waiting_for_input;
+		  /* This hack was adopted from the NS port.  Whether
+		     or not it is actually safe is a different story
+		     altogether.  */
+		  if (waiting_for_input)
+		    waiting_for_input = 0;
+		  set_frame_menubar (f, true);
+		  waiting_for_input = was_waiting_for_input;
+		}
+#endif
+	      goto XI_OTHER;
+	    }
+	  case XI_KeyPress:
+	    {
+	      int state = xev->mods.base
+		| xev->mods.effective
+		| xev->mods.latched
+		| xev->mods.locked;
+	      Lisp_Object c;
+#ifdef HAVE_XKB
+	      unsigned int mods_rtrn;
+#endif
+	      int keycode = xev->detail;
+	      KeySym keysym;
+	      char copy_buffer[81];
+	      char *copy_bufptr = copy_buffer;
+	      unsigned char *copy_ubufptr;
+#ifdef HAVE_XKB
+	      int copy_bufsiz = sizeof (copy_buffer);
+#endif
+	      ptrdiff_t i;
+	      int nchars, len;
+
+#ifdef HAVE_XKB
+	      if (dpyinfo->xkb_desc)
+		{
+		  if (!XkbTranslateKeyCode (dpyinfo->xkb_desc, keycode,
+					    state, &mods_rtrn, &keysym))
+		    goto XI_OTHER;
+		}
+	      else
+		{
+#endif
+		  int keysyms_per_keycode_return;
+		  KeySym *ksms = XGetKeyboardMapping (dpyinfo->display, keycode, 1,
+						      &keysyms_per_keycode_return);
+		  if (!(keysym = ksms[0]))
+		    {
+		      XFree (ksms);
+		      goto XI_OTHER;
+		    }
+		  XFree (ksms);
+#ifdef HAVE_XKB
+		}
+#endif
+
+	      if (keysym == NoSymbol)
+		goto XI_OTHER;
+
+	      x_display_set_last_user_time (dpyinfo, xev->time);
+	      ignore_next_mouse_click_timeout = 0;
+
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+	      /* Dispatch XI_KeyPress events when in menu.  */
+	      if (popup_activated ())
+		goto XI_OTHER;
+#endif
+
+	      f = x_any_window_to_frame (dpyinfo, xev->event);
+
+	      /* If mouse-highlight is an integer, input clears out
+		 mouse highlighting.  */
+	      if (!hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight)
+		  && (f == 0
+#if ! defined (USE_GTK)
+		      || !EQ (f->tool_bar_window, hlinfo->mouse_face_window)
+#endif
+		      || !EQ (f->tab_bar_window, hlinfo->mouse_face_window))
+		  )
+		{
+		  clear_mouse_face (hlinfo);
+		  hlinfo->mouse_face_hidden = true;
+		}
+
+	      if (f != 0)
+		{
+#ifdef USE_GTK
+		  /* Don't pass keys to GTK.  A Tab will shift focus to the
+		     tool bar in GTK 2.4.  Keys will still go to menus and
+		     dialogs because in that case popup_activated is nonzero
+		     (see above).  */
+		  *finish = X_EVENT_DROP;
+#endif
+		  /* If not using XIM/XIC, and a compose sequence is in progress,
+		     we break here.  Otherwise, chars_matched is always 0.  */
+		  if (compose_status.chars_matched > 0 && nbytes == 0)
+		    goto XI_OTHER;
+
+		  memset (&compose_status, 0, sizeof (compose_status));
+
+		  XSETFRAME (inev.ie.frame_or_window, f);
+		  inev.ie.modifiers
+		    = x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), state);
+		  inev.ie.timestamp = xev->time;
+
+		  /* First deal with keysyms which have defined
+		     translations to characters.  */
+		  if (keysym >= 32 && keysym < 128)
+		    /* Avoid explicitly decoding each ASCII character.  */
+		    {
+		      inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+		      inev.ie.code = keysym;
+
+		      goto xi_done_keysym;
+		    }
+
+		  /* Keysyms directly mapped to Unicode characters.  */
+		  if (keysym >= 0x01000000 && keysym <= 0x0110FFFF)
+		    {
+		      if (keysym < 0x01000080)
+			inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+		      else
+			inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
+		      inev.ie.code = keysym & 0xFFFFFF;
+		      goto xi_done_keysym;
+		    }
+
+		  /* Now non-ASCII.  */
+		  if (HASH_TABLE_P (Vx_keysym_table)
+		      && (c = Fgethash (make_fixnum (keysym),
+					Vx_keysym_table,
+					Qnil),
+			  FIXNATP (c)))
+		    {
+		      inev.ie.kind = (SINGLE_BYTE_CHAR_P (XFIXNAT (c))
+				      ? ASCII_KEYSTROKE_EVENT
+				      : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+		      inev.ie.code = XFIXNAT (c);
+		      goto xi_done_keysym;
+		    }
+
+		  /* Random non-modifier sorts of keysyms.  */
+		  if (((keysym >= XK_BackSpace && keysym <= XK_Escape)
+		       || keysym == XK_Delete
+#ifdef XK_ISO_Left_Tab
+		       || (keysym >= XK_ISO_Left_Tab
+			   && keysym <= XK_ISO_Enter)
+#endif
+		       || IsCursorKey (keysym) /* 0xff50 <= x < 0xff60 */
+		       || IsMiscFunctionKey (keysym) /* 0xff60 <= x < VARIES */
+#ifdef HPUX
+		       /* This recognizes the "extended function
+			  keys".  It seems there's no cleaner way.
+			  Test IsModifierKey to avoid handling
+			  mode_switch incorrectly.  */
+		       || (XK_Select <= keysym && keysym < XK_KP_Space)
+#endif
+#ifdef XK_dead_circumflex
+		       || keysym == XK_dead_circumflex
+#endif
+#ifdef XK_dead_grave
+		       || keysym == XK_dead_grave
+#endif
+#ifdef XK_dead_tilde
+		       || keysym == XK_dead_tilde
+#endif
+#ifdef XK_dead_diaeresis
+		       || keysym == XK_dead_diaeresis
+#endif
+#ifdef XK_dead_macron
+		       || keysym == XK_dead_macron
+#endif
+#ifdef XK_dead_degree
+		       || keysym == XK_dead_degree
+#endif
+#ifdef XK_dead_acute
+		       || keysym == XK_dead_acute
+#endif
+#ifdef XK_dead_cedilla
+		       || keysym == XK_dead_cedilla
+#endif
+#ifdef XK_dead_breve
+		       || keysym == XK_dead_breve
+#endif
+#ifdef XK_dead_ogonek
+		       || keysym == XK_dead_ogonek
+#endif
+#ifdef XK_dead_caron
+		       || keysym == XK_dead_caron
+#endif
+#ifdef XK_dead_doubleacute
+		       || keysym == XK_dead_doubleacute
+#endif
+#ifdef XK_dead_abovedot
+		       || keysym == XK_dead_abovedot
+#endif
+		       || IsKeypadKey (keysym) /* 0xff80 <= x < 0xffbe */
+		       || IsFunctionKey (keysym) /* 0xffbe <= x < 0xffe1 */
+		       /* Any "vendor-specific" key is ok.  */
+		       || (keysym & (1 << 28))
+		       || (keysym != NoSymbol && nbytes == 0))
+		      && ! (IsModifierKey (keysym)
+			    /* The symbols from XK_ISO_Lock
+			       to XK_ISO_Last_Group_Lock
+			       don't have real modifiers but
+			       should be treated similarly to
+			       Mode_switch by Emacs. */
+#if defined XK_ISO_Lock && defined XK_ISO_Last_Group_Lock
+			    || (XK_ISO_Lock <= keysym
+				&& keysym <= XK_ISO_Last_Group_Lock)
+#endif
+			    ))
+		    {
+		      STORE_KEYSYM_FOR_DEBUG (keysym);
+		      /* make_lispy_event will convert this to a symbolic
+			 key.  */
+		      inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+		      inev.ie.code = keysym;
+		      goto xi_done_keysym;
+		    }
+
+#ifdef HAVE_XKB
+		  int overflow = 0;
+		  KeySym sym = keysym;
+
+		  if (dpyinfo->xkb_desc)
+		    {
+		      if (!(nbytes = XkbTranslateKeySym (dpyinfo->display, &sym,
+							 state & ~mods_rtrn, copy_bufptr,
+							 copy_bufsiz, &overflow)))
+			goto XI_OTHER;
+		    }
+		  else
+#else
+		    {
+		      block_input ();
+		      char *str = XKeysymToString (keysym);
+		      if (!str)
+			{
+			  unblock_input ();
+			  goto XI_OTHER;
+			}
+		      nbytes = strlen (str) + 1;
+		      copy_bufptr = alloca (nbytes);
+		      strcpy (copy_bufptr, str);
+		      unblock_input ();
+		    }
+#endif
+#ifdef HAVE_XKB
+		  if (overflow)
+		    {
+		      overflow = 0;
+		      copy_bufptr = alloca (copy_bufsiz + overflow);
+		      keysym = sym;
+		      if (!(nbytes = XkbTranslateKeySym (dpyinfo->display, &sym,
+							 state & ~mods_rtrn, copy_bufptr,
+							 copy_bufsiz + overflow, &overflow)))
+			goto XI_OTHER;
+
+		      if (overflow)
+			goto XI_OTHER;
+		    }
+#endif
+
+		  for (i = 0, nchars = 0; i < nbytes; i++)
+		    {
+		      if (ASCII_CHAR_P (copy_bufptr[i]))
+			nchars++;
+		      STORE_KEYSYM_FOR_DEBUG (copy_bufptr[i]);
+		    }
+
+		  if (nchars < nbytes)
+		    {
+		      /* Decode the input data.  */
+
+		      setup_coding_system (Vlocale_coding_system, &coding);
+		      coding.src_multibyte = false;
+		      coding.dst_multibyte = true;
+		      /* The input is converted to events, thus we can't
+			 handle composition.  Anyway, there's no XIM that
+			 gives us composition information.  */
+		      coding.common_flags &= ~CODING_ANNOTATION_MASK;
+
+		      SAFE_NALLOCA (coding.destination, MAX_MULTIBYTE_LENGTH,
+				    nbytes);
+		      coding.dst_bytes = MAX_MULTIBYTE_LENGTH * nbytes;
+		      coding.mode |= CODING_MODE_LAST_BLOCK;
+		      decode_coding_c_string (&coding, (unsigned char *) copy_bufptr,
+					      nbytes, Qnil);
+		      nbytes = coding.produced;
+		      nchars = coding.produced_char;
+		      copy_bufptr = (char *) coding.destination;
+		    }
+
+		  copy_ubufptr = (unsigned char *) copy_bufptr;
+
+		  /* Convert the input data to a sequence of
+		     character events.  */
+		  for (i = 0; i < nbytes; i += len)
+		    {
+		      int ch;
+		      if (nchars == nbytes)
+			ch = copy_ubufptr[i], len = 1;
+		      else
+			ch = string_char_and_length (copy_ubufptr + i, &len);
+		      inev.ie.kind = (SINGLE_BYTE_CHAR_P (ch)
+				      ? ASCII_KEYSTROKE_EVENT
+				      : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+		      inev.ie.code = ch;
+		      kbd_buffer_store_buffered_event (&inev, hold_quit);
+		    }
+
+		  inev.ie.kind = NO_EVENT;
+		  goto xi_done_keysym;
+		}
+	      goto XI_OTHER;
+	    }
+	  case XI_KeyRelease:
+	    x_display_set_last_user_time (dpyinfo, xev->time);
+	    goto XI_OTHER;
+	  case XI_PropertyEvent:
+	  case XI_HierarchyChanged:
+	  case XI_DeviceChanged:
+	    x_init_master_valuators (dpyinfo);
+	    goto XI_OTHER;
+	  default:
+	    goto XI_OTHER;
+	  }
+      xi_done_keysym:
+	if (must_free_data)
+	  XFreeEventData (dpyinfo->display, &event->xcookie);
+	goto done_keysym;
+      XI_OTHER:
+	if (must_free_data)
+	  XFreeEventData (dpyinfo->display, &event->xcookie);
+	goto OTHER;
+      }
+#endif
 
     default:
     OTHER:
@@ -13199,6 +14253,40 @@ #define NUM_ARGV 10
     dpyinfo->supports_xdbe = true;
 #endif
 
+#ifdef HAVE_XINPUT2
+  dpyinfo->supports_xi2 = false;
+  int rc;
+  int major = 2;
+#ifdef XI_BarrierHit /* XInput 2.3 */
+  int minor = 3;
+#elif defined XI_TouchBegin /* XInput 2.2 */
+  int minor = 2;
+#elif defined XIScrollClass /* XInput 1.1 */
+  int minor = 1;
+#else /* Some old version of XI2 we're not interested in. */
+  int minor = 0;
+#endif
+  int fer, fee;
+
+  if (XQueryExtension (dpyinfo->display, "XInputExtension",
+		       &dpyinfo->xi2_opcode, &fer, &fee))
+    {
+      rc = XIQueryVersion (dpyinfo->display, &major, &minor);
+      if (rc == Success)
+	{
+	  dpyinfo->supports_xi2 = true;
+	  x_init_master_valuators (dpyinfo);
+	}
+    }
+  dpyinfo->xi2_version = minor;
+#endif
+
+#ifdef HAVE_XKB
+  dpyinfo->xkb_desc = XkbGetMap (dpyinfo->display,
+				 XkbAllComponentsMask,
+				 XkbUseCoreKbd);
+#endif
+
 #if defined USE_CAIRO || defined HAVE_XFT
   {
     /* If we are using Xft, the following precautions should be made:
@@ -13631,6 +14719,14 @@ x_delete_terminal (struct terminal *terminal)
       XrmDestroyDatabase (dpyinfo->rdb);
 #endif
 
+#ifdef HAVE_XKB
+      if (dpyinfo->xkb_desc)
+	XkbFreeKeyboard (dpyinfo->xkb_desc, XkbAllComponentsMask, True);
+#endif
+#ifdef HAVE_XINPUT2
+      if (dpyinfo->supports_xi2)
+	x_free_xi_devices (dpyinfo);
+#endif
 #ifdef USE_GTK
       xg_display_close (dpyinfo->display);
 #else
@@ -13790,9 +14886,12 @@ x_initialize (void)
 void
 init_xterm (void)
 {
-  /* Emacs can handle only core input events, so make sure
-     Gtk doesn't use Xinput or Xinput2 extensions.  */
+#ifndef HAVE_XINPUT2
+  /* Emacs can handle only core input events when built without XI2
+     support, so make sure Gtk doesn't use Xinput or Xinput2
+     extensions.  */
   xputenv ("GDK_CORE_DEVICE_EVENTS=1");
+#endif
 }
 #endif
 
diff --git a/src/xterm.h b/src/xterm.h
index 9d9534dd62..7abe168bc6 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -88,6 +88,10 @@ #define XSync(d, b) do { gdk_window_process_all_updates (); \
 #include <X11/Xlib-xcb.h>
 #endif
 
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+#endif
+
 #include "dispextern.h"
 #include "termhooks.h"
 
@@ -163,6 +167,28 @@ #define MAX_CLIP_RECTS 2
   char *name;
 };
 
+#ifdef HAVE_XINPUT2
+struct xi_scroll_valuator_t
+{
+  bool invalid_p;
+  double current_value;
+  double emacs_value;
+  double increment;
+
+  int number;
+  int horizontal;
+};
+
+struct xi_device_t
+{
+  int device_id;
+  int scroll_valuator_count;
+  int grab;
+
+  struct xi_scroll_valuator_t *valuators;
+};
+#endif
+
 Status x_parse_color (struct frame *f, const char *color_name,
 		      XColor *color);
 
@@ -474,6 +500,19 @@ #define MAX_CLIP_RECTS 2
 #ifdef HAVE_XDBE
   bool supports_xdbe;
 #endif
+
+#ifdef HAVE_XINPUT2
+  bool supports_xi2;
+  int xi2_version;
+  int xi2_opcode;
+
+  int num_devices;
+  struct xi_device_t *devices;
+#endif
+
+#ifdef HAVE_XKB
+  XkbDescPtr xkb_desc;
+#endif
 };
 
 #ifdef HAVE_X_I18N
@@ -481,6 +520,11 @@ #define MAX_CLIP_RECTS 2
 extern bool use_xim;
 #endif
 
+#ifdef HAVE_XINPUT2
+/* Defined in xmenu.c. */
+extern int popup_activated_flag;
+#endif
+
 /* This is a chain of structures for all the X displays currently in use.  */
 extern struct x_display_info *x_display_list;
 
-- 
2.31.1


[-- Attachment #3: Type: text/plain, Size: 58 bytes --]


And here is the patch that exposes pixel deltas to Lisp:

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #4: 0002-Expose-pixel-wise-wheel-events-to-Lisp.patch --]
[-- Type: text/x-patch, Size: 4616 bytes --]

From aee86e1d2f94dff95644a817cad072a1de9a88bf Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Tue, 16 Nov 2021 19:39:50 +0800
Subject: [PATCH 2/2] Expose pixel-wise wheel events to Lisp

* doc/lispref/commands.texi (Misc Events): Document changes to
wheel events.

* src/keyboard.c (make_lispy_event): Handle wheel events with
pixel delta data.
* src/termhooks.h (WHEEL_EVENT): Document changes to
WHEEL_EVENT args.
* src/xfns.c (syms_of_xfns): Declare new symbols.
* src/xterm.c (handle_one_xevent): Give wheel events pixel delta
data.
---
 doc/lispref/commands.texi |  6 +++++-
 src/keyboard.c            |  5 ++++-
 src/termhooks.h           |  7 ++++++-
 src/xfns.c                |  3 +++
 src/xterm.c               | 13 +++++++++++--
 5 files changed, 29 insertions(+), 5 deletions(-)

diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index 1509c200e0..be0f4189ee 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -1985,7 +1985,11 @@ Misc Events
 These kinds of event are generated by moving a mouse wheel.  The
 @var{position} element is a mouse position list (@pxref{Click
 Events}), specifying the position of the mouse cursor when the event
-occurred.
+occurred.  The event may have additional arguments after
+@var{position}.  The third argument after @var{position}, if present,
+is a property list of the form @w{@code{(:delta-x @var{x} :delta-y
+@var{y})}}, where @var{x} and @var{y} are the number of pixels to
+scroll by in each axis.
 
 @vindex mouse-wheel-up-event
 @vindex mouse-wheel-down-event
diff --git a/src/keyboard.c b/src/keyboard.c
index de9805df32..276fd8c5aa 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -5980,7 +5980,10 @@ make_lispy_event (struct input_event *event)
 				      ASIZE (wheel_syms));
 	}
 
-        if (NUMBERP (event->arg))
+	if (CONSP (event->arg))
+	  return list5 (head, position, make_fixnum (double_click_count),
+			XCAR (event->arg), XCDR (event->arg));
+        else if (NUMBERP (event->arg))
           return list4 (head, position, make_fixnum (double_click_count),
                         event->arg);
 	else if (event->modifiers & (double_modifier | triple_modifier))
diff --git a/src/termhooks.h b/src/termhooks.h
index e7539bbce2..43a9fc2f22 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -119,7 +119,12 @@ #define EMACS_TERMHOOKS_H
 				   .timestamp gives a timestamp (in
 				   milliseconds) for the event.
                                    .arg may contain the number of
-                                   lines to scroll.  */
+                                   lines to scroll, or a list of
+				   the form (NUMBER-OF-LINES .
+				   (:DELTA-X :DELTA-Y Y)) where
+				   DELTA-X and DELTA-Y are the number
+				   of pixels on each axis to scroll
+				   by.  */
   HORIZ_WHEEL_EVENT,            /* A wheel event generated by a second
                                    horizontal wheel that is present on some
                                    mice. See WHEEL_EVENT.  */
diff --git a/src/xfns.c b/src/xfns.c
index b33b40b330..9d70ba479b 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -8085,6 +8085,9 @@ syms_of_xfns (void)
 
 #ifdef HAVE_XINPUT2
   DEFSYM (Qxinput2, "xinput2");
+  DEFSYM (QCdelta_x, ":delta-x");
+  DEFSYM (QCdelta_y, ":delta-y");
+
   Fprovide (Qxinput2, Qnil);
 #endif
 
diff --git a/src/xterm.c b/src/xterm.c
index 63754a2cb6..6eb361efca 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -9935,7 +9935,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 		    block_input ();
 
 		    struct xi_scroll_valuator_t *val;
-		    double delta;
+		    double delta, scroll_unit;
 
 		    delta = x_get_scroll_valuator_delta (dpyinfo, xev->deviceid,
 							 i, *values, &val);
@@ -9943,6 +9943,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 		    if (delta != DBL_MAX)
 		      {
 			f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+			scroll_unit = pow (FRAME_PIXEL_HEIGHT (f), 2.0 / 3.0);
 			found_valuator = true;
 
 			if (signbit (delta) != signbit (val->emacs_value))
@@ -9975,7 +9976,15 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 			inev.ie.modifiers
 			  |= x_x_to_emacs_modifiers (dpyinfo,
 						     xev->mods.effective);
-			inev.ie.arg = Qnil;
+
+			if (val->horizontal)
+			  inev.ie.arg = list5 (Qnil, QCdelta_x,
+					       make_float (delta * scroll_unit),
+					       QCdelta_y, make_float (0));
+			else
+			  inev.ie.arg = list5 (Qnil, QCdelta_x, make_float (0),
+					       QCdelta_y,
+					       make_float (delta * scroll_unit));
 
 			kbd_buffer_store_event_hold (&inev.ie, hold_quit);
 
-- 
2.31.1


[-- Attachment #5: Type: text/plain, Size: 16 bytes --]


WDYT?  Thanks.

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-16 12:38 ` bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-16 13:39   ` Robert Pluim
  2021-11-17  0:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 18+ messages in thread
From: Robert Pluim @ 2021-11-16 13:39 UTC (permalink / raw)
  To: 51891; +Cc: Po Lu

>>>>> On Tue, 16 Nov 2021 20:38:03 +0800, Po Lu via "Bug reports for GNU Emacs, the Swiss army knife of text editors" <bug-gnu-emacs@gnu.org> said:
    Po> +occurred.  The event may have additional arguments after
    Po> +@var{position}.  The third argument after @var{position}, if present,
    Po> +is a property list of the form @w{@code{(:delta-x @var{x} :delta-y
    Po> +@var{y})}}, where @var{x} and @var{y} are the number of pixels to
    Po> +scroll by in each axis.

Is there really a need for this to be a plist with :delta-x and
:delta-y in it? Just a cons of x and y would work.

    Po>  @vindex mouse-wheel-up-event
    Po>  @vindex mouse-wheel-down-event
    Po> diff --git a/src/keyboard.c b/src/keyboard.c
    Po> index de9805df32..276fd8c5aa 100644
    Po> --- a/src/keyboard.c
    Po> +++ b/src/keyboard.c
    Po> @@ -5980,7 +5980,10 @@ make_lispy_event (struct input_event *event)
    Po>  				      ASIZE (wheel_syms));
    Po>  	}
 
    Po> -        if (NUMBERP (event->arg))
    Po> +	if (CONSP (event->arg))
    Po> +	  return list5 (head, position, make_fixnum (double_click_count),
    Po> +			XCAR (event->arg), XCDR (event->arg));
    Po> +        else if (NUMBERP (event->arg))
    Po>            return list4 (head, position, make_fixnum (double_click_count),
    event-> arg);
    Po>  	else if (event->modifiers & (double_modifier | triple_modifier))
    Po> diff --git a/src/termhooks.h b/src/termhooks.h
    Po> index e7539bbce2..43a9fc2f22 100644
    Po> --- a/src/termhooks.h
    Po> +++ b/src/termhooks.h
    Po> @@ -119,7 +119,12 @@ #define EMACS_TERMHOOKS_H
    Po>  				   .timestamp gives a timestamp (in
    Po>  				   milliseconds) for the event.
    Po>                                     .arg may contain the number of
    Po> -                                   lines to scroll.  */
    Po> +                                   lines to scroll, or a list of
    Po> +				   the form (NUMBER-OF-LINES .
    Po> +				   (:DELTA-X :DELTA-Y Y)) where
    Po> +				   DELTA-X and DELTA-Y are the number
    Po> +				   of pixels on each axis to scroll
    Po> +				   by.  */

I donʼt think this is quite right. The 'X' is missing, and itʼs 'X'
and 'Y' that give the number of pixels. And :delta-x and :delta-y
(lowercase).

    Po>    HORIZ_WHEEL_EVENT,            /* A wheel event generated by a second
    Po>                                     horizontal wheel that is present on some
    Po>                                     mice. See WHEEL_EVENT.  */
    Po> diff --git a/src/xfns.c b/src/xfns.c
    Po> index b33b40b330..9d70ba479b 100644
    Po> --- a/src/xfns.c
    Po> +++ b/src/xfns.c
    Po> @@ -8085,6 +8085,9 @@ syms_of_xfns (void)
 
    Po>  #ifdef HAVE_XINPUT2
    Po>    DEFSYM (Qxinput2, "xinput2");
    Po> +  DEFSYM (QCdelta_x, ":delta-x");
    Po> +  DEFSYM (QCdelta_y, ":delta-y");
    Po> +
    Po>    Fprovide (Qxinput2, Qnil);
    Po>  #endif
 
    Po> diff --git a/src/xterm.c b/src/xterm.c
    Po> index 63754a2cb6..6eb361efca 100644
    Po> --- a/src/xterm.c
    Po> +++ b/src/xterm.c
    Po> @@ -9935,7 +9935,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
    Po>  		    block_input ();
 
    Po>  		    struct xi_scroll_valuator_t *val;
    Po> -		    double delta;
    Po> +		    double delta, scroll_unit;
 
    Po>  		    delta = x_get_scroll_valuator_delta (dpyinfo, xev->deviceid,
    Po>  							 i, *values, &val);
    Po> @@ -9943,6 +9943,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
    Po>  		    if (delta != DBL_MAX)
    Po>  		      {
    Po>  			f = mouse_or_wdesc_frame (dpyinfo, xev->event);
    Po> +			scroll_unit = pow (FRAME_PIXEL_HEIGHT (f), 2.0 / 3.0);
    Po>  			found_valuator = true;
 
    Po>  			if (signbit (delta) != signbit (val->emacs_value))
    Po> @@ -9975,7 +9976,15 @@ handle_one_xevent (struct x_display_info *dpyinfo,
    Po>  			inev.ie.modifiers
    Po>  			  |= x_x_to_emacs_modifiers (dpyinfo,
    xev-> mods.effective);
    Po> -			inev.ie.arg = Qnil;
    Po> +
    Po> +			if (val->horizontal)
    Po> +			  inev.ie.arg = list5 (Qnil, QCdelta_x,
    Po> +					       make_float (delta * scroll_unit),
    Po> +					       QCdelta_y, make_float (0));
    Po> +			else
    Po> +			  inev.ie.arg = list5 (Qnil, QCdelta_x, make_float (0),
    Po> +					       QCdelta_y,
    Po> +					       make_float (delta * scroll_unit));

So the :delta-y value is always 0 when :delta-x is non-zero and vice
versa? Why bother to return both then? (and why floats and not ints?).

Robert
-- 





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-16 13:39   ` Robert Pluim
@ 2021-11-17  0:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-17  2:38       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-17  0:34 UTC (permalink / raw)
  To: Robert Pluim; +Cc: 51891

Robert Pluim <rpluim@gmail.com> writes:

> Is there really a need for this to be a plist with :delta-x and
> :delta-y in it? Just a cons of x and y would work.

Yes, what if other window systems decide to expose different values to
Lisp code?  Such as for example, the phase of the scroll.

> I donʼt think this is quite right. The 'X' is missing, and itʼs 'X'
> and 'Y' that give the number of pixels. And :delta-x and :delta-y
> (lowercase).

Thanks, I fixed that on my side.

> So the :delta-y value is always 0 when :delta-x is non-zero and vice
> versa? Why bother to return both then? (and why floats and not ints?).

Because other systems (such as GDK) have different methods of reporting
scroll deltas.  While it makes sense in the context of the XInput 2 code
to report them one at a time, it does not in the context of GDK, as it
reports both deltas to Emacs at the same time.

Which means the pure GTK port will report both delta-y and delta-x at
the same time.

Thanks.





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-17  0:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-17  2:38       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-17 13:33         ` Eli Zaretskii
  0 siblings, 1 reply; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-17  2:38 UTC (permalink / raw)
  To: Robert Pluim; +Cc: 51891

[-- Attachment #1: Type: text/plain, Size: 557 bytes --]

Po Lu <luangruo@yahoo.com> writes:

> Because other systems (such as GDK) have different methods of reporting
> scroll deltas.  While it makes sense in the context of the XInput 2 code
> to report them one at a time, it does not in the context of GDK, as it
> reports both deltas to Emacs at the same time.
>
> Which means the pure GTK port will report both delta-y and delta-x at
> the same time.
>
> Thanks.

And here's a new version of the patch with the fixes to documentation,
and an extra option to prevent motion events from being sent too
rapidly.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0002-Expose-pixel-wise-wheel-events-to-Lisp.patch --]
[-- Type: text/x-patch, Size: 6367 bytes --]

From cf9a4a28cd44077e7682c0deccda9d1fbc98344d Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Tue, 16 Nov 2021 19:39:50 +0800
Subject: [PATCH 2/2] Expose pixel-wise wheel events to Lisp

* doc/lispref/commands.texi (Misc Events): Document changes to
wheel events.

* src/keyboard.c (make_lispy_event): Handle wheel events with
pixel delta data.
* src/termhooks.h (WHEEL_EVENT): Document changes to
WHEEL_EVENT args.
* src/xfns.c (syms_of_xfns): Declare new symbols.
* src/xterm.c (handle_one_xevent): Give wheel events pixel delta
data.
(x_coalesce_scroll_events): New user option.
---
 doc/lispref/commands.texi |  6 +++++-
 src/keyboard.c            |  5 ++++-
 src/termhooks.h           |  6 +++++-
 src/xfns.c                |  3 +++
 src/xterm.c               | 37 ++++++++++++++++++++++++++-----------
 5 files changed, 43 insertions(+), 14 deletions(-)

diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index 1509c200e0..be0f4189ee 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -1985,7 +1985,11 @@ Misc Events
 These kinds of event are generated by moving a mouse wheel.  The
 @var{position} element is a mouse position list (@pxref{Click
 Events}), specifying the position of the mouse cursor when the event
-occurred.
+occurred.  The event may have additional arguments after
+@var{position}.  The third argument after @var{position}, if present,
+is a property list of the form @w{@code{(:delta-x @var{x} :delta-y
+@var{y})}}, where @var{x} and @var{y} are the number of pixels to
+scroll by in each axis.
 
 @vindex mouse-wheel-up-event
 @vindex mouse-wheel-down-event
diff --git a/src/keyboard.c b/src/keyboard.c
index de9805df32..276fd8c5aa 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -5980,7 +5980,10 @@ make_lispy_event (struct input_event *event)
 				      ASIZE (wheel_syms));
 	}
 
-        if (NUMBERP (event->arg))
+	if (CONSP (event->arg))
+	  return list5 (head, position, make_fixnum (double_click_count),
+			XCAR (event->arg), XCDR (event->arg));
+        else if (NUMBERP (event->arg))
           return list4 (head, position, make_fixnum (double_click_count),
                         event->arg);
 	else if (event->modifiers & (double_modifier | triple_modifier))
diff --git a/src/termhooks.h b/src/termhooks.h
index e7539bbce2..19a6cc82da 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -119,7 +119,11 @@ #define EMACS_TERMHOOKS_H
 				   .timestamp gives a timestamp (in
 				   milliseconds) for the event.
                                    .arg may contain the number of
-                                   lines to scroll.  */
+                                   lines to scroll, or a list of
+				   the form (NUMBER-OF-LINES .
+				   (:delta-x :delta-y Y)) where
+				   X and Y are the number of pixels
+				   on each axis to scroll by.  */
   HORIZ_WHEEL_EVENT,            /* A wheel event generated by a second
                                    horizontal wheel that is present on some
                                    mice. See WHEEL_EVENT.  */
diff --git a/src/xfns.c b/src/xfns.c
index b33b40b330..9d70ba479b 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -8085,6 +8085,9 @@ syms_of_xfns (void)
 
 #ifdef HAVE_XINPUT2
   DEFSYM (Qxinput2, "xinput2");
+  DEFSYM (QCdelta_x, ":delta-x");
+  DEFSYM (QCdelta_y, ":delta-y");
+
   Fprovide (Qxinput2, Qnil);
 #endif
 
diff --git a/src/xterm.c b/src/xterm.c
index 63754a2cb6..74f0cefb7b 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -556,6 +556,7 @@ xi_reset_scroll_valuators_for_device_id (struct x_display_info *dpyinfo, int id)
     {
       valuator = &device->valuators[i];
       valuator->invalid_p = true;
+      valuator->emacs_value = 0.0;
     }
 
   return;
@@ -9921,8 +9922,6 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #endif
 	    goto XI_OTHER;
 	  case XI_Motion:
-	    /* First test if there is some kind of scroll event
-	       here! */
 	    states = &xev->valuators;
 	    values = states->values;
 
@@ -9932,10 +9931,8 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 	      {
 		if (XIMaskIsSet (states->mask, i))
 		  {
-		    block_input ();
-
 		    struct xi_scroll_valuator_t *val;
-		    double delta;
+		    double delta, scroll_unit;
 
 		    delta = x_get_scroll_valuator_delta (dpyinfo, xev->deviceid,
 							 i, *values, &val);
@@ -9943,6 +9940,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 		    if (delta != DBL_MAX)
 		      {
 			f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+			scroll_unit = pow (FRAME_PIXEL_HEIGHT (f), 2.0 / 3.0);
 			found_valuator = true;
 
 			if (signbit (delta) != signbit (val->emacs_value))
@@ -9950,15 +9948,16 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 
 			val->emacs_value += delta;
 
+			if (x_coalesce_scroll_events
+			    && (fabs (val->emacs_value) < 1))
+			  continue;
+
 			if (!f)
 			  {
 			    f = x_any_window_to_frame (dpyinfo, xev->event);
 
 			    if (!f)
-			      {
-				unblock_input ();
-				goto XI_OTHER;
-			      }
+			      goto XI_OTHER;
 			  }
 
 			bool s = signbit (val->emacs_value);
@@ -9975,13 +9974,22 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 			inev.ie.modifiers
 			  |= x_x_to_emacs_modifiers (dpyinfo,
 						     xev->mods.effective);
-			inev.ie.arg = Qnil;
+
+			if (val->horizontal)
+			  inev.ie.arg = list5 (Qnil, QCdelta_x,
+					       make_float (val->emacs_value
+							   * scroll_unit),
+					       QCdelta_y, make_float (0));
+			else
+			  inev.ie.arg = list5 (Qnil, QCdelta_x, make_float (0),
+					       QCdelta_y,
+					       make_float (val->emacs_value
+							   * scroll_unit));
 
 			kbd_buffer_store_event_hold (&inev.ie, hold_quit);
 
 			val->emacs_value = 0;
 		      }
-		    unblock_input ();
 		    values++;
 		  }
 
@@ -15048,4 +15056,11 @@ syms_of_xterm (void)
 consuming frame position adjustments.  In newer versions of GTK, Emacs
 always uses gtk_window_move and ignores the value of this variable.  */);
   x_gtk_use_window_move = true;
+
+  DEFVAR_BOOL ("x-coalesce-scroll-events", x_coalesce_scroll_events,
+	       doc: /* Non-nil means to only send one wheel event for each scroll unit.
+Otherwise, a wheel event will be sent every time the mouse wheel is
+moved.  This option is only effective when Emacs is built with XInput
+2.  */);
+  x_coalesce_scroll_events = true;
 }
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-17  2:38       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-17 13:33         ` Eli Zaretskii
  2021-11-18  0:15           ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 18+ messages in thread
From: Eli Zaretskii @ 2021-11-17 13:33 UTC (permalink / raw)
  To: Po Lu; +Cc: rpluim, 51891

> Cc: 51891@debbugs.gnu.org
> Date: Wed, 17 Nov 2021 10:38:41 +0800
> From:  Po Lu via "Bug reports for GNU Emacs,
>  the Swiss army knife of text editors" <bug-gnu-emacs@gnu.org>
> 
> --- a/doc/lispref/commands.texi
> +++ b/doc/lispref/commands.texi
> @@ -1985,7 +1985,11 @@ Misc Events
>  These kinds of event are generated by moving a mouse wheel.  The
>  @var{position} element is a mouse position list (@pxref{Click
>  Events}), specifying the position of the mouse cursor when the event
> -occurred.
> +occurred.  The event may have additional arguments after
> +@var{position}.  The third argument after @var{position}, if present,
> +is a property list of the form @w{@code{(:delta-x @var{x} :delta-y
> +@var{y})}}, where @var{x} and @var{y} are the number of pixels to
> +scroll by in each axis.

Did you consider the possibility of defining a new event?

And should mwheel.el be aware of this change somehow?

And finally, why introduce keyword-value pairs into a form that didn't
use them before?  Can we store just the values there?

> +			scroll_unit = pow (FRAME_PIXEL_HEIGHT (f), 2.0 / 3.0);

Ouch! can we avoid calling 'pow' here?  It's an expensive function.

> +  DEFVAR_BOOL ("x-coalesce-scroll-events", x_coalesce_scroll_events,
> +	       doc: /* Non-nil means to only send one wheel event for each scroll unit.
> +Otherwise, a wheel event will be sent every time the mouse wheel is
> +moved.

This is confusing: what does "scroll unit" mean in this context?





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-17 13:33         ` Eli Zaretskii
@ 2021-11-18  0:15           ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-18  7:33             ` Eli Zaretskii
  0 siblings, 1 reply; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-18  0:15 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: rpluim, 51891

Eli Zaretskii <eliz@gnu.org> writes:

> Did you consider the possibility of defining a new event?

Yes I did, but IMO it makes more sense to make this use the normal
`wheel-events'.

> And should mwheel.el be aware of this change somehow?

It doesn't have to.  A separate mwheel-scroll-*-function can be used to
implement pixel scrolling, once we get to that.

> And finally, why introduce keyword-value pairs into a form that didn't
> use them before?  Can we store just the values there?

Different window systems might have more/different information.  For
instace, I can imagine the GTK port wanting to include scroll phase and
inertia data into this propertly list.

>> +			scroll_unit = pow (FRAME_PIXEL_HEIGHT (f), 2.0 / 3.0);

> Ouch! can we avoid calling 'pow' here?  It's an expensive function.

That's what the other big users of XInput 2 do, including GTK+ and
Mozilla Firefox, and is unfortunately the only method of obtaining true
pixel scroll data from a scroll valuator.

>> +  DEFVAR_BOOL ("x-coalesce-scroll-events", x_coalesce_scroll_events,
>> +	       doc: /* Non-nil means to only send one wheel event for each scroll unit.
>> +Otherwise, a wheel event will be sent every time the mouse wheel is
>> +moved.

> This is confusing: what does "scroll unit" mean in this context?

An amount of scrolling that would previously generate a `mouse-4' or
`mouse-5' event.

But since I think most people don't know what such button events are,
it would not be sufficient to just put that into the doc string.

Any ideas?

Thanks!





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-18  0:15           ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-18  7:33             ` Eli Zaretskii
  2021-11-18  9:17               ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 18+ messages in thread
From: Eli Zaretskii @ 2021-11-18  7:33 UTC (permalink / raw)
  To: Po Lu; +Cc: rpluim, 51891

> From: Po Lu <luangruo@yahoo.com>
> Cc: rpluim@gmail.com,  51891@debbugs.gnu.org
> Date: Thu, 18 Nov 2021 08:15:27 +0800
> 
> > And finally, why introduce keyword-value pairs into a form that didn't
> > use them before?  Can we store just the values there?
> 
> Different window systems might have more/different information.  For
> instace, I can imagine the GTK port wanting to include scroll phase and
> inertia data into this propertly list.

A Lisp object could have different types, one each for every use case
we want to support.  For example, we could use a single object there,
which in your case will be a list of 2 values, and in other cases will
have more members.  We do this kind of stuff all the time, including
in specifying POSITION in various events -- there are a gazillion of
different forms of POSITION already in Emacs.  I see no reason to
change the format when all we need to do is add one more form of
POSITION.

> >> +			scroll_unit = pow (FRAME_PIXEL_HEIGHT (f), 2.0 / 3.0);
> 
> > Ouch! can we avoid calling 'pow' here?  It's an expensive function.
> 
> That's what the other big users of XInput 2 do, including GTK+ and
> Mozilla Firefox, and is unfortunately the only method of obtaining true
> pixel scroll data from a scroll valuator.

Sorry, I don't understand: XInput2 knows about FRAME_PIXEL_HEIGHT of
our frames?

> >> +  DEFVAR_BOOL ("x-coalesce-scroll-events", x_coalesce_scroll_events,
> >> +	       doc: /* Non-nil means to only send one wheel event for each scroll unit.
> >> +Otherwise, a wheel event will be sent every time the mouse wheel is
> >> +moved.
> 
> > This is confusing: what does "scroll unit" mean in this context?
> 
> An amount of scrolling that would previously generate a `mouse-4' or
> `mouse-5' event.

That doesn't really answer my question.  Let me ask it differently:
how does "scroll unit" differ from "every time the wheel is moved"?

> But since I think most people don't know what such button events are,
> it would not be sufficient to just put that into the doc string.
> 
> Any ideas?

I might have ideas once I understand what you are trying to say ;-)





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-18  7:33             ` Eli Zaretskii
@ 2021-11-18  9:17               ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-18 10:27                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-19 13:09                 ` Eli Zaretskii
  0 siblings, 2 replies; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-18  9:17 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: rpluim, 51891

Eli Zaretskii <eliz@gnu.org> writes:

> A Lisp object could have different types, one each for every use case
> we want to support.  For example, we could use a single object there,
> which in your case will be a list of 2 values, and in other cases will
> have more members.  We do this kind of stuff all the time, including
> in specifying POSITION in various events -- there are a gazillion of
> different forms of POSITION already in Emacs.  I see no reason to
> change the format when all we need to do is add one more form of
> POSITION.

That makes sense, I'll modify the change in a bit to report scroll
deltas as a pair of (DELTA-X . DELTA-Y) instead.

> Sorry, I don't understand: XInput2 knows about FRAME_PIXEL_HEIGHT of
> our frames?

Indeed it does.  It's the window server, after all.

>> An amount of scrolling that would previously generate a `mouse-4' or
>> `mouse-5' event.

> That doesn't really answer my question.  Let me ask it differently:
> how does "scroll unit" differ from "every time the wheel is moved"?

Basically, even tiny movements of the scroll wheel will cause XInput 2
to generate wheel events.  This makes the behavior stay like the
original by default, where you have to scroll 96 or so pixels before a
wheel event is actually sent.  (This is generally true, but certain mice
may behave differently.)

> I might have ideas once I understand what you are trying to say ;-)

Thanks, I hope what I just said clears things up a bit.





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-18  9:17               ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-18 10:27                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-19 13:05                   ` Eli Zaretskii
  2021-11-19 13:09                 ` Eli Zaretskii
  1 sibling, 1 reply; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-18 10:27 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: rpluim, 51891

[-- Attachment #1: Type: text/plain, Size: 193 bytes --]

Po Lu <luangruo@yahoo.com> writes:

> That makes sense, I'll modify the change in a bit to report scroll
> deltas as a pair of (DELTA-X . DELTA-Y) instead.

Here it is, and thanks in advance.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0002-Expose-pixel-wise-wheel-events-to-Lisp.patch --]
[-- Type: text/x-patch, Size: 6307 bytes --]

From aeb2a2bec3936ee2e9fb0b4a135e80474d360d99 Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Tue, 16 Nov 2021 19:39:50 +0800
Subject: [PATCH 2/2] Expose pixel-wise wheel events to Lisp

* doc/lispref/commands.texi (Misc Events): Document changes to
wheel events.

* src/keyboard.c (make_lispy_event): Handle wheel events with
pixel delta data.
* src/termhooks.h (WHEEL_EVENT): Document changes to
WHEEL_EVENT args.
* src/xfns.c (syms_of_xfns): Declare new symbols.
* src/xterm.c (handle_one_xevent): Give wheel events pixel delta
data.
(x_coalesce_scroll_events): New user option.
---
 doc/lispref/commands.texi |  5 ++++-
 src/keyboard.c            |  6 +++++-
 src/termhooks.h           |  5 ++++-
 src/xfns.c                |  1 +
 src/xterm.c               | 41 ++++++++++++++++++++++++++++-----------
 5 files changed, 44 insertions(+), 14 deletions(-)

diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index 1509c200e0..a32548e9d5 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -1985,7 +1985,10 @@ Misc Events
 These kinds of event are generated by moving a mouse wheel.  The
 @var{position} element is a mouse position list (@pxref{Click
 Events}), specifying the position of the mouse cursor when the event
-occurred.
+occurred.  The event may have additional arguments after
+@var{position}.  The third argument after @var{position}, if present,
+is a pair of the form @w{@code{(@var{x} . @var{y})}}, where @var{x}
+and @var{y} are the number of pixels to scroll by in each axis.
 
 @vindex mouse-wheel-up-event
 @vindex mouse-wheel-down-event
diff --git a/src/keyboard.c b/src/keyboard.c
index c3bc8307d7..0c48790ce8 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -5980,7 +5980,11 @@ make_lispy_event (struct input_event *event)
 				      ASIZE (wheel_syms));
 	}
 
-        if (NUMBERP (event->arg))
+	if (CONSP (event->arg))
+	  return list5 (head, position, make_fixnum (double_click_count),
+			XCAR (event->arg), Fcons (XCAR (XCDR (event->arg)),
+						  XCAR (XCDR (XCDR (event->arg)))));
+        else if (NUMBERP (event->arg))
           return list4 (head, position, make_fixnum (double_click_count),
                         event->arg);
 	else if (event->modifiers & (double_modifier | triple_modifier))
diff --git a/src/termhooks.h b/src/termhooks.h
index e7539bbce2..b274be9e3c 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -119,7 +119,10 @@ #define EMACS_TERMHOOKS_H
 				   .timestamp gives a timestamp (in
 				   milliseconds) for the event.
                                    .arg may contain the number of
-                                   lines to scroll.  */
+                                   lines to scroll, or a list of
+				   the form (NUMBER-OF-LINES . (X Y)) where
+				   X and Y are the number of pixels
+				   on each axis to scroll by.  */
   HORIZ_WHEEL_EVENT,            /* A wheel event generated by a second
                                    horizontal wheel that is present on some
                                    mice. See WHEEL_EVENT.  */
diff --git a/src/xfns.c b/src/xfns.c
index b33b40b330..0ea43d1330 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -8085,6 +8085,7 @@ syms_of_xfns (void)
 
 #ifdef HAVE_XINPUT2
   DEFSYM (Qxinput2, "xinput2");
+
   Fprovide (Qxinput2, Qnil);
 #endif
 
diff --git a/src/xterm.c b/src/xterm.c
index 63754a2cb6..b98b7d1889 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -556,6 +556,7 @@ xi_reset_scroll_valuators_for_device_id (struct x_display_info *dpyinfo, int id)
     {
       valuator = &device->valuators[i];
       valuator->invalid_p = true;
+      valuator->emacs_value = 0.0;
     }
 
   return;
@@ -9921,8 +9922,6 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #endif
 	    goto XI_OTHER;
 	  case XI_Motion:
-	    /* First test if there is some kind of scroll event
-	       here! */
 	    states = &xev->valuators;
 	    values = states->values;
 
@@ -9932,10 +9931,8 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 	      {
 		if (XIMaskIsSet (states->mask, i))
 		  {
-		    block_input ();
-
 		    struct xi_scroll_valuator_t *val;
-		    double delta;
+		    double delta, scroll_unit;
 
 		    delta = x_get_scroll_valuator_delta (dpyinfo, xev->deviceid,
 							 i, *values, &val);
@@ -9943,6 +9940,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 		    if (delta != DBL_MAX)
 		      {
 			f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+			scroll_unit = pow (FRAME_PIXEL_HEIGHT (f), 2.0 / 3.0);
 			found_valuator = true;
 
 			if (signbit (delta) != signbit (val->emacs_value))
@@ -9950,15 +9948,16 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 
 			val->emacs_value += delta;
 
+			if (x_coalesce_scroll_events
+			    && (fabs (val->emacs_value) < 1))
+			  continue;
+
 			if (!f)
 			  {
 			    f = x_any_window_to_frame (dpyinfo, xev->event);
 
 			    if (!f)
-			      {
-				unblock_input ();
-				goto XI_OTHER;
-			      }
+			      goto XI_OTHER;
 			  }
 
 			bool s = signbit (val->emacs_value);
@@ -9975,13 +9974,26 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 			inev.ie.modifiers
 			  |= x_x_to_emacs_modifiers (dpyinfo,
 						     xev->mods.effective);
-			inev.ie.arg = Qnil;
+
+			if (val->horizontal)
+			  {
+			    inev.ie.arg
+			      = list3 (Qnil,
+				       make_float (val->emacs_value
+						   * scroll_unit),
+				       make_float (0));
+			  }
+                        else
+			  {
+			    inev.ie.arg = list3 (Qnil, make_float (0),
+						 make_float (val->emacs_value
+							     * scroll_unit));
+			  }
 
 			kbd_buffer_store_event_hold (&inev.ie, hold_quit);
 
 			val->emacs_value = 0;
 		      }
-		    unblock_input ();
 		    values++;
 		  }
 
@@ -15048,4 +15060,11 @@ syms_of_xterm (void)
 consuming frame position adjustments.  In newer versions of GTK, Emacs
 always uses gtk_window_move and ignores the value of this variable.  */);
   x_gtk_use_window_move = true;
+
+  DEFVAR_BOOL ("x-coalesce-scroll-events", x_coalesce_scroll_events,
+	       doc: /* Non-nil means to only send one wheel event for each scroll unit.
+Otherwise, a wheel event will be sent every time the mouse wheel is
+moved.  This option is only effective when Emacs is built with XInput
+2.  */);
+  x_coalesce_scroll_events = true;
 }
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-18 10:27                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-19 13:05                   ` Eli Zaretskii
  2021-11-19 13:10                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 18+ messages in thread
From: Eli Zaretskii @ 2021-11-19 13:05 UTC (permalink / raw)
  To: Po Lu; +Cc: rpluim, 51891

> From: Po Lu <luangruo@yahoo.com>
> Cc: rpluim@gmail.com,  51891@debbugs.gnu.org
> Date: Thu, 18 Nov 2021 18:27:44 +0800
> 
> Po Lu <luangruo@yahoo.com> writes:
> 
> > That makes sense, I'll modify the change in a bit to report scroll
> > deltas as a pair of (DELTA-X . DELTA-Y) instead.
> 
> Here it is, and thanks in advance.

Thanks, LGTM.  But I wonder whether we should have some more prominent
explanations in commands.texi about the significance of these wheel
events, and also some index entry to allow to find this quickly.





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-18  9:17               ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-18 10:27                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-19 13:09                 ` Eli Zaretskii
  2021-11-19 13:24                   ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  1 sibling, 1 reply; 18+ messages in thread
From: Eli Zaretskii @ 2021-11-19 13:09 UTC (permalink / raw)
  To: Po Lu; +Cc: rpluim, 51891

> From: Po Lu <luangruo@yahoo.com>
> Cc: rpluim@gmail.com,  51891@debbugs.gnu.org
> Date: Thu, 18 Nov 2021 17:17:25 +0800
> 
> >> An amount of scrolling that would previously generate a `mouse-4' or
> >> `mouse-5' event.
> 
> > That doesn't really answer my question.  Let me ask it differently:
> > how does "scroll unit" differ from "every time the wheel is moved"?
> 
> Basically, even tiny movements of the scroll wheel will cause XInput 2
> to generate wheel events.  This makes the behavior stay like the
> original by default, where you have to scroll 96 or so pixels before a
> wheel event is actually sent.  (This is generally true, but certain mice
> may behave differently.)

Then the first sentence should probably be

  Non-nil means send a wheel event only for scrolling at least one screen line.





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-19 13:05                   ` Eli Zaretskii
@ 2021-11-19 13:10                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-19 13:23                       ` Eli Zaretskii
  0 siblings, 1 reply; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-19 13:10 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: rpluim, 51891

Eli Zaretskii <eliz@gnu.org> writes:

> Thanks, LGTM.  But I wonder whether we should have some more prominent
> explanations in commands.texi about the significance of these wheel
> events, and also some index entry to allow to find this quickly.

I have no idea what further explanation to put in commands.texi.  As for
an idex entry, how about "pixel wheel events" or "pixel deltas in wheel
events"?

Thanks.





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-19 13:10                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-19 13:23                       ` Eli Zaretskii
  2021-11-20  0:30                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 18+ messages in thread
From: Eli Zaretskii @ 2021-11-19 13:23 UTC (permalink / raw)
  To: Po Lu; +Cc: rpluim, 51891

> From: Po Lu <luangruo@yahoo.com>
> Cc: rpluim@gmail.com,  51891@debbugs.gnu.org
> Date: Fri, 19 Nov 2021 21:10:14 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > Thanks, LGTM.  But I wonder whether we should have some more prominent
> > explanations in commands.texi about the significance of these wheel
> > events, and also some index entry to allow to find this quickly.
> 
> I have no idea what further explanation to put in commands.texi.

Some text which makes it clear how these additional POSITION
parameters could be used by Lisp programs.

> As for an idex entry, how about "pixel wheel events" or "pixel
> deltas in wheel events"?

I think "pixel-resolution wheel events".





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-19 13:09                 ` Eli Zaretskii
@ 2021-11-19 13:24                   ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-19 13:55                     ` Eli Zaretskii
  0 siblings, 1 reply; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-19 13:24 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: rpluim, 51891

[-- Attachment #1: Type: text/plain, Size: 278 bytes --]

Eli Zaretskii <eliz@gnu.org> writes:

> Then the first sentence should probably be
>
>   Non-nil means send a wheel event only for scrolling at least one screen line.

Thanks.  Would it be OK for me to install this version of the change
that has the changes you proposed made?


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Expose-pixel-wise-wheel-events-to-Lisp.patch --]
[-- Type: text/x-patch, Size: 6316 bytes --]

From 62285bb2debd7226110df94f8971df0e97efe672 Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Tue, 16 Nov 2021 19:39:50 +0800
Subject: [PATCH] Expose pixel-wise wheel events to Lisp

* doc/lispref/commands.texi (Misc Events): Document changes to
wheel events.

* src/keyboard.c (make_lispy_event): Handle wheel events with
pixel delta data.
* src/termhooks.h (WHEEL_EVENT): Document changes to
WHEEL_EVENT args.
* src/xfns.c (syms_of_xfns): Declare new symbols.
* src/xterm.c (handle_one_xevent): Give wheel events pixel delta
data.
(x_coalesce_scroll_events): New user option.
---
 doc/lispref/commands.texi |  5 ++++-
 src/keyboard.c            |  6 +++++-
 src/termhooks.h           |  5 ++++-
 src/xfns.c                |  1 +
 src/xterm.c               | 41 ++++++++++++++++++++++++++++-----------
 5 files changed, 44 insertions(+), 14 deletions(-)

diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi
index 1509c200e0..a32548e9d5 100644
--- a/doc/lispref/commands.texi
+++ b/doc/lispref/commands.texi
@@ -1985,7 +1985,10 @@ Misc Events
 These kinds of event are generated by moving a mouse wheel.  The
 @var{position} element is a mouse position list (@pxref{Click
 Events}), specifying the position of the mouse cursor when the event
-occurred.
+occurred.  The event may have additional arguments after
+@var{position}.  The third argument after @var{position}, if present,
+is a pair of the form @w{@code{(@var{x} . @var{y})}}, where @var{x}
+and @var{y} are the number of pixels to scroll by in each axis.
 
 @vindex mouse-wheel-up-event
 @vindex mouse-wheel-down-event
diff --git a/src/keyboard.c b/src/keyboard.c
index c3bc8307d7..0c48790ce8 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -5980,7 +5980,11 @@ make_lispy_event (struct input_event *event)
 				      ASIZE (wheel_syms));
 	}
 
-        if (NUMBERP (event->arg))
+	if (CONSP (event->arg))
+	  return list5 (head, position, make_fixnum (double_click_count),
+			XCAR (event->arg), Fcons (XCAR (XCDR (event->arg)),
+						  XCAR (XCDR (XCDR (event->arg)))));
+        else if (NUMBERP (event->arg))
           return list4 (head, position, make_fixnum (double_click_count),
                         event->arg);
 	else if (event->modifiers & (double_modifier | triple_modifier))
diff --git a/src/termhooks.h b/src/termhooks.h
index e7539bbce2..b274be9e3c 100644
--- a/src/termhooks.h
+++ b/src/termhooks.h
@@ -119,7 +119,10 @@ #define EMACS_TERMHOOKS_H
 				   .timestamp gives a timestamp (in
 				   milliseconds) for the event.
                                    .arg may contain the number of
-                                   lines to scroll.  */
+                                   lines to scroll, or a list of
+				   the form (NUMBER-OF-LINES . (X Y)) where
+				   X and Y are the number of pixels
+				   on each axis to scroll by.  */
   HORIZ_WHEEL_EVENT,            /* A wheel event generated by a second
                                    horizontal wheel that is present on some
                                    mice. See WHEEL_EVENT.  */
diff --git a/src/xfns.c b/src/xfns.c
index b33b40b330..0ea43d1330 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -8085,6 +8085,7 @@ syms_of_xfns (void)
 
 #ifdef HAVE_XINPUT2
   DEFSYM (Qxinput2, "xinput2");
+
   Fprovide (Qxinput2, Qnil);
 #endif
 
diff --git a/src/xterm.c b/src/xterm.c
index 63754a2cb6..ed3921f286 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -556,6 +556,7 @@ xi_reset_scroll_valuators_for_device_id (struct x_display_info *dpyinfo, int id)
     {
       valuator = &device->valuators[i];
       valuator->invalid_p = true;
+      valuator->emacs_value = 0.0;
     }
 
   return;
@@ -9921,8 +9922,6 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 #endif
 	    goto XI_OTHER;
 	  case XI_Motion:
-	    /* First test if there is some kind of scroll event
-	       here! */
 	    states = &xev->valuators;
 	    values = states->values;
 
@@ -9932,10 +9931,8 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 	      {
 		if (XIMaskIsSet (states->mask, i))
 		  {
-		    block_input ();
-
 		    struct xi_scroll_valuator_t *val;
-		    double delta;
+		    double delta, scroll_unit;
 
 		    delta = x_get_scroll_valuator_delta (dpyinfo, xev->deviceid,
 							 i, *values, &val);
@@ -9943,6 +9940,7 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 		    if (delta != DBL_MAX)
 		      {
 			f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+			scroll_unit = pow (FRAME_PIXEL_HEIGHT (f), 2.0 / 3.0);
 			found_valuator = true;
 
 			if (signbit (delta) != signbit (val->emacs_value))
@@ -9950,15 +9948,16 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 
 			val->emacs_value += delta;
 
+			if (x_coalesce_scroll_events
+			    && (fabs (val->emacs_value) < 1))
+			  continue;
+
 			if (!f)
 			  {
 			    f = x_any_window_to_frame (dpyinfo, xev->event);
 
 			    if (!f)
-			      {
-				unblock_input ();
-				goto XI_OTHER;
-			      }
+			      goto XI_OTHER;
 			  }
 
 			bool s = signbit (val->emacs_value);
@@ -9975,13 +9974,26 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 			inev.ie.modifiers
 			  |= x_x_to_emacs_modifiers (dpyinfo,
 						     xev->mods.effective);
-			inev.ie.arg = Qnil;
+
+			if (val->horizontal)
+			  {
+			    inev.ie.arg
+			      = list3 (Qnil,
+				       make_float (val->emacs_value
+						   * scroll_unit),
+				       make_float (0));
+			  }
+                        else
+			  {
+			    inev.ie.arg = list3 (Qnil, make_float (0),
+						 make_float (val->emacs_value
+							     * scroll_unit));
+			  }
 
 			kbd_buffer_store_event_hold (&inev.ie, hold_quit);
 
 			val->emacs_value = 0;
 		      }
-		    unblock_input ();
 		    values++;
 		  }
 
@@ -15048,4 +15060,11 @@ syms_of_xterm (void)
 consuming frame position adjustments.  In newer versions of GTK, Emacs
 always uses gtk_window_move and ignores the value of this variable.  */);
   x_gtk_use_window_move = true;
+
+  DEFVAR_BOOL ("x-coalesce-scroll-events", x_coalesce_scroll_events,
+	       doc: /* Non-nil means send a wheel event only for scrolling at least one screen line.
+Otherwise, a wheel event will be sent every time the mouse wheel is
+moved.  This option is only effective when Emacs is built with XInput
+2.  */);
+  x_coalesce_scroll_events = true;
 }
-- 
2.31.1


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0001-Add-support-for-event-processing-via-XInput-2.patch --]
[-- Type: text/x-patch, Size: 57750 bytes --]

From 765b343f3fc92c55c7ca48360c4e09ca2793d44d Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Sat, 16 Oct 2021 13:15:36 +0800
Subject: [PATCH 1/2] Add support for event processing via XInput 2

* configure.ac: Add an option to use XInput 2 if available.
* src/Makefile.in (XINPUT_LIBS, XINPUT_CFLAGS): New variables.
(EMACS_CFLAGS): Add Xinput CFLAGS.
(LIBES): Add XInput libs.
* src/xmenu.c (popup_activated_flag): Expose flag if
XInput 2 is available.
* src/xfns.c (x_window): Set XInput 2 event mask.
(setup_xi_event_mask): New function.
(syms_of_xfns): Provide XInput 2 feature.
* src/xterm.c (x_detect_focus_change): Handle XInput 2
GenericEvents.
(handle_one_xevent): Handle XInput 2 events.
(x_term_init): Ask the server for XInput 2 support and set
xkb_desc if available.
(x_delete_terminal): Free XKB kb desc if it exists, and free
XI2 devices if they exist.
(xi_grab_or_ungrab_device)
(xi_reset_scroll_valuators_for_device_id)
(x_free_xi_devices, x_init_master_valuators): New functions.
(x_get_scroll_valuator_delta): New function.
(init_xterm): Don't tell GTK to only use Core Input when built
with XInput 2 support.
* src/xterm.h (struct x_display_info): Add fields for XKB
and XI2 support.
* src/gtkutil.c (xg_event_is_for_menubar): Handle
XIDeviceEvents.
(xg_is_menu_window): New function.
(xg_event_is_for_scrollbar): Handle XIDeviceEvents.
* etc/NEWS: Document changes.

* lisp/mwheel.el (mouse-wheel-down-alternate-event)
(mouse-wheel-up-alternate-event)
(mouse-wheel-left-alternate-event)
(mouse-wheel-right-alternate-event): New user options.

(mouse-wheel-text-scale)
(mwheel-scroll): Test for alternate events.
(mouse-wheel--setup-bindings): Set up bindings for alternate
buttons.
---
 configure.ac    |   22 +
 etc/NEWS        |   17 +
 lisp/mwheel.el  |   66 ++-
 src/Makefile.in |    7 +-
 src/gtkutil.c   |   72 ++-
 src/gtkutil.h   |    4 +
 src/xfns.c      |   50 +++
 src/xmenu.c     |    4 +
 src/xterm.c     | 1107 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/xterm.h     |   44 ++
 10 files changed, 1372 insertions(+), 21 deletions(-)

diff --git a/configure.ac b/configure.ac
index c231c2ceae..239bf72f71 100644
--- a/configure.ac
+++ b/configure.ac
@@ -487,6 +487,7 @@ AC_DEFUN
 OPTION_DEFAULT_ON([threads],[don't compile with elisp threading support])
 OPTION_DEFAULT_OFF([native-compilation],[compile with Emacs Lisp native compiler support])
 OPTION_DEFAULT_OFF([cygwin32-native-compilation],[use native compilation on 32-bit Cygwin])
+OPTION_DEFAULT_OFF([xinput2],[use version 2.0 the X Input Extension for input])
 
 AC_ARG_WITH([file-notification],[AS_HELP_STRING([--with-file-notification=LIB],
  [use a file notification library (LIB one of: yes, inotify, kqueue, gfile, w32, no)])],
@@ -4237,6 +4238,26 @@ AC_DEFUN
 AC_SUBST(XFIXES_CFLAGS)
 AC_SUBST(XFIXES_LIBS)
 
+## Use XInput 2.0 if available
+HAVE_XINPUT2=no
+if test "${HAVE_X11}" = "yes" && test "${with_xinput2}" != "no"; then
+   EMACS_CHECK_MODULES([XINPUT], [xi])
+   if test $HAVE_XINPUT = yes; then
+     # Now check for XInput2.h
+     AC_CHECK_HEADER(X11/extensions/XInput2.h,
+       [AC_CHECK_LIB(Xi, XIGrabButton, HAVE_XINPUT2=yes)])
+   fi
+   if test $HAVE_XINPUT2 = yes; then
+     AC_DEFINE(HAVE_XINPUT2, 1, [Define to 1 if the X Input Extension version 2.0 is present.])
+     if test "$USE_GTK_TOOLKIT" = "GTK2"; then
+       AC_MSG_WARN([You are building Emacs with GTK+ 2 and the X Input Extension version 2.
+This might lead to problems if your version of GTK+ is not built with support for XInput 2.])
+     fi
+   fi
+fi
+AC_SUBST(XINPUT_CFLAGS)
+AC_SUBST(XINPUT_LIBS)
+
 ### Use Xdbe (-lXdbe) if available
 HAVE_XDBE=no
 if test "${HAVE_X11}" = "yes"; then
@@ -6011,6 +6032,7 @@ AC_DEFUN
   Does Emacs support legacy unexec dumping?               ${with_unexec}
   Which dumping strategy does Emacs use?                  ${with_dumping}
   Does Emacs have native lisp compiler?                   ${HAVE_NATIVE_COMP}
+  Does Emacs use version 2 of the the X Input Extension?  ${HAVE_XINPUT2}
 "])
 
 if test -n "${EMACSDATA}"; then
diff --git a/etc/NEWS b/etc/NEWS
index 80be6c0e49..ca6b946843 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -30,6 +30,14 @@ The file is typically installed using a file name akin to
 If a constant file name is required, the file can be renamed to
 "emacs.pdmp", and Emacs will find it during startup anyway.
 
+** Emacs now supports use of XInput 2 for input events.
+If your X server has support and you have the XInput 2 development headers
+installed, you can configure Emacs with the option '--with-xinput2' to enable
+this support.
+
+The named feature `xinput2' can be used to test for the presence of
+XInput 2 support from Lisp programs.
+
 \f
 * Startup Changes in Emacs 29.1
 
@@ -217,6 +225,15 @@ The user option 'comint-terminfo-terminal' and variable
 'system-uses-terminfo' can now be set as connection-local variables to
 change the terminal used on a remote host.
 
+** Mwheel
+
+---
+*** New user options for alternate wheel events.
+The options 'mouse-wheel-down-alternate-event', 'mouse-wheel-up-alternate-event',
+'mouse-wheel-left-alternate-event', and 'mouse-wheel-right-alternate-event' have
+been added to better support systems where two kinds of wheel events can be
+received.
+
 \f
 * Changes in Specialized Modes and Packages in Emacs 29.1
 
diff --git a/lisp/mwheel.el b/lisp/mwheel.el
index 51410e3ef4..3d0b8f07cb 100644
--- a/lisp/mwheel.el
+++ b/lisp/mwheel.el
@@ -63,6 +63,13 @@ mouse-wheel-down-event
   :type 'symbol
   :set 'mouse-wheel-change-button)
 
+(defcustom mouse-wheel-down-alternate-event
+  (when (featurep 'xinput2) 'wheel-up)
+  "Alternative wheel down event to consider."
+  :group 'mouse
+  :type 'symbol
+  :set 'mouse-wheel-change-button)
+
 (defcustom mouse-wheel-up-event
   (if (or (featurep 'w32-win) (featurep 'ns-win))
       'wheel-down
@@ -72,6 +79,13 @@ mouse-wheel-up-event
   :type 'symbol
   :set 'mouse-wheel-change-button)
 
+(defcustom mouse-wheel-up-alternate-event
+  (when (featurep 'xinput2) 'wheel-down)
+  "Alternative wheel up event to consider."
+  :group 'mouse
+  :type 'symbol
+  :set 'mouse-wheel-change-button)
+
 (defcustom mouse-wheel-click-event 'mouse-2
   "Event that should be temporarily inhibited after mouse scrolling.
 The mouse wheel is typically on the mouse-2 button, so it may easily
@@ -226,12 +240,20 @@ mouse-wheel-left-event
     'mouse-6)
   "Event used for scrolling left.")
 
+(defvar mouse-wheel-left-alternate-event
+  (when (featurep 'xinput2) 'wheel-left)
+  "Alternative wheel left event to consider.")
+
 (defvar mouse-wheel-right-event
   (if (or (featurep 'w32-win) (featurep 'ns-win))
       'wheel-right
     'mouse-7)
   "Event used for scrolling right.")
 
+(defvar mouse-wheel-right-alternate-event
+  (when (featurep 'xinput2) 'wheel-right)
+  "Alternative wheel right event to consider.")
+
 (defun mouse-wheel--get-scroll-window (event)
   "Return window for mouse wheel event EVENT.
 If `mouse-wheel-follow-mouse' is non-nil, return the window that
@@ -296,14 +318,16 @@ mwheel-scroll
     (condition-case nil
         (unwind-protect
 	    (let ((button (mwheel-event-button event)))
-              (cond ((and (eq amt 'hscroll) (eq button mouse-wheel-down-event))
+              (cond ((and (eq amt 'hscroll) (memq button (list mouse-wheel-down-event
+                                                               mouse-wheel-down-alternate-event)))
                      (when (and (natnump arg) (> arg 0))
                        (setq mouse-wheel-scroll-amount-horizontal arg))
                      (funcall (if mouse-wheel-flip-direction
                                   mwheel-scroll-left-function
                                 mwheel-scroll-right-function)
                               mouse-wheel-scroll-amount-horizontal))
-                    ((eq button mouse-wheel-down-event)
+                    ((memq button (list mouse-wheel-down-event
+                                        mouse-wheel-down-alternate-event))
                      (condition-case nil (funcall mwheel-scroll-down-function amt)
                        ;; Make sure we do indeed scroll to the beginning of
                        ;; the buffer.
@@ -318,23 +342,27 @@ mwheel-scroll
                           ;; for a reason that escapes me.  This problem seems
                           ;; to only affect scroll-down.  --Stef
                           (set-window-start (selected-window) (point-min))))))
-                    ((and (eq amt 'hscroll) (eq button mouse-wheel-up-event))
+                    ((and (eq amt 'hscroll) (memq button (list mouse-wheel-up-event
+                                                               mouse-wheel-up-alternate-event)))
                      (when (and (natnump arg) (> arg 0))
                        (setq mouse-wheel-scroll-amount-horizontal arg))
                      (funcall (if mouse-wheel-flip-direction
                                   mwheel-scroll-right-function
                                 mwheel-scroll-left-function)
                               mouse-wheel-scroll-amount-horizontal))
-                    ((eq button mouse-wheel-up-event)
+                    ((memq button (list mouse-wheel-up-event
+                                        mouse-wheel-up-alternate-event))
                      (condition-case nil (funcall mwheel-scroll-up-function amt)
                        ;; Make sure we do indeed scroll to the end of the buffer.
                        (end-of-buffer (while t (funcall mwheel-scroll-up-function)))))
-                    ((eq button mouse-wheel-left-event) ; for tilt scroll
+                    ((memq button (list mouse-wheel-left-event
+                                        mouse-wheel-left-alternate-event)) ; for tilt scroll
                      (when mouse-wheel-tilt-scroll
                        (funcall (if mouse-wheel-flip-direction
                                     mwheel-scroll-right-function
                                   mwheel-scroll-left-function) amt)))
-                    ((eq button mouse-wheel-right-event) ; for tilt scroll
+                    ((memq button (list mouse-wheel-right-event
+                                        mouse-wheel-right-alternate-event)) ; for tilt scroll
                      (when mouse-wheel-tilt-scroll
                        (funcall (if mouse-wheel-flip-direction
                                     mwheel-scroll-left-function
@@ -378,9 +406,11 @@ mouse-wheel-text-scale
         (button (mwheel-event-button event)))
     (select-window scroll-window 'mark-for-redisplay)
     (unwind-protect
-        (cond ((eq button mouse-wheel-down-event)
+        (cond ((memq button (list mouse-wheel-down-event
+                                  mouse-wheel-down-alternate-event))
                (text-scale-increase 1))
-              ((eq button mouse-wheel-up-event)
+              ((eq button (list mouse-wheel-up-event
+                                mouse-wheel-up-alternate-event))
                (text-scale-decrease 1)))
       (select-window selected-window))))
 
@@ -432,15 +462,23 @@ mouse-wheel--setup-bindings
     (cond
      ;; Bindings for changing font size.
      ((and (consp binding) (eq (cdr binding) 'text-scale))
-      (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event))
-        (mouse-wheel--add-binding `[,(list (caar binding) event)]
-                                  'mouse-wheel-text-scale)))
+      (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event
+                           mouse-wheel-down-alternate-event
+                           mouse-wheel-up-alternate-event))
+        (when event
+          (mouse-wheel--add-binding `[,(list (caar binding) event)]
+                                    'mouse-wheel-text-scale))))
      ;; Bindings for scrolling.
      (t
       (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event
-                           mouse-wheel-left-event mouse-wheel-right-event))
-        (dolist (key (mouse-wheel--create-scroll-keys binding event))
-          (mouse-wheel--add-binding key 'mwheel-scroll)))))))
+                           mouse-wheel-left-event mouse-wheel-right-event
+                           mouse-wheel-down-alternate-event
+                           mouse-wheel-up-alternate-event
+                           mouse-wheel-left-alternate-event
+                           mouse-wheel-right-alternate-event))
+        (when event
+          (dolist (key (mouse-wheel--create-scroll-keys binding event))
+            (mouse-wheel--add-binding key 'mwheel-scroll))))))))
 
 (when mouse-wheel-mode
   (mouse-wheel--setup-bindings))
diff --git a/src/Makefile.in b/src/Makefile.in
index 4c5535f8ad..0aaaf91d39 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -258,6 +258,9 @@ XINERAMA_CFLAGS =
 XFIXES_LIBS = @XFIXES_LIBS@
 XFIXES_CFLAGS = @XFIXES_CFLAGS@
 
+XINPUT_LIBS = @XINPUT_LIBS@
+XINPUT_CFLAGS = @XINPUT_CFLAGS@
+
 XDBE_LIBS = @XDBE_LIBS@
 XDBE_CFLAGS = @XDBE_CFLAGS@
 
@@ -374,7 +377,7 @@ EMACS_CFLAGS=
   $(GNUSTEP_CFLAGS) $(CFLAGS_SOUND) $(RSVG_CFLAGS) $(IMAGEMAGICK_CFLAGS) \
   $(PNG_CFLAGS) $(LIBXML2_CFLAGS) $(LIBGCCJIT_CFLAGS) $(DBUS_CFLAGS) \
   $(XRANDR_CFLAGS) $(XINERAMA_CFLAGS) $(XFIXES_CFLAGS) $(XDBE_CFLAGS) \
-  $(WEBKIT_CFLAGS) $(WEBP_CFLAGS) $(LCMS2_CFLAGS) \
+  $(XINPUT_CFLAGS) $(WEBP_CFLAGS) $(WEBKIT_CFLAGS) $(LCMS2_CFLAGS) \
   $(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \
   $(HARFBUZZ_CFLAGS) $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \
   $(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) \
@@ -524,7 +527,7 @@ LIBES =
    $(FREETYPE_LIBS) $(FONTCONFIG_LIBS) $(HARFBUZZ_LIBS) $(LIBOTF_LIBS) $(M17N_FLT_LIBS) \
    $(LIBGNUTLS_LIBS) $(LIB_PTHREAD) $(GETADDRINFO_A_LIBS) $(LCMS2_LIBS) \
    $(NOTIFY_LIBS) $(LIB_MATH) $(LIBZ) $(LIBMODULES) $(LIBSYSTEMD_LIBS) \
-   $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS)
+   $(JSON_LIBS) $(LIBGMP) $(LIBGCCJIT_LIBS) $(XINPUT_LIBS)
 
 ## FORCE it so that admin/unidata can decide whether this file is
 ## up-to-date.  Although since charprop depends on bootstrap-emacs,
diff --git a/src/gtkutil.c b/src/gtkutil.c
index a9eabf47d8..9e676cd025 100644
--- a/src/gtkutil.c
+++ b/src/gtkutil.c
@@ -47,6 +47,10 @@ Copyright (C) 2003-2021 Free Software Foundation, Inc.
 
 #include <gdk/gdkkeysyms.h>
 
+#ifdef HAVE_XINPUT2
+#include <X11/extensions/XInput2.h>
+#endif
+
 #ifdef HAVE_XFT
 #include <X11/Xft/Xft.h>
 #endif
@@ -839,6 +843,23 @@ my_log_handler (const gchar *log_domain, GLogLevelFlags log_level,
 }
 #endif
 
+#if defined HAVE_GTK3 && defined HAVE_XINPUT2
+bool
+xg_is_menu_window (Display *dpy, Window wdesc)
+{
+  GtkWidget *gwdesc = xg_win_to_widget (dpy, wdesc);
+
+  if (GTK_IS_WINDOW (gwdesc))
+    {
+      GtkWidget *fw = gtk_bin_get_child (GTK_BIN (gwdesc));
+      if (GTK_IS_MENU (fw))
+	return true;
+    }
+
+  return false;
+}
+#endif
+
 /* Make a geometry string and pass that to GTK.  It seems this is the
    only way to get geometry position right if the user explicitly
    asked for a position when starting Emacs.
@@ -3589,6 +3610,18 @@ xg_event_is_for_menubar (struct frame *f, const XEvent *event)
 
   if (! x->menubar_widget) return 0;
 
+#ifdef HAVE_XINPUT2
+  XIDeviceEvent *xev = (XIDeviceEvent *) event->xcookie.data;
+  if (event->type == GenericEvent) /* XI_ButtonPress or XI_ButtonRelease */
+    {
+      if (! (xev->event_x >= 0
+	     && xev->event_x < FRAME_PIXEL_WIDTH (f)
+	     && xev->event_y >= 0
+	     && xev->event_y < FRAME_MENUBAR_HEIGHT (f)))
+	return 0;
+    }
+  else
+#endif
   if (! (event->xbutton.x >= 0
          && event->xbutton.x < FRAME_PIXEL_WIDTH (f)
          && event->xbutton.y >= 0
@@ -3597,7 +3630,12 @@ xg_event_is_for_menubar (struct frame *f, const XEvent *event)
     return 0;
 
   gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
-  gw = gdk_x11_window_lookup_for_display (gdpy, event->xbutton.window);
+#ifdef HAVE_XINPUT2
+  if (event->type == GenericEvent)
+    gw = gdk_x11_window_lookup_for_display (gdpy, xev->event);
+  else
+#endif
+    gw = gdk_x11_window_lookup_for_display (gdpy, event->xbutton.window);
   if (! gw) return 0;
   gevent.any.window = gw;
   gevent.any.type = GDK_NOTHING;
@@ -4244,7 +4282,20 @@ xg_event_is_for_scrollbar (struct frame *f, const XEvent *event)
 {
   bool retval = 0;
 
+#ifdef HAVE_XINPUT2
+  XIDeviceEvent *xev = (XIDeviceEvent *) event->xcookie.data;
+  if (f && ((FRAME_DISPLAY_INFO (f)->supports_xi2
+	     && event->type == GenericEvent
+	     && (event->xgeneric.extension
+		 == FRAME_DISPLAY_INFO (f)->xi2_opcode)
+	     && ((event->xgeneric.evtype == XI_ButtonPress
+		  && xev->detail < 4)
+		 || (event->xgeneric.evtype == XI_Motion)))
+	    || (event->type == ButtonPress
+		&& event->xbutton.button < 4)))
+#else
   if (f && event->type == ButtonPress && event->xbutton.button < 4)
+#endif /* HAVE_XINPUT2 */
     {
       /* Check if press occurred outside the edit widget.  */
       GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (FRAME_X_DISPLAY (f));
@@ -4262,10 +4313,29 @@ xg_event_is_for_scrollbar (struct frame *f, const XEvent *event)
       gwin = gdk_display_get_window_at_pointer (gdpy, NULL, NULL);
 #endif
       retval = gwin != gtk_widget_get_window (f->output_data.x->edit_widget);
+#ifdef HAVE_XINPUT2
+      GtkWidget *grab = gtk_grab_get_current ();
+      if (event->type == GenericEvent
+	  && event->xgeneric.evtype == XI_Motion)
+	retval = retval || (grab && GTK_IS_SCROLLBAR (grab));
+#endif
     }
+#ifdef HAVE_XINPUT2
+  else if (f && ((FRAME_DISPLAY_INFO (f)->supports_xi2
+		  && event->type == GenericEvent
+		  && (event->xgeneric.extension
+		      == FRAME_DISPLAY_INFO (f)->xi2_opcode)
+		  && ((event->xgeneric.evtype == XI_ButtonRelease
+		       && xev->detail < 4)
+		      || (event->xgeneric.evtype == XI_Motion)))
+		 || ((event->type == ButtonRelease
+		      && event->xbutton.button < 4)
+		     || event->type == MotionNotify)))
+#else
   else if (f
            && ((event->type == ButtonRelease && event->xbutton.button < 4)
                || event->type == MotionNotify))
+#endif /* HAVE_XINPUT2 */
     {
       /* If we are releasing or moving the scroll bar, it has the grab.  */
       GtkWidget *w = gtk_grab_get_current ();
diff --git a/src/gtkutil.h b/src/gtkutil.h
index 31a12cd5d3..95dd75b7fa 100644
--- a/src/gtkutil.h
+++ b/src/gtkutil.h
@@ -192,6 +192,10 @@ #define XG_ITEM_DATA "emacs_menuitem"
 extern void xg_print_frames_dialog (Lisp_Object);
 #endif
 
+#if defined HAVE_GTK3 && defined HAVE_XINPUT2
+extern bool xg_is_menu_window (Display *dpy, Window);
+#endif
+
 /* Mark all callback data that are Lisp_object:s during GC.  */
 extern void xg_mark_data (void);
 
diff --git a/src/xfns.c b/src/xfns.c
index 785ae3baca..b33b40b330 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -57,6 +57,10 @@ Copyright (C) 1989, 1992-2021 Free Software Foundation, Inc.
 #include <X11/extensions/Xdbe.h>
 #endif
 
+#ifdef HAVE_XINPUT2
+#include <X11/extensions/XInput2.h>
+#endif
+
 #ifdef USE_X_TOOLKIT
 #include <X11/Shell.h>
 
@@ -2912,6 +2916,37 @@ initial_set_up_x_back_buffer (struct frame *f)
   unblock_input ();
 }
 
+#if defined HAVE_XINPUT2 && !defined USE_GTK
+static void
+setup_xi_event_mask (struct frame *f)
+{
+  XIEventMask mask;
+  ptrdiff_t l = XIMaskLen (XI_LASTEVENT);
+  unsigned char *m;
+
+  mask.mask = m = alloca (l);
+  memset (m, 0, l);
+  mask.mask_len = l;
+  mask.deviceid = XIAllMasterDevices;
+
+  XISetMask (m, XI_ButtonPress);
+  XISetMask (m, XI_ButtonRelease);
+  XISetMask (m, XI_KeyPress);
+  XISetMask (m, XI_KeyRelease);
+  XISetMask (m, XI_Motion);
+  XISetMask (m, XI_Enter);
+  XISetMask (m, XI_Leave);
+  XISetMask (m, XI_FocusIn);
+  XISetMask (m, XI_FocusOut);
+  XISetMask (m, XI_PropertyEvent);
+  XISetMask (m, XI_HierarchyChanged);
+  XISetMask (m, XI_DeviceChanged);
+  XISelectEvents (FRAME_X_DISPLAY (f),
+		  FRAME_X_WINDOW (f),
+		  &mask, 1);
+}
+#endif
+
 #ifdef USE_X_TOOLKIT
 
 /* Create and set up the X widget for frame F.  */
@@ -3074,6 +3109,11 @@ x_window (struct frame *f, long window_prompting)
   class_hints.res_class = SSDATA (Vx_resource_class);
   XSetClassHint (FRAME_X_DISPLAY (f), XtWindow (shell_widget), &class_hints);
 
+#ifdef HAVE_XINPUT2
+  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
+    setup_xi_event_mask (f);
+#endif
+
 #ifdef HAVE_X_I18N
   FRAME_XIC (f) = NULL;
   if (use_xim)
@@ -3254,6 +3294,11 @@ x_window (struct frame *f)
     }
 #endif /* HAVE_X_I18N */
 
+#ifdef HAVE_XINPUT2
+  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
+    setup_xi_event_mask (f);
+#endif
+
   validate_x_resource_name ();
 
   class_hints.res_name = SSDATA (Vx_resource_name);
@@ -8038,6 +8083,11 @@ syms_of_xfns (void)
   /* Tell Emacs about this window system.  */
   Fprovide (Qx, Qnil);
 
+#ifdef HAVE_XINPUT2
+  DEFSYM (Qxinput2, "xinput2");
+  Fprovide (Qxinput2, Qnil);
+#endif
+
 #ifdef USE_X_TOOLKIT
   Fprovide (intern_c_string ("x-toolkit"), Qnil);
 #ifdef USE_MOTIF
diff --git a/src/xmenu.c b/src/xmenu.c
index ea2cbab203..07255911f9 100644
--- a/src/xmenu.c
+++ b/src/xmenu.c
@@ -105,7 +105,11 @@ Copyright (C) 1986, 1988, 1993-1994, 1996, 1999-2021 Free Software
 \f
 /* Flag which when set indicates a dialog or menu has been posted by
    Xt on behalf of one of the widget sets.  */
+#ifndef HAVE_XINPUT2
 static int popup_activated_flag;
+#else
+int popup_activated_flag;
+#endif
 
 \f
 #ifdef USE_X_TOOLKIT
diff --git a/src/xterm.c b/src/xterm.c
index 816b6dc5a8..63754a2cb6 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -42,6 +42,10 @@ Copyright (C) 1989, 1993-2021 Free Software Foundation, Inc.
 #include <X11/extensions/Xdbe.h>
 #endif
 
+#ifdef HAVE_XINPUT2
+#include <X11/extensions/XInput2.h>
+#endif
+
 /* Load sys/types.h if not already loaded.
    In some systems loading it twice is suicidal.  */
 #ifndef makedev
@@ -223,9 +227,15 @@ #define XtNinitialState "initialState"
 static void x_check_fullscreen (struct frame *);
 static void x_check_expected_move (struct frame *, int, int);
 static void x_sync_with_move (struct frame *, int, int, bool);
+#ifndef HAVE_XINPUT2
 static int handle_one_xevent (struct x_display_info *,
 			      const XEvent *, int *,
 			      struct input_event *);
+#else
+static int handle_one_xevent (struct x_display_info *,
+			      XEvent *, int *,
+			      struct input_event *);
+#endif
 #if ! (defined USE_X_TOOLKIT || defined USE_MOTIF) && defined USE_GTK
 static int x_dispatch_event (XEvent *, Display *);
 #endif
@@ -335,6 +345,224 @@ x_extension_initialize (struct x_display_info *dpyinfo)
   dpyinfo->ext_codes = ext_codes;
 }
 
+
+#ifdef HAVE_XINPUT2
+
+/* Free all XI2 devices on dpyinfo.  */
+static void
+x_free_xi_devices (struct x_display_info *dpyinfo)
+{
+  block_input ();
+
+  if (dpyinfo->num_devices)
+    {
+      for (int i = 0; i < dpyinfo->num_devices; ++i)
+	{
+	  XIUngrabDevice (dpyinfo->display, dpyinfo->devices[i].device_id,
+			  CurrentTime);
+	  xfree (dpyinfo->devices[i].valuators);
+	}
+
+      xfree (dpyinfo->devices);
+      dpyinfo->devices = NULL;
+      dpyinfo->num_devices = 0;
+    }
+
+  unblock_input ();
+}
+
+/* Setup valuator tracking for XI2 master devices on
+   DPYINFO->display.  */
+
+static void
+x_init_master_valuators (struct x_display_info *dpyinfo)
+{
+  int ndevices;
+  XIDeviceInfo *infos;
+
+  block_input ();
+  x_free_xi_devices (dpyinfo);
+  infos = XIQueryDevice (dpyinfo->display,
+			 XIAllMasterDevices,
+			 &ndevices);
+
+  if (!ndevices)
+    {
+      XIFreeDeviceInfo (infos);
+      unblock_input ();
+      return;
+    }
+
+  int actual_devices = 0;
+  dpyinfo->devices = xmalloc (sizeof *dpyinfo->devices * ndevices);
+
+  for (int i = 0; i < ndevices; ++i)
+    {
+      XIDeviceInfo *device = &infos[i];
+
+      if (device->enabled)
+	{
+	  int actual_valuator_count = 0;
+	  struct xi_device_t *xi_device = &dpyinfo->devices[actual_devices++];
+	  xi_device->device_id = device->deviceid;
+	  xi_device->grab = 0;
+	  xi_device->valuators =
+	    xmalloc (sizeof *xi_device->valuators * device->num_classes);
+
+	  for (int c = 0; c < device->num_classes; ++c)
+	    {
+	      switch (device->classes[c]->type)
+		{
+#ifdef XIScrollClass /* XInput 2.1 */
+		case XIScrollClass:
+		  {
+		    XIScrollClassInfo *info =
+		      (XIScrollClassInfo *) device->classes[c];
+		    struct xi_scroll_valuator_t *valuator =
+		      &xi_device->valuators[actual_valuator_count++];
+
+		    valuator->horizontal
+		      = (info->scroll_type == XIScrollTypeHorizontal);
+		    valuator->invalid_p = true;
+		    valuator->emacs_value = DBL_MIN;
+		    valuator->increment = info->increment;
+		    valuator->number = info->number;
+		    break;
+		  }
+#endif
+		default:
+		  break;
+		}
+	    }
+	  xi_device->scroll_valuator_count = actual_valuator_count;
+	}
+    }
+
+  dpyinfo->num_devices = actual_devices;
+  XIFreeDeviceInfo (infos);
+  unblock_input ();
+}
+
+/* Return the delta of the scroll valuator VALUATOR_NUMBER under
+   DEVICE_ID in the display DPYINFO with VALUE.  The valuator's
+   valuator will be set to VALUE afterwards.  In case no scroll
+   valuator is found, or if device_id is not known to Emacs, DBL_MAX
+   is returned.  Otherwise, the valuator is returned in
+   VALUATOR_RETURN.  */
+static double
+x_get_scroll_valuator_delta (struct x_display_info *dpyinfo, int device_id,
+			     int valuator_number, double value,
+			     struct xi_scroll_valuator_t **valuator_return)
+{
+  block_input ();
+
+  for (int i = 0; i < dpyinfo->num_devices; ++i)
+    {
+      struct xi_device_t *device = &dpyinfo->devices[i];
+
+      if (device->device_id == device_id)
+	{
+	  for (int j = 0; j < device->scroll_valuator_count; ++j)
+	    {
+	      struct xi_scroll_valuator_t *sv = &device->valuators[j];
+
+	      if (sv->number == valuator_number)
+		{
+		  if (sv->invalid_p)
+		    {
+		      sv->current_value = value;
+		      sv->invalid_p = false;
+		      *valuator_return = sv;
+
+		      unblock_input ();
+		      return 0.0;
+		    }
+		  else
+		    {
+		      double delta = (sv->current_value - value) / sv->increment;
+                      sv->current_value = value;
+		      *valuator_return = sv;
+
+		      unblock_input ();
+		      return delta;
+		    }
+		}
+	    }
+
+	  unblock_input ();
+	  return DBL_MAX;
+	}
+    }
+
+  unblock_input ();
+  return DBL_MAX;
+}
+
+static struct xi_device_t *
+xi_device_from_id (struct x_display_info *dpyinfo, int deviceid)
+{
+  for (int i = 0; i < dpyinfo->num_devices; ++i)
+    {
+      if (dpyinfo->devices[i].device_id == deviceid)
+	return &dpyinfo->devices[i];
+    }
+
+  return NULL;
+}
+
+static void
+xi_grab_or_ungrab_device (struct xi_device_t *device,
+			  struct x_display_info *dpyinfo,
+			  Window window)
+{
+  XIEventMask mask;
+  ptrdiff_t l = XIMaskLen (XI_LASTEVENT);
+  unsigned char *m;
+  mask.mask = m = alloca (l);
+  memset (m, 0, l);
+  mask.mask_len = l;
+
+  XISetMask (m, XI_ButtonPress);
+  XISetMask (m, XI_ButtonRelease);
+  XISetMask (m, XI_Motion);
+  XISetMask (m, XI_Enter);
+  XISetMask (m, XI_Leave);
+
+  if (device->grab)
+    {
+      XIGrabDevice (dpyinfo->display, device->device_id, window,
+		    CurrentTime, None, GrabModeAsync,
+		    GrabModeAsync, True, &mask);
+    }
+  else
+    {
+      XIUngrabDevice (dpyinfo->display, device->device_id, CurrentTime);
+    }
+}
+
+static void
+xi_reset_scroll_valuators_for_device_id (struct x_display_info *dpyinfo, int id)
+{
+  struct xi_device_t *device = xi_device_from_id (dpyinfo, id);
+  struct xi_scroll_valuator_t *valuator;
+
+  if (!device)
+    return;
+
+  if (!device->scroll_valuator_count)
+    return;
+
+  for (int i = 0; i < device->scroll_valuator_count; ++i)
+    {
+      valuator = &device->valuators[i];
+      valuator->invalid_p = true;
+    }
+
+  return;
+}
+
+#endif
+
 void
 x_cr_destroy_frame_context (struct frame *f)
 {
@@ -4768,7 +4996,16 @@ x_any_window_to_frame (struct x_display_info *dpyinfo, int wdesc)
 x_menubar_window_to_frame (struct x_display_info *dpyinfo,
 			   const XEvent *event)
 {
-  Window wdesc = event->xany.window;
+  Window wdesc;
+#ifdef HAVE_XINPUT2
+  if (event->type == GenericEvent
+      && dpyinfo->supports_xi2
+      && (event->xcookie.evtype == XI_ButtonPress
+	  || event->xcookie.evtype == XI_ButtonRelease))
+    wdesc = ((XIDeviceEvent *) event->xcookie.data)->event;
+  else
+#endif
+    wdesc = event->xany.window;
   Lisp_Object tail, frame;
   struct frame *f;
   struct x_output *x;
@@ -4871,6 +5108,29 @@ x_detect_focus_change (struct x_display_info *dpyinfo, struct frame *frame,
       }
       break;
 
+#ifdef HAVE_XINPUT2
+    case GenericEvent:
+      {
+	XIEvent *xi_event = (XIEvent *) event;
+
+        struct frame *focus_frame = dpyinfo->x_focus_event_frame;
+        int focus_state
+          = focus_frame ? focus_frame->output_data.x->focus_state : 0;
+
+	if (!((xi_event->evtype == XI_Enter
+	       || xi_event->evtype == XI_Leave)
+	      && (focus_state & FOCUS_EXPLICIT)))
+	  x_focus_changed ((xi_event->evtype == XI_Enter
+			    || xi_event->evtype == XI_FocusIn
+			    ? FocusIn : FocusOut),
+			   (xi_event->evtype == XI_Enter
+			    || xi_event->evtype == XI_Leave
+			    ? FOCUS_IMPLICIT : FOCUS_EXPLICIT),
+			   dpyinfo, frame, bufp);
+	break;
+      }
+#endif
+
     case FocusIn:
     case FocusOut:
       /* Ignore transient focus events from hotkeys, window manager
@@ -7975,7 +8235,11 @@ mouse_or_wdesc_frame (struct x_display_info *dpyinfo, int wdesc)
 
 static int
 handle_one_xevent (struct x_display_info *dpyinfo,
+#ifndef HAVE_XINPUT2
 		   const XEvent *event,
+#else
+		   XEvent *event,
+#endif
 		   int *finish, struct input_event *hold_quit)
 {
   union buffered_input_event inev;
@@ -8001,7 +8265,14 @@ handle_one_xevent (struct x_display_info *dpyinfo,
   inev.ie.kind = NO_EVENT;
   inev.ie.arg = Qnil;
 
-  any = x_any_window_to_frame (dpyinfo, event->xany.window);
+#ifdef HAVE_XINPUT2
+  if (event->type != GenericEvent)
+#endif
+    any = x_any_window_to_frame (dpyinfo, event->xany.window);
+#ifdef HAVE_XINPUT2
+  else
+    any = NULL;
+#endif
 
   if (any && any->wait_event_type == event->type)
     any->wait_event_type = 0; /* Indicates we got it.  */
@@ -8480,6 +8751,10 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       goto OTHER;
 
     case MapNotify:
+#if defined HAVE_XINPUT2 && defined HAVE_GTK3
+      if (xg_is_menu_window (dpyinfo->display, event->xmap.window))
+	popup_activated_flag = 1;
+#endif
       /* We use x_top_window_to_frame because map events can
          come for sub-windows and they don't mean that the
          frame is visible.  */
@@ -9518,6 +9793,785 @@ handle_one_xevent (struct x_display_info *dpyinfo,
     case DestroyNotify:
       xft_settings_event (dpyinfo, event);
       break;
+#ifdef HAVE_XINPUT2
+    case GenericEvent:
+      {
+	if (!dpyinfo->supports_xi2)
+	  goto OTHER;
+	if (event->xgeneric.extension != dpyinfo->xi2_opcode)
+	  /* Not an XI2 event. */
+	  goto OTHER;
+	bool must_free_data = false;
+	XIEvent *xi_event = (XIEvent *) event->xcookie.data;
+	/* Sometimes the event is already claimed by GTK, which
+	   will free its data in due course. */
+	if (!xi_event && XGetEventData (dpyinfo->display, &event->xcookie))
+	  {
+	    must_free_data = true;
+	    xi_event = (XIEvent *) event->xcookie.data;
+	  }
+
+	XIDeviceEvent *xev = (XIDeviceEvent *) xi_event;
+	XILeaveEvent *leave = (XILeaveEvent *) xi_event;
+	XIEnterEvent *enter = (XIEnterEvent *) xi_event;
+	XIFocusInEvent *focusin = (XIFocusInEvent *) xi_event;
+	XIFocusOutEvent *focusout = (XIFocusOutEvent *) xi_event;
+	XIValuatorState *states;
+	double *values;
+	bool found_valuator = false;
+
+	/* A fake XMotionEvent for x_note_mouse_movement. */
+	XMotionEvent ev;
+	/* A fake XButtonEvent for x_construct_mouse_click. */
+	XButtonEvent bv;
+
+	if (!xi_event)
+	  {
+	    eassert (!must_free_data);
+	    goto OTHER;
+	  }
+
+	switch (event->xcookie.evtype)
+	  {
+	  case XI_FocusIn:
+	    any = x_any_window_to_frame (dpyinfo, focusin->event);
+#ifndef USE_GTK
+	    /* Some WMs (e.g. Mutter in Gnome Shell), don't unmap
+	       minimized/iconified windows; thus, for those WMs we won't get
+	       a MapNotify when unminimizing/deconifying.  Check here if we
+	       are deiconizing a window (Bug42655).
+
+	       But don't do that on GTK since it may cause a plain invisible
+	       frame get reported as iconified, compare
+	       https://lists.gnu.org/archive/html/emacs-devel/2017-02/msg00133.html.
+	       That is fixed above but bites us here again.  */
+	    f = any;
+	    if (f && FRAME_ICONIFIED_P (f))
+	      {
+		SET_FRAME_VISIBLE (f, 1);
+		SET_FRAME_ICONIFIED (f, false);
+		f->output_data.x->has_been_visible = true;
+		inev.ie.kind = DEICONIFY_EVENT;
+		XSETFRAME (inev.ie.frame_or_window, f);
+	      }
+#endif /* USE_GTK */
+	    x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+	    goto XI_OTHER;
+	  case XI_FocusOut:
+	    any = x_any_window_to_frame (dpyinfo, focusout->event);
+	    x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+	    goto XI_OTHER;
+	  case XI_Enter:
+	    any = x_any_window_to_frame (dpyinfo, enter->event);
+	    ev.x = lrint (enter->event_x);
+	    ev.y = lrint (enter->event_y);
+	    ev.window = leave->event;
+
+	    x_display_set_last_user_time (dpyinfo, xi_event->time);
+	    x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+	    xi_reset_scroll_valuators_for_device_id (dpyinfo, enter->deviceid);
+	    f = any;
+
+	    if (f && x_mouse_click_focus_ignore_position)
+	      ignore_next_mouse_click_timeout = xi_event->time + 200;
+
+	    /* EnterNotify counts as mouse movement,
+	       so update things that depend on mouse position.  */
+	    if (f && !f->output_data.x->hourglass_p)
+	      x_note_mouse_movement (f, &ev);
+#ifdef USE_GTK
+	    /* We may get an EnterNotify on the buttons in the toolbar.  In that
+	       case we moved out of any highlighted area and need to note this.  */
+	    if (!f && dpyinfo->last_mouse_glyph_frame)
+	      x_note_mouse_movement (dpyinfo->last_mouse_glyph_frame, &ev);
+#endif
+	    goto XI_OTHER;
+	  case XI_Leave:
+	    ev.x = lrint (leave->event_x);
+	    ev.y = lrint (leave->event_y);
+	    ev.window = leave->event;
+	    any = x_any_window_to_frame (dpyinfo, leave->event);
+
+	    x_display_set_last_user_time (dpyinfo, xi_event->time);
+	    x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+	    xi_reset_scroll_valuators_for_device_id (dpyinfo, leave->deviceid);
+
+	    f = x_top_window_to_frame (dpyinfo, leave->event);
+	    if (f)
+	      {
+		if (f == hlinfo->mouse_face_mouse_frame)
+		  {
+		    /* If we move outside the frame, then we're
+		       certainly no longer on any text in the frame.  */
+		    clear_mouse_face (hlinfo);
+		    hlinfo->mouse_face_mouse_frame = 0;
+		  }
+
+		/* Generate a nil HELP_EVENT to cancel a help-echo.
+		   Do it only if there's something to cancel.
+		   Otherwise, the startup message is cleared when
+		   the mouse leaves the frame.  */
+		if (any_help_event_p)
+		  do_help = -1;
+	      }
+#ifdef USE_GTK
+	    /* See comment in EnterNotify above */
+	    else if (dpyinfo->last_mouse_glyph_frame)
+	      x_note_mouse_movement (dpyinfo->last_mouse_glyph_frame, &ev);
+#endif
+	    goto XI_OTHER;
+	  case XI_Motion:
+	    /* First test if there is some kind of scroll event
+	       here! */
+	    states = &xev->valuators;
+	    values = states->values;
+
+	    x_display_set_last_user_time (dpyinfo, xi_event->time);
+
+	    for (int i = 0; i < states->mask_len * 8; i++)
+	      {
+		if (XIMaskIsSet (states->mask, i))
+		  {
+		    block_input ();
+
+		    struct xi_scroll_valuator_t *val;
+		    double delta;
+
+		    delta = x_get_scroll_valuator_delta (dpyinfo, xev->deviceid,
+							 i, *values, &val);
+
+		    if (delta != DBL_MAX)
+		      {
+			f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+			found_valuator = true;
+
+			if (signbit (delta) != signbit (val->emacs_value))
+			  val->emacs_value = 0;
+
+			val->emacs_value += delta;
+
+			if (!f)
+			  {
+			    f = x_any_window_to_frame (dpyinfo, xev->event);
+
+			    if (!f)
+			      {
+				unblock_input ();
+				goto XI_OTHER;
+			      }
+			  }
+
+			bool s = signbit (val->emacs_value);
+			inev.ie.kind = (val->horizontal
+					? HORIZ_WHEEL_EVENT
+					: WHEEL_EVENT);
+			inev.ie.timestamp = xev->time;
+
+			XSETINT (inev.ie.x, lrint (xev->event_x));
+			XSETINT (inev.ie.y, lrint (xev->event_y));
+			XSETFRAME (inev.ie.frame_or_window, f);
+
+			inev.ie.modifiers = !s ? up_modifier : down_modifier;
+			inev.ie.modifiers
+			  |= x_x_to_emacs_modifiers (dpyinfo,
+						     xev->mods.effective);
+			inev.ie.arg = Qnil;
+
+			kbd_buffer_store_event_hold (&inev.ie, hold_quit);
+
+			val->emacs_value = 0;
+		      }
+		    unblock_input ();
+		    values++;
+		  }
+
+		inev.ie.kind = NO_EVENT;
+	      }
+
+	    if (found_valuator)
+	      goto XI_OTHER;
+
+	    ev.x = lrint (xev->event_x);
+	    ev.y = lrint (xev->event_y);
+	    ev.window = xev->event;
+
+	    previous_help_echo_string = help_echo_string;
+	    help_echo_string = Qnil;
+
+	    if (hlinfo->mouse_face_hidden)
+	      {
+		hlinfo->mouse_face_hidden = false;
+		clear_mouse_face (hlinfo);
+	      }
+
+	    f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+
+#ifdef USE_GTK
+	    if (f && xg_event_is_for_scrollbar (f, event))
+	      f = 0;
+#endif
+	    if (f)
+	      {
+		/* Maybe generate a SELECT_WINDOW_EVENT for
+		   `mouse-autoselect-window' but don't let popup menus
+		   interfere with this (Bug#1261).  */
+		if (!NILP (Vmouse_autoselect_window)
+		    && !popup_activated ()
+		    /* Don't switch if we're currently in the minibuffer.
+		       This tries to work around problems where the
+		       minibuffer gets unselected unexpectedly, and where
+		       you then have to move your mouse all the way down to
+		       the minibuffer to select it.  */
+		    && !MINI_WINDOW_P (XWINDOW (selected_window))
+		    /* With `focus-follows-mouse' non-nil create an event
+		       also when the target window is on another frame.  */
+		    && (f == XFRAME (selected_frame)
+			|| !NILP (focus_follows_mouse)))
+		  {
+		    static Lisp_Object last_mouse_window;
+		    Lisp_Object window = window_from_coordinates (f, ev.x, ev.y, 0, false, false);
+
+		    /* A window will be autoselected only when it is not
+		       selected now and the last mouse movement event was
+		       not in it.  The remainder of the code is a bit vague
+		       wrt what a "window" is.  For immediate autoselection,
+		       the window is usually the entire window but for GTK
+		       where the scroll bars don't count.  For delayed
+		       autoselection the window is usually the window's text
+		       area including the margins.  */
+		    if (WINDOWP (window)
+			&& !EQ (window, last_mouse_window)
+			&& !EQ (window, selected_window))
+		      {
+			inev.ie.kind = SELECT_WINDOW_EVENT;
+			inev.ie.frame_or_window = window;
+		      }
+
+		    /* Remember the last window where we saw the mouse.  */
+		    last_mouse_window = window;
+		  }
+
+		if (!x_note_mouse_movement (f, &ev))
+		  help_echo_string = previous_help_echo_string;
+	      }
+	    else
+	      {
+#ifndef USE_TOOLKIT_SCROLL_BARS
+		struct scroll_bar *bar
+		  = x_window_to_scroll_bar (xi_event->display, xev->event, 2);
+
+		if (bar)
+		  x_scroll_bar_note_movement (bar, &ev);
+#endif /* USE_TOOLKIT_SCROLL_BARS */
+
+		/* If we move outside the frame, then we're
+		   certainly no longer on any text in the frame.  */
+		clear_mouse_face (hlinfo);
+	      }
+
+	    /* If the contents of the global variable help_echo_string
+	       has changed, generate a HELP_EVENT.  */
+	    if (!NILP (help_echo_string)
+		|| !NILP (previous_help_echo_string))
+	      do_help = 1;
+	    goto XI_OTHER;
+	  case XI_ButtonRelease:
+	  case XI_ButtonPress:
+	    {
+	      /* If we decide we want to generate an event to be seen
+		 by the rest of Emacs, we put it here.  */
+	      Lisp_Object tab_bar_arg = Qnil;
+	      bool tab_bar_p = false;
+	      bool tool_bar_p = false;
+	      struct xi_device_t *device;
+
+	      /* Ignore emulated scroll events when XI2 native
+		 scroll events are present.  */
+	      if (dpyinfo->xi2_version >= 1 && xev->detail >= 4
+		  && xev->detail <= 8)
+		goto XI_OTHER;
+
+	      device = xi_device_from_id (dpyinfo, xev->deviceid);
+
+	      bv.button = xev->detail;
+	      bv.type = xev->evtype == XI_ButtonPress ? ButtonPress : ButtonRelease;
+	      bv.x = lrint (xev->event_x);
+	      bv.y = lrint (xev->event_y);
+	      bv.window = xev->event;
+	      bv.state = xev->mods.base
+		| xev->mods.effective
+		| xev->mods.latched
+		| xev->mods.locked;
+
+	      memset (&compose_status, 0, sizeof (compose_status));
+	      dpyinfo->last_mouse_glyph_frame = NULL;
+	      x_display_set_last_user_time (dpyinfo, xev->time);
+
+	      f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+
+	      if (f && xev->evtype == XI_ButtonPress
+		  && !popup_activated ()
+		  && !x_window_to_scroll_bar (xev->display, xev->event, 2)
+		  && !FRAME_NO_ACCEPT_FOCUS (f))
+		{
+		  /* When clicking into a child frame or when clicking
+		     into a parent frame with the child frame selected and
+		     `no-accept-focus' is not set, select the clicked
+		     frame.  */
+		  struct frame *hf = dpyinfo->highlight_frame;
+
+		  if (FRAME_PARENT_FRAME (f) || (hf && frame_ancestor_p (f, hf)))
+		    {
+		      block_input ();
+		      XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f),
+				      RevertToParent, CurrentTime);
+		      if (FRAME_PARENT_FRAME (f))
+			XRaiseWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f));
+		      unblock_input ();
+		    }
+		}
+
+#ifdef USE_GTK
+	      if (f && xg_event_is_for_scrollbar (f, event))
+		f = 0;
+#endif
+
+	      if (f)
+		{
+		  /* Is this in the tab-bar?  */
+		  if (WINDOWP (f->tab_bar_window)
+		      && WINDOW_TOTAL_LINES (XWINDOW (f->tab_bar_window)))
+		    {
+		      Lisp_Object window;
+		      int x = bv.x;
+		      int y = bv.y;
+
+		      window = window_from_coordinates (f, x, y, 0, true, true);
+		      tab_bar_p = EQ (window, f->tab_bar_window);
+
+		      if (tab_bar_p)
+			tab_bar_arg = handle_tab_bar_click
+			  (f, x, y, xev->evtype == XI_ButtonPress,
+			   x_x_to_emacs_modifiers (dpyinfo, bv.state));
+		    }
+
+#if ! defined (USE_GTK)
+		  /* Is this in the tool-bar?  */
+		  if (WINDOWP (f->tool_bar_window)
+		      && WINDOW_TOTAL_LINES (XWINDOW (f->tool_bar_window)))
+		    {
+		      Lisp_Object window;
+		      int x = bv.x;
+		      int y = bv.y;
+
+		      window = window_from_coordinates (f, x, y, 0, true, true);
+		      tool_bar_p = EQ (window, f->tool_bar_window);
+
+		      if (tool_bar_p && xev->detail < 4)
+			handle_tool_bar_click
+			  (f, x, y, xev->evtype == XI_ButtonPress,
+			   x_x_to_emacs_modifiers (dpyinfo, bv.state));
+		    }
+#endif /* !USE_GTK */
+
+		  if (!(tab_bar_p && NILP (tab_bar_arg)) && !tool_bar_p)
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+		    if (! popup_activated ())
+#endif
+		      {
+			if (ignore_next_mouse_click_timeout)
+			  {
+			    if (xev->evtype == XI_ButtonPress
+				&& xev->time > ignore_next_mouse_click_timeout)
+			      {
+				ignore_next_mouse_click_timeout = 0;
+				x_construct_mouse_click (&inev.ie, &bv, f);
+			      }
+			    if (xev->evtype == XI_ButtonRelease)
+			      ignore_next_mouse_click_timeout = 0;
+			  }
+			else
+			  x_construct_mouse_click (&inev.ie, &bv, f);
+
+			if (!NILP (tab_bar_arg))
+			  inev.ie.arg = tab_bar_arg;
+		      }
+		  if (FRAME_X_EMBEDDED_P (f))
+		    xembed_send_message (f, xev->time,
+					 XEMBED_REQUEST_FOCUS, 0, 0, 0);
+		}
+
+	      if (xev->evtype == XI_ButtonPress)
+		{
+		  dpyinfo->grabbed |= (1 << xev->detail);
+		  device->grab |= (1 << xev->detail);
+		  dpyinfo->last_mouse_frame = f;
+		  if (f && !tab_bar_p)
+		    f->last_tab_bar_item = -1;
+#if ! defined (USE_GTK)
+		  if (f && !tool_bar_p)
+		    f->last_tool_bar_item = -1;
+#endif /* not USE_GTK */
+
+		}
+	      else
+		{
+		  dpyinfo->grabbed &= ~(1 << xev->detail);
+		  device->grab &= ~(1 << xev->detail);
+		}
+
+	      xi_grab_or_ungrab_device (device, dpyinfo, xev->event);
+
+	      if (f)
+		f->mouse_moved = false;
+
+#if defined (USE_GTK)
+	      /* No Xt toolkit currently available has support for XI2.
+	         So the code here assumes use of GTK.  */
+	      f = x_menubar_window_to_frame (dpyinfo, event);
+	      if (f /* Gtk+ menus only react to the first three buttons. */
+		  && xev->detail < 3)
+		{
+		  /* What is done with Core Input ButtonPressed is not
+		     possible here, because GenericEvents cannot be saved.  */
+		  bool was_waiting_for_input = waiting_for_input;
+		  /* This hack was adopted from the NS port.  Whether
+		     or not it is actually safe is a different story
+		     altogether.  */
+		  if (waiting_for_input)
+		    waiting_for_input = 0;
+		  set_frame_menubar (f, true);
+		  waiting_for_input = was_waiting_for_input;
+		}
+#endif
+	      goto XI_OTHER;
+	    }
+	  case XI_KeyPress:
+	    {
+	      int state = xev->mods.base
+		| xev->mods.effective
+		| xev->mods.latched
+		| xev->mods.locked;
+	      Lisp_Object c;
+#ifdef HAVE_XKB
+	      unsigned int mods_rtrn;
+#endif
+	      int keycode = xev->detail;
+	      KeySym keysym;
+	      char copy_buffer[81];
+	      char *copy_bufptr = copy_buffer;
+	      unsigned char *copy_ubufptr;
+#ifdef HAVE_XKB
+	      int copy_bufsiz = sizeof (copy_buffer);
+#endif
+	      ptrdiff_t i;
+	      int nchars, len;
+
+#ifdef HAVE_XKB
+	      if (dpyinfo->xkb_desc)
+		{
+		  if (!XkbTranslateKeyCode (dpyinfo->xkb_desc, keycode,
+					    state, &mods_rtrn, &keysym))
+		    goto XI_OTHER;
+		}
+	      else
+		{
+#endif
+		  int keysyms_per_keycode_return;
+		  KeySym *ksms = XGetKeyboardMapping (dpyinfo->display, keycode, 1,
+						      &keysyms_per_keycode_return);
+		  if (!(keysym = ksms[0]))
+		    {
+		      XFree (ksms);
+		      goto XI_OTHER;
+		    }
+		  XFree (ksms);
+#ifdef HAVE_XKB
+		}
+#endif
+
+	      if (keysym == NoSymbol)
+		goto XI_OTHER;
+
+	      x_display_set_last_user_time (dpyinfo, xev->time);
+	      ignore_next_mouse_click_timeout = 0;
+
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+	      /* Dispatch XI_KeyPress events when in menu.  */
+	      if (popup_activated ())
+		goto XI_OTHER;
+#endif
+
+	      f = x_any_window_to_frame (dpyinfo, xev->event);
+
+	      /* If mouse-highlight is an integer, input clears out
+		 mouse highlighting.  */
+	      if (!hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight)
+		  && (f == 0
+#if ! defined (USE_GTK)
+		      || !EQ (f->tool_bar_window, hlinfo->mouse_face_window)
+#endif
+		      || !EQ (f->tab_bar_window, hlinfo->mouse_face_window))
+		  )
+		{
+		  clear_mouse_face (hlinfo);
+		  hlinfo->mouse_face_hidden = true;
+		}
+
+	      if (f != 0)
+		{
+#ifdef USE_GTK
+		  /* Don't pass keys to GTK.  A Tab will shift focus to the
+		     tool bar in GTK 2.4.  Keys will still go to menus and
+		     dialogs because in that case popup_activated is nonzero
+		     (see above).  */
+		  *finish = X_EVENT_DROP;
+#endif
+		  /* If not using XIM/XIC, and a compose sequence is in progress,
+		     we break here.  Otherwise, chars_matched is always 0.  */
+		  if (compose_status.chars_matched > 0 && nbytes == 0)
+		    goto XI_OTHER;
+
+		  memset (&compose_status, 0, sizeof (compose_status));
+
+		  XSETFRAME (inev.ie.frame_or_window, f);
+		  inev.ie.modifiers
+		    = x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), state);
+		  inev.ie.timestamp = xev->time;
+
+		  /* First deal with keysyms which have defined
+		     translations to characters.  */
+		  if (keysym >= 32 && keysym < 128)
+		    /* Avoid explicitly decoding each ASCII character.  */
+		    {
+		      inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+		      inev.ie.code = keysym;
+
+		      goto xi_done_keysym;
+		    }
+
+		  /* Keysyms directly mapped to Unicode characters.  */
+		  if (keysym >= 0x01000000 && keysym <= 0x0110FFFF)
+		    {
+		      if (keysym < 0x01000080)
+			inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+		      else
+			inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
+		      inev.ie.code = keysym & 0xFFFFFF;
+		      goto xi_done_keysym;
+		    }
+
+		  /* Now non-ASCII.  */
+		  if (HASH_TABLE_P (Vx_keysym_table)
+		      && (c = Fgethash (make_fixnum (keysym),
+					Vx_keysym_table,
+					Qnil),
+			  FIXNATP (c)))
+		    {
+		      inev.ie.kind = (SINGLE_BYTE_CHAR_P (XFIXNAT (c))
+				      ? ASCII_KEYSTROKE_EVENT
+				      : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+		      inev.ie.code = XFIXNAT (c);
+		      goto xi_done_keysym;
+		    }
+
+		  /* Random non-modifier sorts of keysyms.  */
+		  if (((keysym >= XK_BackSpace && keysym <= XK_Escape)
+		       || keysym == XK_Delete
+#ifdef XK_ISO_Left_Tab
+		       || (keysym >= XK_ISO_Left_Tab
+			   && keysym <= XK_ISO_Enter)
+#endif
+		       || IsCursorKey (keysym) /* 0xff50 <= x < 0xff60 */
+		       || IsMiscFunctionKey (keysym) /* 0xff60 <= x < VARIES */
+#ifdef HPUX
+		       /* This recognizes the "extended function
+			  keys".  It seems there's no cleaner way.
+			  Test IsModifierKey to avoid handling
+			  mode_switch incorrectly.  */
+		       || (XK_Select <= keysym && keysym < XK_KP_Space)
+#endif
+#ifdef XK_dead_circumflex
+		       || keysym == XK_dead_circumflex
+#endif
+#ifdef XK_dead_grave
+		       || keysym == XK_dead_grave
+#endif
+#ifdef XK_dead_tilde
+		       || keysym == XK_dead_tilde
+#endif
+#ifdef XK_dead_diaeresis
+		       || keysym == XK_dead_diaeresis
+#endif
+#ifdef XK_dead_macron
+		       || keysym == XK_dead_macron
+#endif
+#ifdef XK_dead_degree
+		       || keysym == XK_dead_degree
+#endif
+#ifdef XK_dead_acute
+		       || keysym == XK_dead_acute
+#endif
+#ifdef XK_dead_cedilla
+		       || keysym == XK_dead_cedilla
+#endif
+#ifdef XK_dead_breve
+		       || keysym == XK_dead_breve
+#endif
+#ifdef XK_dead_ogonek
+		       || keysym == XK_dead_ogonek
+#endif
+#ifdef XK_dead_caron
+		       || keysym == XK_dead_caron
+#endif
+#ifdef XK_dead_doubleacute
+		       || keysym == XK_dead_doubleacute
+#endif
+#ifdef XK_dead_abovedot
+		       || keysym == XK_dead_abovedot
+#endif
+		       || IsKeypadKey (keysym) /* 0xff80 <= x < 0xffbe */
+		       || IsFunctionKey (keysym) /* 0xffbe <= x < 0xffe1 */
+		       /* Any "vendor-specific" key is ok.  */
+		       || (keysym & (1 << 28))
+		       || (keysym != NoSymbol && nbytes == 0))
+		      && ! (IsModifierKey (keysym)
+			    /* The symbols from XK_ISO_Lock
+			       to XK_ISO_Last_Group_Lock
+			       don't have real modifiers but
+			       should be treated similarly to
+			       Mode_switch by Emacs. */
+#if defined XK_ISO_Lock && defined XK_ISO_Last_Group_Lock
+			    || (XK_ISO_Lock <= keysym
+				&& keysym <= XK_ISO_Last_Group_Lock)
+#endif
+			    ))
+		    {
+		      STORE_KEYSYM_FOR_DEBUG (keysym);
+		      /* make_lispy_event will convert this to a symbolic
+			 key.  */
+		      inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+		      inev.ie.code = keysym;
+		      goto xi_done_keysym;
+		    }
+
+#ifdef HAVE_XKB
+		  int overflow = 0;
+		  KeySym sym = keysym;
+
+		  if (dpyinfo->xkb_desc)
+		    {
+		      if (!(nbytes = XkbTranslateKeySym (dpyinfo->display, &sym,
+							 state & ~mods_rtrn, copy_bufptr,
+							 copy_bufsiz, &overflow)))
+			goto XI_OTHER;
+		    }
+		  else
+#else
+		    {
+		      block_input ();
+		      char *str = XKeysymToString (keysym);
+		      if (!str)
+			{
+			  unblock_input ();
+			  goto XI_OTHER;
+			}
+		      nbytes = strlen (str) + 1;
+		      copy_bufptr = alloca (nbytes);
+		      strcpy (copy_bufptr, str);
+		      unblock_input ();
+		    }
+#endif
+#ifdef HAVE_XKB
+		  if (overflow)
+		    {
+		      overflow = 0;
+		      copy_bufptr = alloca (copy_bufsiz + overflow);
+		      keysym = sym;
+		      if (!(nbytes = XkbTranslateKeySym (dpyinfo->display, &sym,
+							 state & ~mods_rtrn, copy_bufptr,
+							 copy_bufsiz + overflow, &overflow)))
+			goto XI_OTHER;
+
+		      if (overflow)
+			goto XI_OTHER;
+		    }
+#endif
+
+		  for (i = 0, nchars = 0; i < nbytes; i++)
+		    {
+		      if (ASCII_CHAR_P (copy_bufptr[i]))
+			nchars++;
+		      STORE_KEYSYM_FOR_DEBUG (copy_bufptr[i]);
+		    }
+
+		  if (nchars < nbytes)
+		    {
+		      /* Decode the input data.  */
+
+		      setup_coding_system (Vlocale_coding_system, &coding);
+		      coding.src_multibyte = false;
+		      coding.dst_multibyte = true;
+		      /* The input is converted to events, thus we can't
+			 handle composition.  Anyway, there's no XIM that
+			 gives us composition information.  */
+		      coding.common_flags &= ~CODING_ANNOTATION_MASK;
+
+		      SAFE_NALLOCA (coding.destination, MAX_MULTIBYTE_LENGTH,
+				    nbytes);
+		      coding.dst_bytes = MAX_MULTIBYTE_LENGTH * nbytes;
+		      coding.mode |= CODING_MODE_LAST_BLOCK;
+		      decode_coding_c_string (&coding, (unsigned char *) copy_bufptr,
+					      nbytes, Qnil);
+		      nbytes = coding.produced;
+		      nchars = coding.produced_char;
+		      copy_bufptr = (char *) coding.destination;
+		    }
+
+		  copy_ubufptr = (unsigned char *) copy_bufptr;
+
+		  /* Convert the input data to a sequence of
+		     character events.  */
+		  for (i = 0; i < nbytes; i += len)
+		    {
+		      int ch;
+		      if (nchars == nbytes)
+			ch = copy_ubufptr[i], len = 1;
+		      else
+			ch = string_char_and_length (copy_ubufptr + i, &len);
+		      inev.ie.kind = (SINGLE_BYTE_CHAR_P (ch)
+				      ? ASCII_KEYSTROKE_EVENT
+				      : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+		      inev.ie.code = ch;
+		      kbd_buffer_store_buffered_event (&inev, hold_quit);
+		    }
+
+		  inev.ie.kind = NO_EVENT;
+		  goto xi_done_keysym;
+		}
+	      goto XI_OTHER;
+	    }
+	  case XI_KeyRelease:
+	    x_display_set_last_user_time (dpyinfo, xev->time);
+	    goto XI_OTHER;
+	  case XI_PropertyEvent:
+	  case XI_HierarchyChanged:
+	  case XI_DeviceChanged:
+	    x_init_master_valuators (dpyinfo);
+	    goto XI_OTHER;
+	  default:
+	    goto XI_OTHER;
+	  }
+      xi_done_keysym:
+	if (must_free_data)
+	  XFreeEventData (dpyinfo->display, &event->xcookie);
+	goto done_keysym;
+      XI_OTHER:
+	if (must_free_data)
+	  XFreeEventData (dpyinfo->display, &event->xcookie);
+	goto OTHER;
+      }
+#endif
 
     default:
     OTHER:
@@ -13199,6 +14253,40 @@ #define NUM_ARGV 10
     dpyinfo->supports_xdbe = true;
 #endif
 
+#ifdef HAVE_XINPUT2
+  dpyinfo->supports_xi2 = false;
+  int rc;
+  int major = 2;
+#ifdef XI_BarrierHit /* XInput 2.3 */
+  int minor = 3;
+#elif defined XI_TouchBegin /* XInput 2.2 */
+  int minor = 2;
+#elif defined XIScrollClass /* XInput 1.1 */
+  int minor = 1;
+#else /* Some old version of XI2 we're not interested in. */
+  int minor = 0;
+#endif
+  int fer, fee;
+
+  if (XQueryExtension (dpyinfo->display, "XInputExtension",
+		       &dpyinfo->xi2_opcode, &fer, &fee))
+    {
+      rc = XIQueryVersion (dpyinfo->display, &major, &minor);
+      if (rc == Success)
+	{
+	  dpyinfo->supports_xi2 = true;
+	  x_init_master_valuators (dpyinfo);
+	}
+    }
+  dpyinfo->xi2_version = minor;
+#endif
+
+#ifdef HAVE_XKB
+  dpyinfo->xkb_desc = XkbGetMap (dpyinfo->display,
+				 XkbAllComponentsMask,
+				 XkbUseCoreKbd);
+#endif
+
 #if defined USE_CAIRO || defined HAVE_XFT
   {
     /* If we are using Xft, the following precautions should be made:
@@ -13631,6 +14719,14 @@ x_delete_terminal (struct terminal *terminal)
       XrmDestroyDatabase (dpyinfo->rdb);
 #endif
 
+#ifdef HAVE_XKB
+      if (dpyinfo->xkb_desc)
+	XkbFreeKeyboard (dpyinfo->xkb_desc, XkbAllComponentsMask, True);
+#endif
+#ifdef HAVE_XINPUT2
+      if (dpyinfo->supports_xi2)
+	x_free_xi_devices (dpyinfo);
+#endif
 #ifdef USE_GTK
       xg_display_close (dpyinfo->display);
 #else
@@ -13790,9 +14886,12 @@ x_initialize (void)
 void
 init_xterm (void)
 {
-  /* Emacs can handle only core input events, so make sure
-     Gtk doesn't use Xinput or Xinput2 extensions.  */
+#ifndef HAVE_XINPUT2
+  /* Emacs can handle only core input events when built without XI2
+     support, so make sure Gtk doesn't use Xinput or Xinput2
+     extensions.  */
   xputenv ("GDK_CORE_DEVICE_EVENTS=1");
+#endif
 }
 #endif
 
diff --git a/src/xterm.h b/src/xterm.h
index 9d9534dd62..7abe168bc6 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -88,6 +88,10 @@ #define XSync(d, b) do { gdk_window_process_all_updates (); \
 #include <X11/Xlib-xcb.h>
 #endif
 
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+#endif
+
 #include "dispextern.h"
 #include "termhooks.h"
 
@@ -163,6 +167,28 @@ #define MAX_CLIP_RECTS 2
   char *name;
 };
 
+#ifdef HAVE_XINPUT2
+struct xi_scroll_valuator_t
+{
+  bool invalid_p;
+  double current_value;
+  double emacs_value;
+  double increment;
+
+  int number;
+  int horizontal;
+};
+
+struct xi_device_t
+{
+  int device_id;
+  int scroll_valuator_count;
+  int grab;
+
+  struct xi_scroll_valuator_t *valuators;
+};
+#endif
+
 Status x_parse_color (struct frame *f, const char *color_name,
 		      XColor *color);
 
@@ -474,6 +500,19 @@ #define MAX_CLIP_RECTS 2
 #ifdef HAVE_XDBE
   bool supports_xdbe;
 #endif
+
+#ifdef HAVE_XINPUT2
+  bool supports_xi2;
+  int xi2_version;
+  int xi2_opcode;
+
+  int num_devices;
+  struct xi_device_t *devices;
+#endif
+
+#ifdef HAVE_XKB
+  XkbDescPtr xkb_desc;
+#endif
 };
 
 #ifdef HAVE_X_I18N
@@ -481,6 +520,11 @@ #define MAX_CLIP_RECTS 2
 extern bool use_xim;
 #endif
 
+#ifdef HAVE_XINPUT2
+/* Defined in xmenu.c. */
+extern int popup_activated_flag;
+#endif
+
 /* This is a chain of structures for all the X displays currently in use.  */
 extern struct x_display_info *x_display_list;
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-19 13:24                   ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-19 13:55                     ` Eli Zaretskii
  2021-11-20 10:28                       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 1 reply; 18+ messages in thread
From: Eli Zaretskii @ 2021-11-19 13:55 UTC (permalink / raw)
  To: Po Lu; +Cc: rpluim, 51891

> From: Po Lu <luangruo@yahoo.com>
> Cc: rpluim@gmail.com,  51891@debbugs.gnu.org
> Date: Fri, 19 Nov 2021 21:24:49 +0800
> 
> >   Non-nil means send a wheel event only for scrolling at least one screen line.
> 
> Thanks.  Would it be OK for me to install this version of the change
> that has the changes you proposed made?

Sure, why not?

Thanks.





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-19 13:23                       ` Eli Zaretskii
@ 2021-11-20  0:30                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  2021-11-20  7:06                           ` Eli Zaretskii
  0 siblings, 1 reply; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-20  0:30 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: rpluim, 51891

Eli Zaretskii <eliz@gnu.org> writes:

> Some text which makes it clear how these additional POSITION
> parameters could be used by Lisp programs.
> I think "pixel-resolution wheel events".

WDYT about the following passage?

  @cindex pixel-resolution wheel events
  You can use @var{x} and @var{y} to determine how much the mouse wheel
  has actually moved.  Scrolling the screen by these pixel deltas allows
  to present movement that appears to follow the user's grip on the
  mouse wheel.

Thanks.





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-20  0:30                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
@ 2021-11-20  7:06                           ` Eli Zaretskii
  0 siblings, 0 replies; 18+ messages in thread
From: Eli Zaretskii @ 2021-11-20  7:06 UTC (permalink / raw)
  To: Po Lu; +Cc: rpluim, 51891

> From: Po Lu <luangruo@yahoo.com>
> Cc: rpluim@gmail.com,  51891@debbugs.gnu.org
> Date: Sat, 20 Nov 2021 08:30:07 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > Some text which makes it clear how these additional POSITION
> > parameters could be used by Lisp programs.
> > I think "pixel-resolution wheel events".
> 
> WDYT about the following passage?
> 
>   @cindex pixel-resolution wheel events
>   You can use @var{x} and @var{y} to determine how much the mouse wheel
>   has actually moved.

Here I would say "... has actually moved at pixel resolution."

>                       Scrolling the screen by these pixel deltas allows
>   to present movement that appears to follow the user's grip on the
>   mouse wheel.

That's just an example of using this feature, so:

  For example, the pixelwise deltas could be used to scroll the
  display at pixel resolution, exactly according to the user's turning
  the mouse wheel.

Thanks.





^ permalink raw reply	[flat|nested] 18+ messages in thread

* bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X
  2021-11-19 13:55                     ` Eli Zaretskii
@ 2021-11-20 10:28                       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
  0 siblings, 0 replies; 18+ messages in thread
From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2021-11-20 10:28 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: rpluim, 51891-done

Eli Zaretskii <eliz@gnu.org> writes:

> Sure, why not?
>
> Thanks.

Thanks, installed with the improvement to documentation you suggested in
the other message.





^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2021-11-20 10:28 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <87zgq4uvh0.fsf.ref@yahoo.com>
2021-11-16 12:38 ` bug#51891: 29.0.50; [PATCH] Pixel delta support for wheel events on X Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-16 13:39   ` Robert Pluim
2021-11-17  0:34     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-17  2:38       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-17 13:33         ` Eli Zaretskii
2021-11-18  0:15           ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-18  7:33             ` Eli Zaretskii
2021-11-18  9:17               ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-18 10:27                 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-19 13:05                   ` Eli Zaretskii
2021-11-19 13:10                     ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-19 13:23                       ` Eli Zaretskii
2021-11-20  0:30                         ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-20  7:06                           ` Eli Zaretskii
2021-11-19 13:09                 ` Eli Zaretskii
2021-11-19 13:24                   ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors
2021-11-19 13:55                     ` Eli Zaretskii
2021-11-20 10:28                       ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).