unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* XInput 2 support (again)
       [not found] <87v90wmh6m.fsf.ref@yahoo.com>
@ 2021-11-13 11:24 ` Po Lu via Emacs development discussions.
  2021-11-13 15:04   ` Stefan Monnier
  0 siblings, 1 reply; 26+ messages in thread
From: Po Lu via Emacs development discussions. @ 2021-11-13 11:24 UTC (permalink / raw)
  To: emacs-devel

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

Some of the feedback I've been getting lately about the xwidget feature
is that scrolling in xwidgets is "choppy".  This seems to be because we
don't get pixel-wise scroll events from X, as we rely on the Core Input
API for input events.

I brushed up my XInput 2 patch from last month.  I would like to install
this as an optional feature if nobody has any immediate objections to
it, so I can begin work on adding support for better event handling in
xwidgets.  (This feature is entirely optional: if `--with-xinput2' is
not specified as an argument to configure, then the code will behave
exactly as it used to.)

Thanks.


[-- 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: 49805 bytes --]

From 9daafc497ea49b3f15a5a945769d973c4bd56f5a Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Sat, 16 Oct 2021 13:15:36 +0800
Subject: [PATCH] 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.
* 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)
(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.
---
 configure.ac    |   22 +
 src/Makefile.in |    7 +-
 src/gtkutil.c   |   72 ++-
 src/gtkutil.h   |    4 +
 src/xfns.c      |   62 +++
 src/xmenu.c     |    4 +
 src/xterm.c     | 1134 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/xterm.h     |   44 ++
 8 files changed, 1342 insertions(+), 7 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/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..561ee5988f 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>
 
@@ -3074,6 +3078,35 @@ 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)
+    {
+      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 HAVE_X_I18N
   FRAME_XIC (f) = NULL;
   if (use_xim)
@@ -3254,6 +3287,35 @@ x_window (struct frame *f)
     }
 #endif /* HAVE_X_I18N */
 
+#ifdef HAVE_XINPUT2
+  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
+    {
+      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
+
   validate_x_resource_name ();
 
   class_hints.res_name = SSDATA (Vx_resource_name);
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 172abe919d..7d08d0d62d 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,203 @@ 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->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);
+    }
+}
+
+#endif
+
 void
 x_cr_destroy_frame_context (struct frame *f)
 {
@@ -4768,7 +4975,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 +5087,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 +8214,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 +8244,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 +8730,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 +9772,833 @@ 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);
+	    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);
+
+	    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 =
+		      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 = DBL_MIN;
+
+			val->emacs_value += delta;
+
+			if (!f)
+			  {
+			    f = x_any_window_to_frame (dpyinfo, xev->event);
+
+			    if (!f)
+			      {
+				unblock_input ();
+				goto XI_OTHER;
+			      }
+			  }
+
+			if ((val->horizontal
+			     && fabs (val->emacs_value / val->increment) >= 1)
+			    || (!val->horizontal
+				&& fabs (val->emacs_value / val->increment) >= 1))
+			  {
+			    Lisp_Object tab_bar_arg = Qnil;
+			    bool tab_bar_p = false;
+			    bool tool_bar_p = false;
+			    bool s = signbit (val->emacs_value);
+
+			    bv.button = !val->horizontal ? (s ? 5 : 4) : (s ? 7 : 6);
+			    bv.type = ButtonPress;
+
+			    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;
+
+			    /* 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, true, x_x_to_emacs_modifiers (dpyinfo, bv.state));
+				    tab_bar_arg = handle_tab_bar_click
+				      (f, x, y, false, x_x_to_emacs_modifiers (dpyinfo, bv.state));
+				  }
+			      }
+
+			    if (!NILP (tab_bar_arg))
+			      inev.ie.arg = tab_bar_arg;
+
+			    if (!tool_bar_p && !(NILP (tab_bar_arg) && tab_bar_p))
+			      {
+				if (ignore_next_mouse_click_timeout)
+				  {
+				    if (xev->time > ignore_next_mouse_click_timeout)
+				      {
+					x_construct_mouse_click (&inev.ie, &bv, f);
+					if (!NILP (tab_bar_arg))
+					  inev.ie.arg = tab_bar_arg;
+					kbd_buffer_store_event (&inev.ie);
+					inev.ie.modifiers &= ~down_modifier;
+					inev.ie.modifiers |= up_modifier;
+					kbd_buffer_store_event (&inev.ie);
+				      }
+				    ignore_next_mouse_click_timeout = 0;
+				  }
+				else
+				  {
+				    x_construct_mouse_click (&inev.ie, &bv, f);
+				    kbd_buffer_store_event (&inev.ie);
+				    inev.ie.modifiers &= ~down_modifier;
+				    inev.ie.modifiers |= up_modifier;
+				    kbd_buffer_store_event (&inev.ie);
+				  }
+			      }
+
+			    val->emacs_value = DBL_MIN;
+			  }
+		      }
+		    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:
@@ -13192,6 +14273,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:
@@ -13624,6 +14739,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
@@ -13783,9 +14906,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] 26+ messages in thread

* Re: XInput 2 support (again)
  2021-11-13 11:24 ` XInput 2 support (again) Po Lu via Emacs development discussions.
@ 2021-11-13 15:04   ` Stefan Monnier
  2021-11-14  1:29     ` Po Lu via Emacs development discussions.
  0 siblings, 1 reply; 26+ messages in thread
From: Stefan Monnier @ 2021-11-13 15:04 UTC (permalink / raw)
  To: Po Lu via Emacs development discussions.; +Cc: Po Lu

> @@ -3074,6 +3078,35 @@ 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)
> +    {
> +      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 HAVE_X_I18N
>    FRAME_XIC (f) = NULL;
>    if (use_xim)
> @@ -3254,6 +3287,35 @@ x_window (struct frame *f)
>      }
>  #endif /* HAVE_X_I18N */
>  
> +#ifdef HAVE_XINPUT2
> +  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
> +    {
> +      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
> +

Any reason this isn't consolidated into a separate function to avoid the
code duplication?

> +	  struct xi_device_t *xi_device =
> +	    &dpyinfo->devices[actual_devices++];

The GNU coding standard encourages to cut lines just *before* infix
operators rather after.  E.g.:

	  struct xi_device_t *xi_device
	    = &dpyinfo->devices[actual_devices++];

> @@ -9518,6 +9772,833 @@ 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);
> +	    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);
> +
> +	    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 =
> +		      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 = DBL_MIN;
> +
> +			val->emacs_value += delta;
> +
> +			if (!f)
> +			  {
> +			    f = x_any_window_to_frame (dpyinfo, xev->event);
> +
> +			    if (!f)
> +			      {
> +				unblock_input ();
> +				goto XI_OTHER;
> +			      }
> +			  }
> +
> +			if ((val->horizontal
> +			     && fabs (val->emacs_value / val->increment) >= 1)
> +			    || (!val->horizontal
> +				&& fabs (val->emacs_value / val->increment) >= 1))
> +			  {
> +			    Lisp_Object tab_bar_arg = Qnil;
> +			    bool tab_bar_p = false;
> +			    bool tool_bar_p = false;
> +			    bool s = signbit (val->emacs_value);
> +
> +			    bv.button = !val->horizontal ? (s ? 5 : 4) : (s ? 7 : 6);
> +			    bv.type = ButtonPress;
> +
> +			    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;
> +
> +			    /* 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, true, x_x_to_emacs_modifiers (dpyinfo, bv.state));
> +				    tab_bar_arg = handle_tab_bar_click
> +				      (f, x, y, false, x_x_to_emacs_modifiers (dpyinfo, bv.state));
> +				  }
> +			      }
> +
> +			    if (!NILP (tab_bar_arg))
> +			      inev.ie.arg = tab_bar_arg;
> +
> +			    if (!tool_bar_p && !(NILP (tab_bar_arg) && tab_bar_p))
> +			      {
> +				if (ignore_next_mouse_click_timeout)
> +				  {
> +				    if (xev->time > ignore_next_mouse_click_timeout)
> +				      {
> +					x_construct_mouse_click (&inev.ie, &bv, f);
> +					if (!NILP (tab_bar_arg))
> +					  inev.ie.arg = tab_bar_arg;
> +					kbd_buffer_store_event (&inev.ie);
> +					inev.ie.modifiers &= ~down_modifier;
> +					inev.ie.modifiers |= up_modifier;
> +					kbd_buffer_store_event (&inev.ie);
> +				      }
> +				    ignore_next_mouse_click_timeout = 0;
> +				  }
> +				else
> +				  {
> +				    x_construct_mouse_click (&inev.ie, &bv, f);
> +				    kbd_buffer_store_event (&inev.ie);
> +				    inev.ie.modifiers &= ~down_modifier;
> +				    inev.ie.modifiers |= up_modifier;
> +				    kbd_buffer_store_event (&inev.ie);
> +				  }
> +			      }
> +
> +			    val->emacs_value = DBL_MIN;
> +			  }
> +		      }
> +		    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

That's a damn large chunk of code to add into a function.

A lot of it is copy&pasted from other parts of `handle_one_xevent`.
Please avoid such code duplication (`handle_one_xevent` is already bad
enough from this point of view).

Also the code uses more than 80 columns at various places for no good
reason; that needs to be fixed.


        Stefan




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

* Re: XInput 2 support (again)
  2021-11-13 15:04   ` Stefan Monnier
@ 2021-11-14  1:29     ` Po Lu via Emacs development discussions.
  2021-11-14 15:29       ` Stefan Monnier
  0 siblings, 1 reply; 26+ messages in thread
From: Po Lu via Emacs development discussions. @ 2021-11-14  1:29 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Po Lu via Emacs development discussions.

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

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> @@ -3074,6 +3078,35 @@ 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)
>> +    {
>> +      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 HAVE_X_I18N
>>    FRAME_XIC (f) = NULL;
>>    if (use_xim)
>> @@ -3254,6 +3287,35 @@ x_window (struct frame *f)
>>      }
>>  #endif /* HAVE_X_I18N */
>>  
>> +#ifdef HAVE_XINPUT2
>> +  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
>> +    {
>> +      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
>> +

> Any reason this isn't consolidated into a separate function to avoid the
> code duplication?

Thanks, done.  During development there were a few different masks that
needed to be added to the no-toolkit build.

>> +	  struct xi_device_t *xi_device =
>> +	    &dpyinfo->devices[actual_devices++];

> The GNU coding standard encourages to cut lines just *before* infix
> operators rather after.  E.g.:
>
> 	  struct xi_device_t *xi_device
> 	    = &dpyinfo->devices[actual_devices++];

Also fixed, see attached patch.

> That's a damn large chunk of code to add into a function.

It's `handle_one_xevent' after all.

> A lot of it is copy&pasted from other parts of `handle_one_xevent`.
> Please avoid such code duplication (`handle_one_xevent` is already bad
> enough from this point of view).

I don't know enough about most of the Core Input code to dare change it.
It does mysterious things that I can not understand.

Also, most of the XInput 2 event code differs in subtle ways from the
Core Input code (see for example how the button grab is handled there).

It would make even more of a mess to try to reuse the Core Input 

> Also the code uses more than 80 columns at various places for no good
> reason; that needs to be fixed.

I tried to fix that as well.  Thanks for the comments.


[-- 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: 49345 bytes --]

From 315a02af7d6fa4b13b298c3495b73ceb37c13526 Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Sat, 16 Oct 2021 13:15:36 +0800
Subject: [PATCH] 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.
* 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)
(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.
---
 configure.ac    |   22 +
 src/Makefile.in |    7 +-
 src/gtkutil.c   |   72 ++-
 src/gtkutil.h   |    4 +
 src/xfns.c      |   45 ++
 src/xmenu.c     |    4 +
 src/xterm.c     | 1137 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/xterm.h     |   44 ++
 8 files changed, 1328 insertions(+), 7 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/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..e60dab9272 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);
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 fd498c0e32..d4da320c0b 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,203 @@ 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->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);
+    }
+}
+
+#endif
+
 void
 x_cr_destroy_frame_context (struct frame *f)
 {
@@ -4768,7 +4975,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 +5087,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 +8214,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 +8244,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 +8730,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 +9772,836 @@ 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);
+	    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);
+
+	    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 = DBL_MIN;
+
+			val->emacs_value += delta;
+
+			if (!f)
+			  {
+			    f = x_any_window_to_frame (dpyinfo, xev->event);
+
+			    if (!f)
+			      {
+				unblock_input ();
+				goto XI_OTHER;
+			      }
+			  }
+
+			if ((val->horizontal
+			     && fabs (val->emacs_value / val->increment) >= 1)
+			    || (!val->horizontal
+				&& fabs (val->emacs_value / val->increment) >= 1))
+			  {
+			    Lisp_Object tab_bar_arg = Qnil;
+			    bool tab_bar_p = false;
+			    bool tool_bar_p = false;
+			    bool s = signbit (val->emacs_value);
+
+			    bv.button = !val->horizontal ? (s ? 5 : 4) : (s ? 7 : 6);
+			    bv.type = ButtonPress;
+
+			    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;
+
+			    /* 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, true, x_x_to_emacs_modifiers (dpyinfo, bv.state));
+				    tab_bar_arg = handle_tab_bar_click
+				      (f, x, y, false, x_x_to_emacs_modifiers (dpyinfo, bv.state));
+				  }
+			      }
+
+			    if (!NILP (tab_bar_arg))
+			      inev.ie.arg = tab_bar_arg;
+
+			    if (!tool_bar_p && !(NILP (tab_bar_arg) && tab_bar_p))
+			      {
+				if (ignore_next_mouse_click_timeout)
+				  {
+				    if (xev->time > ignore_next_mouse_click_timeout)
+				      {
+					x_construct_mouse_click (&inev.ie, &bv, f);
+					if (!NILP (tab_bar_arg))
+					  inev.ie.arg = tab_bar_arg;
+					kbd_buffer_store_event (&inev.ie);
+					inev.ie.modifiers &= ~down_modifier;
+					inev.ie.modifiers |= up_modifier;
+					kbd_buffer_store_event (&inev.ie);
+				      }
+				    ignore_next_mouse_click_timeout = 0;
+				  }
+				else
+				  {
+				    x_construct_mouse_click (&inev.ie, &bv, f);
+				    kbd_buffer_store_event (&inev.ie);
+				    inev.ie.modifiers &= ~down_modifier;
+				    inev.ie.modifiers |= up_modifier;
+				    kbd_buffer_store_event (&inev.ie);
+				  }
+			      }
+
+			    val->emacs_value = DBL_MIN;
+			  }
+		      }
+		    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:
@@ -13192,6 +14276,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:
@@ -13624,6 +14742,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
@@ -13783,9 +14909,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] 26+ messages in thread

* Re: XInput 2 support (again)
  2021-11-14  1:29     ` Po Lu via Emacs development discussions.
@ 2021-11-14 15:29       ` Stefan Monnier
  2021-11-14 16:12         ` Eli Zaretskii
  0 siblings, 1 reply; 26+ messages in thread
From: Stefan Monnier @ 2021-11-14 15:29 UTC (permalink / raw)
  To: Po Lu; +Cc: Po Lu via Emacs development discussions.

>> A lot of it is copy&pasted from other parts of `handle_one_xevent`.
>> Please avoid such code duplication (`handle_one_xevent` is already bad
>> enough from this point of view).
>
> I don't know enough about most of the Core Input code to dare change it.
> It does mysterious things that I can not understand.
>
> Also, most of the XInput 2 event code differs in subtle ways from the
> Core Input code (see for example how the button grab is handled there).
>
> It would make even more of a mess to try to reuse the Core Input 

I'll let Eli&Lars decide if they're OK with it (after all, we have
a lot of such code duplication between the various
x/w32/ns/.. backends), but I think I'd like to see a unified version of
the code before deciding that the copy&paste is indeed a better option.


        Stefan




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

* Re: XInput 2 support (again)
  2021-11-14 15:29       ` Stefan Monnier
@ 2021-11-14 16:12         ` Eli Zaretskii
  2021-11-14 18:11           ` Stefan Monnier
  0 siblings, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2021-11-14 16:12 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: luangruo, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: Po Lu via "Emacs development discussions." <emacs-devel@gnu.org>
> Date: Sun, 14 Nov 2021 10:29:02 -0500
> 
> >> A lot of it is copy&pasted from other parts of `handle_one_xevent`.
> >> Please avoid such code duplication (`handle_one_xevent` is already bad
> >> enough from this point of view).
> >
> > I don't know enough about most of the Core Input code to dare change it.
> > It does mysterious things that I can not understand.
> >
> > Also, most of the XInput 2 event code differs in subtle ways from the
> > Core Input code (see for example how the button grab is handled there).
> >
> > It would make even more of a mess to try to reuse the Core Input 
> 
> I'll let Eli&Lars decide if they're OK with it (after all, we have
> a lot of such code duplication between the various
> x/w32/ns/.. backends), but I think I'd like to see a unified version of
> the code before deciding that the copy&paste is indeed a better option.

I had the same comment when this patch was first posted.  Po Lu says
it's infeasible for him to make it less repetitive.



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

* Re: XInput 2 support (again)
  2021-11-14 16:12         ` Eli Zaretskii
@ 2021-11-14 18:11           ` Stefan Monnier
  2021-11-14 18:51             ` Eli Zaretskii
  0 siblings, 1 reply; 26+ messages in thread
From: Stefan Monnier @ 2021-11-14 18:11 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

> I had the same comment when this patch was first posted.  Po Lu says
> it's infeasible for him to make it less repetitive.

Is there a middle-term plan to get rid of the current copy (replaced by
the XInput2 copy)?


        Stefan




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

* Re: XInput 2 support (again)
  2021-11-14 18:11           ` Stefan Monnier
@ 2021-11-14 18:51             ` Eli Zaretskii
  2021-11-15  0:26               ` Po Lu
  0 siblings, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2021-11-14 18:51 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: luangruo, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: luangruo@yahoo.com,  emacs-devel@gnu.org
> Date: Sun, 14 Nov 2021 13:11:04 -0500
> 
> > I had the same comment when this patch was first posted.  Po Lu says
> > it's infeasible for him to make it less repetitive.
> 
> Is there a middle-term plan to get rid of the current copy (replaced by
> the XInput2 copy)?

I don't know enough about developments in the X world to have an
opinion regarding whether we should have such a plan.  If we decide we
should, we need then look for a volunteer to do the job.



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

* Re: XInput 2 support (again)
  2021-11-14 18:51             ` Eli Zaretskii
@ 2021-11-15  0:26               ` Po Lu
  2021-11-15  2:48                 ` Stefan Monnier
  0 siblings, 1 reply; 26+ messages in thread
From: Po Lu @ 2021-11-15  0:26 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: Stefan Monnier, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Is there a middle-term plan to get rid of the current copy (replaced by
>> the XInput2 copy)?

> I don't know enough about developments in the X world to have an
> opinion regarding whether we should have such a plan.  If we decide we
> should, we need then look for a volunteer to do the job.

XInput2 is unavailable on some older X servers, some of which only
support 1.1, which is utterly useless for our purposes.  People also
like to turn it off over remote connections because they think it takes
up more bandwidth than Core Input.

So XInput 2 support should remain optional (not only at compile-time,
but also at runtime on a per-display basis).

Thanks.



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

* Re: XInput 2 support (again)
  2021-11-15  0:26               ` Po Lu
@ 2021-11-15  2:48                 ` Stefan Monnier
  2021-11-15  2:54                   ` Po Lu
  0 siblings, 1 reply; 26+ messages in thread
From: Stefan Monnier @ 2021-11-15  2:48 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

> XInput2 is unavailable on some older X servers, some of which only
> support 1.1, which is utterly useless for our purposes.

Can you quantify this "older"?

> People also like to turn it off over remote connections because they
> think it takes up more bandwidth than Core Input.

Do Gtk applications still work in the absence of XInput2 version ≥1.1 ?


        Stefan




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

* Re: XInput 2 support (again)
  2021-11-15  2:48                 ` Stefan Monnier
@ 2021-11-15  2:54                   ` Po Lu
  2021-11-15  3:18                     ` Stefan Monnier
  0 siblings, 1 reply; 26+ messages in thread
From: Po Lu @ 2021-11-15  2:54 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> Can you quantify this "older"?

Released before X11R7.5.

>> People also like to turn it off over remote connections because they
>> think it takes up more bandwidth than Core Input.

> Do Gtk applications still work in the absence of XInput2 version ≥1.1 ?

Yes, they will fall back to Core Input.  At least, GTK 3 will.  I don't
know about GTK 4, which has significant changes here.

Also, by 1.1 I meant 1.5.  Sorry for the confusion there.



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

* Re: XInput 2 support (again)
  2021-11-15  2:54                   ` Po Lu
@ 2021-11-15  3:18                     ` Stefan Monnier
  2021-11-15  4:50                       ` Po Lu
  0 siblings, 1 reply; 26+ messages in thread
From: Stefan Monnier @ 2021-11-15  3:18 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

Po Lu [2021-11-15 10:54:24] wrote:
> Stefan Monnier <monnier@iro.umontreal.ca> writes:
>> Can you quantify this "older"?
> Released before X11R7.5.

AFAICT that was 2009.  So, yes, old but not quite as ancient as some of
the other things we try to support.

>>> People also like to turn it off over remote connections because they
>>> think it takes up more bandwidth than Core Input.
>> Do Gtk applications still work in the absence of XInput2 version ≥1.1 ?
> Yes, they will fall back to Core Input.  At least, GTK 3 will.  I don't
> know about GTK 4, which has significant changes here.

So it looks like for the next few years we'll still want to support the
non-XInput2 code.


        Stefan




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

* Re: XInput 2 support (again)
  2021-11-15  3:18                     ` Stefan Monnier
@ 2021-11-15  4:50                       ` Po Lu
  2021-12-07 17:23                         ` Possible XInput2 cursor bug ? (Was: Re: XInput 2 support (again)) Madhu
  0 siblings, 1 reply; 26+ messages in thread
From: Po Lu @ 2021-11-15  4:50 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> AFAICT that was 2009.  So, yes, old but not quite as ancient as some of
> the other things we try to support.

> So it looks like for the next few years we'll still want to support the
> non-XInput2 code.

Yes, that would be a good idea.  It would be good to support it for even
longer, as people have the habit of turning XInput off when using X11
remoting, because they think it uses more bandwidth (though I've never
seen evidence that conclusively proves it).

Thanks.



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

* Possible XInput2 cursor bug ? (Was: Re: XInput 2 support (again))
  2021-11-15  4:50                       ` Po Lu
@ 2021-12-07 17:23                         ` Madhu
  2021-12-08  1:27                           ` Possible XInput2 cursor bug ? Po Lu
  0 siblings, 1 reply; 26+ messages in thread
From: Madhu @ 2021-12-07 17:23 UTC (permalink / raw)
  To: emacs-devel


When reading gnus with a recent emacs
configure -C --with-x-toolkit=athena --without-native-compilation
--with-xinput2

If I select a line on the summary buffer, to display the article buffer,
the block cursor disappears and the cursor becomes hollow (as if it were
a cursor in an inactive buffer). The focus is still in the summary
buffer though.

I think This sort of thing happens even outside gnus but perhaps someone
with an xinput2 build and gnus can verify if this is happening, I
rebuilt  --without-xinput2 and don't see the problem.




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

* Re: Possible XInput2 cursor bug ?
  2021-12-07 17:23                         ` Possible XInput2 cursor bug ? (Was: Re: XInput 2 support (again)) Madhu
@ 2021-12-08  1:27                           ` Po Lu
  2021-12-08  2:33                             ` Madhu
  0 siblings, 1 reply; 26+ messages in thread
From: Po Lu @ 2021-12-08  1:27 UTC (permalink / raw)
  To: Madhu; +Cc: emacs-devel

Madhu <enometh@meer.net> writes:

> When reading gnus with a recent emacs
> configure -C --with-x-toolkit=athena --without-native-compilation
> --with-xinput2

> If I select a line on the summary buffer, to display the article buffer,
> the block cursor disappears and the cursor becomes hollow (as if it were
> a cursor in an inactive buffer). The focus is still in the summary
> buffer though.

> I think This sort of thing happens even outside gnus but perhaps someone
> with an xinput2 build and gnus can verify if this is happening, I
> rebuilt  --without-xinput2 and don't see the problem.

First the important question: what window manager are you using (is it
GNOME Shell), and are you using Xwayland?

Secondly, please put a breakpoint on this part of xterm.c:

	    /* 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);
	      }

And see if that part is called when you select such a line in the
summary buffer.

Thanks.



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

* Re: Possible XInput2 cursor bug ?
  2021-12-08  1:27                           ` Possible XInput2 cursor bug ? Po Lu
@ 2021-12-08  2:33                             ` Madhu
  2021-12-08  2:39                               ` Po Lu
  0 siblings, 1 reply; 26+ messages in thread
From: Madhu @ 2021-12-08  2:33 UTC (permalink / raw)
  To: emacs-devel

* Po Lu <87ilvzga2h.fsf @yahoo.com> :
Wrote on Wed, 08 Dec 2021 09:27:34 +0800:
> Madhu <enometh@meer.net> writes:
>> If I select a line on the summary buffer, to display the article buffer,
>> the block cursor disappears and the cursor becomes hollow (as if it were
>> a cursor in an inactive buffer). The focus is still in the summary
>> buffer though.
>
> First the important question: what window manager are you using (is it
> GNOME Shell), and are you using Xwayland?

I was seeing this in a patched version of dwm. Thanks for your
suggestion -the problem indeed seems to be window-manager related.

> Secondly, please put a breakpoint on this part of xterm.c:
>
> 	    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);
> 	      }
>
> And see if that part is called when you select such a line in the
> summary buffer.

(dwm doesn't have a concept of iconification but) this is not getting
hit.  What is happening (I believe) is that the mouse pointer is getting
warped to the top right corner -- the window doesn't have any
decorations -- and emacs probably thinks it has lost focus, but the wm
keeps the focus on the window anyway.




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

* Re: Possible XInput2 cursor bug ?
  2021-12-08  2:33                             ` Madhu
@ 2021-12-08  2:39                               ` Po Lu
  2021-12-12  5:11                                 ` Madhu
  0 siblings, 1 reply; 26+ messages in thread
From: Po Lu @ 2021-12-08  2:39 UTC (permalink / raw)
  To: Madhu; +Cc: emacs-devel

Madhu <enometh@meer.net> writes:

> (dwm doesn't have a concept of iconification but) this is not getting
> hit.  What is happening (I believe) is that the mouse pointer is getting
> warped to the top right corner -- the window doesn't have any
> decorations -- and emacs probably thinks it has lost focus, but the wm
> keeps the focus on the window anyway.

Hmm...  Could you put a breakpoint here:

  else if (type == FocusOut)
    {
->    frame->output_data.x->focus_state &= ~state;

      if (dpyinfo->x_focus_event_frame == frame)
        {
          dpyinfo->x_focus_event_frame = 0;
          x_new_focus_frame (dpyinfo, 0);

          bufp->kind = FOCUS_OUT_EVENT;
          XSETFRAME (bufp->frame_or_window, frame);
        }

#ifdef HAVE_X_I18N
      if (FRAME_XIC (frame))
        XUnsetICFocus (FRAME_XIC (frame));
#endif
      if (frame->pointer_invisible)
        XTtoggle_invisible_pointer (frame, false);
    }

And then show the output of the following command in gdb once it is hit:

  (gdb) p dpyinfo->x_focus_event_frame

Thanks.

Also, what is the value of `focus-follows-mouse', and how is your window
manager configured in that regard?



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

* Re: Possible XInput2 cursor bug ?
  2021-12-08  2:39                               ` Po Lu
@ 2021-12-12  5:11                                 ` Madhu
  2021-12-12  5:27                                   ` Po Lu
  2021-12-12  7:26                                   ` Eli Zaretskii
  0 siblings, 2 replies; 26+ messages in thread
From: Madhu @ 2021-12-12  5:11 UTC (permalink / raw)
  To: luangruo; +Cc: emacs-devel

*  Po Lu <luangruo@yahoo.com> <87a6hbg6ps.fsf@yahoo.com>
Wrote on Wed, 08 Dec 2021 10:39:59 +0800

> Madhu <enometh@meer.net> writes:
>> (dwm doesn't have a concept of iconification but) this is not getting
>> hit.  What is happening (I believe) is that the mouse pointer is getting
>> warped to the top right corner -- the window doesn't have any
>> decorations -- and emacs probably thinks it has lost focus, but the wm
>> keeps the focus on the window anyway.
>
> Hmm...  Could you put a breakpoint here:
>
>   else if (type == FocusOut)
>     {
> ->    frame->output_data.x->focus_state &= ~state;
>
>       if (dpyinfo->x_focus_event_frame == frame)
>         {
>           dpyinfo->x_focus_event_frame = 0;
>           x_new_focus_frame (dpyinfo, 0);
>
>           bufp->kind = FOCUS_OUT_EVENT;
>           XSETFRAME (bufp->frame_or_window, frame);
>         }

> And then show the output of the following command in gdb once it is hit:
>   (gdb) p dpyinfo->x_focus_event_frame
>
> Also, what is the value of `focus-follows-mouse', and how is your window
> manager configured in that regard?

Hello, finally got back to this. To recap: the problem is consistently
reproducible only with my own window manager. The window manager does
"focus follows mouse", and (list focus-follows-mouse
mouse-autoselect-window) => (nil nil)

The test case is  evaluating

(select-frame-set-input-focus (selected-frame))
                                               ^ C-x C-e

in a --with-xinput2 build with xt, this causes the block cursor to be
replaced by a hollow one, indicating that emacs thought that the focus
had gone out of the window.

However debugging this with gdb isn't that easy - I can't do it on the
same desktop as I have to switch windows to get at the gdb prompt and
that interferes with the focus.

Accessing the gdb prompt from another machine, I see the breakpoint is
hit several times.
If the lisp backtrace involves:

"x-focus-frame" (0xffffc970)
"select-frame-set-input-focus" (0xffffcbf0)
"eval" (0xffffcde8)
"elisp--eval-last-sexp" (0xffffd0e8)
"eval-last-sexp" (0xffffd430)
"funcall-interactively" (0xffffd428)
"call-interactively" (0xffffd660)
"command-execute" (0xffffd9b8)

 dpyinfo->x_focus_event_frame

has the value of the single frame. This gets hit 3-4 times.

When the backtrace has the form

#0  x_focus_changed
    (type=10, state=1, dpyinfo=0xbb67b0, frame=0xcfe6e0, bufp=0x7fffffffbf50)
    at ../../src/xterm.c:4895
#1  0x00000000004e9d41 in handle_one_xevent
    (dpyinfo=0xbb67b0, event=0x7fffffffc5e0, finish=0x7fffffffc5dc, hold_quit=0x
7fffffffc6d0) at ../../src/xterm.c:9291
#2  0x00000000004edadf in XTread_socket
    (terminal=<optimized out>, hold_quit=0x7fffffffc6d0)
    at ../../src/xterm.c:10865
[...]
#28 0x000000000050ddc3 in Frecursive_edit () at ../../src/keyboard.c:804
#29 0x000000000042715d in main (argc=4, argv=<optimized out>) at ../../src/emacs
.c:2434

Lisp Backtrace:
"prin1" (0xffffca68)
"elisp--eval-last-sexp-print-value" (0xffffcde0)
"elisp--eval-last-sexp" (0xffffd0e8)
"eval-last-sexp" (0xffffd430)
"funcall-interactively" (0xffffd428)
"call-interactively" (0xffffd660)
"command-execute" (0xffffd9b8)

(gdb) p dpyinfo->x_focus_event_frame
$7 = (struct frame *) 0x0

This also happens 3-4 times.

Now I tried the same thing in a --without-xinput2 build and noticed
that the breakpoint is hit only 3 times (compared to the 8-9 times in
the --with-xinput2 build)

I looked at xev on the window in both cases and could spot no
difference there.

Now the clincher: when accessing the gdb prompt through ssh from
another box (so the mouse is untouched), sometimes I don't see the
problem (of the cursor changing). so this is tricky to debug.

[side comment: The GDB footprint for emacs -Q -D is 3-4GB. for
native-compilation it seems to be 1GB more. gdb is also slow to load
frames with jit on, and a new fingerprint on every `make' makes jit
infeasible during "development".  already I can't use gdb on my 8G RAM
+ 8G swap laptop on a regular running emacs without paging to hades
and back.]

Sorry this is so inconclusive.



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

* Re: Possible XInput2 cursor bug ?
  2021-12-12  5:11                                 ` Madhu
@ 2021-12-12  5:27                                   ` Po Lu
  2021-12-12  6:02                                     ` Po Lu
  2021-12-12  7:26                                   ` Eli Zaretskii
  1 sibling, 1 reply; 26+ messages in thread
From: Po Lu @ 2021-12-12  5:27 UTC (permalink / raw)
  To: Madhu; +Cc: emacs-devel

Madhu <enometh@meer.net> writes:

> Hello, finally got back to this. To recap: the problem is consistently
> reproducible only with my own window manager. The window manager does
> "focus follows mouse", and (list focus-follows-mouse
> mouse-autoselect-window) => (nil nil)

You have to tell Emacs how your window manager behaves in this regard by
setting `focus-follows-mouse' appropriately.  In this case, it should be
set to `t'.  Please try setting it to that and see if the problem
persists.

> The test case is  evaluating
>
> (select-frame-set-input-focus (selected-frame))
>                                                ^ C-x C-e

FWIW, I can't reproduce it here.

> However debugging this with gdb isn't that easy - I can't do it on the
> same desktop as I have to switch windows to get at the gdb prompt and
> that interferes with the focus.

I find the best solution to that problem to be running the program being
debugged inside Xephyr (or some other nested X server).

> Accessing the gdb prompt from another machine, I see the breakpoint is
> hit several times.
> If the lisp backtrace involves:
>
> "x-focus-frame" (0xffffc970)
> "select-frame-set-input-focus" (0xffffcbf0)
> "eval" (0xffffcde8)
> "elisp--eval-last-sexp" (0xffffd0e8)
> "eval-last-sexp" (0xffffd430)
> "funcall-interactively" (0xffffd428)
> "call-interactively" (0xffffd660)
> "command-execute" (0xffffd9b8)
>
>  dpyinfo->x_focus_event_frame
>
> has the value of the single frame. This gets hit 3-4 times.

That's expected.

> When the backtrace has the form
>
> #0  x_focus_changed
>     (type=10, state=1, dpyinfo=0xbb67b0, frame=0xcfe6e0, bufp=0x7fffffffbf50)
>     at ../../src/xterm.c:4895
> #1  0x00000000004e9d41 in handle_one_xevent
>     (dpyinfo=0xbb67b0, event=0x7fffffffc5e0, finish=0x7fffffffc5dc, hold_quit=0x
> 7fffffffc6d0) at ../../src/xterm.c:9291
> #2  0x00000000004edadf in XTread_socket
>     (terminal=<optimized out>, hold_quit=0x7fffffffc6d0)
>     at ../../src/xterm.c:10865

That's a LeaveNotify event, not a GenericEvent.  The important question
is how many times the code for GenericEvents is called, so could you set
a breakpoint here instead, and see if it is called an abnormal amount of
times:

	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);

> I looked at xev on the window in both cases and could spot no
> difference there.

No worries there: xev doesn't know about XInput 2, so it can't display
anything meaningful.

Thanks.



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

* Re: Possible XInput2 cursor bug ?
  2021-12-12  5:27                                   ` Po Lu
@ 2021-12-12  6:02                                     ` Po Lu
  2021-12-12 14:45                                       ` Madhu
  2021-12-13  1:21                                       ` Po Lu
  0 siblings, 2 replies; 26+ messages in thread
From: Po Lu @ 2021-12-12  6:02 UTC (permalink / raw)
  To: Madhu; +Cc: emacs-devel

Po Lu <luangruo@yahoo.com> writes:

> Madhu <enometh@meer.net> writes:
>
>> Hello, finally got back to this. To recap: the problem is consistently
>> reproducible only with my own window manager. The window manager does
>> "focus follows mouse", and (list focus-follows-mouse
>> mouse-autoselect-window) => (nil nil)
>
> You have to tell Emacs how your window manager behaves in this regard by
> setting `focus-follows-mouse' appropriately.  In this case, it should be
> set to `t'.  Please try setting it to that and see if the problem
> persists.

I reproduced your issue on twm.  It should be fixed now, please try
latest master.

Thanks.



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

* Re: Possible XInput2 cursor bug ?
  2021-12-12  5:11                                 ` Madhu
  2021-12-12  5:27                                   ` Po Lu
@ 2021-12-12  7:26                                   ` Eli Zaretskii
  2021-12-12 14:55                                     ` Madhu
  1 sibling, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2021-12-12  7:26 UTC (permalink / raw)
  To: Madhu; +Cc: luangruo, emacs-devel

> Date: Sun, 12 Dec 2021 10:41:21 +0530 (IST)
> From: Madhu <enometh@meer.net>
> Cc: emacs-devel@gnu.org
> 
> [side comment: The GDB footprint for emacs -Q -D is 3-4GB. for
> native-compilation it seems to be 1GB more.

What do you mean by "GDB footprint"?  Is that the GDB memory
consumption?

> gdb is also slow to load frames with jit on

Maybe you should tell GDB not to read symbols automatically from the
shared libraries loaded by Emacs:

  (gdb) set auto-solib-add off

Do that inside GDB, before you run Emacs with the "run" command.  This
should both speed up loading frames and decrease the memory footprint
of GDB.

> and a new fingerprint on every `make' makes jit infeasible during
> "development".

I don't understand this part.  Can you elaborate?



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

* Re: Possible XInput2 cursor bug ?
  2021-12-12  6:02                                     ` Po Lu
@ 2021-12-12 14:45                                       ` Madhu
  2021-12-13  1:21                                       ` Po Lu
  1 sibling, 0 replies; 26+ messages in thread
From: Madhu @ 2021-12-12 14:45 UTC (permalink / raw)
  To: luangruo; +Cc: emacs-devel

*  Po Lu <luangruo@yahoo.com> <87wnkaz7gc.fsf@yahoo.com>
Wrote on Sun, 12 Dec 2021 14:02:43 +0800

> Po Lu <luangruo@yahoo.com> writes:
> 
>> Madhu <enometh@meer.net> writes:
>>
>>> Hello, finally got back to this. To recap: the problem is consistently
>>> reproducible only with my own window manager. The window manager does
>>> "focus follows mouse", and (list focus-follows-mouse
>>> mouse-autoselect-window) => (nil nil)
[sorry I forgot to mention I tried both settings]

>> You have to tell Emacs how your window manager behaves in this regard by
>> setting `focus-follows-mouse' appropriately.  In this case, it should be
>> set to `t'.  Please try setting it to that and see if the problem
>> persists.
>
> I reproduced your issue on twm.  It should be fixed now, please try
> latest master.

I couldn't reproduce this on twm myself, but I didn't try the default
config for twm.

The commit 4f987e189d "Fix last change.* src/xterm.c
(x_detect_focus_change): Test against FOCUS_EXPLICIT." works, in that
the focus is kept in the window.

The next commit 11b2dfca287d "* src/xterm.c (x_detect_focus_change): Fix XI2 focus on GTK builds." reverses the condition in the test! this does not work.

I haven't grokked xinput2 at all, so I'm just reporting what I'm seeing:)






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

* Re: Possible XInput2 cursor bug ?
  2021-12-12  7:26                                   ` Eli Zaretskii
@ 2021-12-12 14:55                                     ` Madhu
  2021-12-12 15:08                                       ` Eli Zaretskii
  0 siblings, 1 reply; 26+ messages in thread
From: Madhu @ 2021-12-12 14:55 UTC (permalink / raw)
  To: eliz; +Cc: luangruo, emacs-devel

*  Eli Zaretskii <eliz@gnu.org> <83ilvup9lx.fsf@gnu.org>
Wrote on Sun, 12 Dec 2021 09:26:18 +0200

>> Date: Sun, 12 Dec 2021 10:41:21 +0530 (IST)
>> From: Madhu <enometh@meer.net>
>> Cc: emacs-devel@gnu.org
>>
>> [side comment: The GDB footprint for emacs -Q -D is 3-4GB. for
>> native-compilation it seems to be 1GB more.
>
> What do you mean by "GDB footprint"?  Is that the GDB memory
> consumption?
yes, I meant resident memory reported by ps/top

>> gdb is also slow to load frames with jit on
>
> Maybe you should tell GDB not to read symbols automatically from the
> shared libraries loaded by Emacs:
>
>   (gdb) set auto-solib-add off

That helps a lot. (It brings down the RES reported in top to 2.5G in
jit builds, I may be misinterpreting what I'm seeing but a non-native
build shows only some 100M in use by gdb)

> Do that inside GDB, before you run Emacs with the "run" command.  This
> should both speed up loading frames and decrease the memory footprint
> of GDB.
>
>> and a new fingerprint on every `make' makes jit infeasible during
>> "development".
>
> I don't understand this part.  Can you elaborate?

Like when I updated master to check the fix, the ELN files had to be
generated again because it's a new emacs. And master is going to be
shortlived and require a new fingerprint very shortly.

But I hit another problem. When I edited xterm.c and hit `make' emacs
was  built but is not able to run, I got a

emacs: Trying to load incoherent dumped eln file /12/build/emacs/build-xt/native-lisp/29.0.50-473a9dd2/preloaded/subr-13adf6a6-a6425de0.eln



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

* Re: Possible XInput2 cursor bug ?
  2021-12-12 14:55                                     ` Madhu
@ 2021-12-12 15:08                                       ` Eli Zaretskii
  2021-12-12 18:15                                         ` Madhu
  0 siblings, 1 reply; 26+ messages in thread
From: Eli Zaretskii @ 2021-12-12 15:08 UTC (permalink / raw)
  To: Madhu; +Cc: luangruo, emacs-devel

> Date: Sun, 12 Dec 2021 20:25:02 +0530 (IST)
> Cc: luangruo@yahoo.com, emacs-devel@gnu.org
> From: Madhu <enometh@meer.net>
> 
> > Maybe you should tell GDB not to read symbols automatically from the
> > shared libraries loaded by Emacs:
> >
> >   (gdb) set auto-solib-add off
> 
> That helps a lot. (It brings down the RES reported in top to 2.5G in
> jit builds, I may be misinterpreting what I'm seeing but a non-native
> build shows only some 100M in use by gdb)

On my system, debugging a natively-compiled Emacs takes GDB just a bit
more that debugging non-native one: 120MB vs 100MB.  The first number
is similar to yours, so I wonder how come the second number is so much
different on you're system.  Is that with "emacs -Q" run under a
debugger?  In any case, a full Emacs version running for 10 days under
GDB causes GDB to have just 200MB of memory here, which is a far cry
from your 2.5GB.  I wonder why such a large difference.

> >> and a new fingerprint on every `make' makes jit infeasible during
> >> "development".
> >
> > I don't understand this part.  Can you elaborate?
> 
> Like when I updated master to check the fix, the ELN files had to be
> generated again because it's a new emacs. And master is going to be
> shortlived and require a new fingerprint very shortly.

Yes, but regenerating the *.eln files takes a few minutes, so why is
that such a big problem?

> But I hit another problem. When I edited xterm.c and hit `make' emacs
> was  built but is not able to run, I got a
> 
> emacs: Trying to load incoherent dumped eln file /12/build/emacs/build-xt/native-lisp/29.0.50-473a9dd2/preloaded/subr-13adf6a6-a6425de0.eln

Look closer at the build process, something probably failed there, so
the build didn't complete successfully.



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

* Re: Possible XInput2 cursor bug ?
  2021-12-12 15:08                                       ` Eli Zaretskii
@ 2021-12-12 18:15                                         ` Madhu
  0 siblings, 0 replies; 26+ messages in thread
From: Madhu @ 2021-12-12 18:15 UTC (permalink / raw)
  To: eliz; +Cc: luangruo, emacs-devel

*  Eli Zaretskii <eliz@gnu.org> <83czm1oo82.fsf@gnu.org>
Wrote on Sun, 12 Dec 2021 17:08:13 +0200
>> Date: Sun, 12 Dec 2021 20:25:02 +0530 (IST)
>> From: Madhu <enometh@meer.net>
>> That helps a lot. (It brings down the RES reported in top to 2.5G
>> in jit builds, I may be misinterpreting what I'm seeing but a
>> non-native build shows only some 100M in use by gdb)
> On my system, debugging a natively-compiled Emacs takes GDB just a bit
> more that debugging non-native one: 120MB vs 100MB.  The first number
> is similar to yours, so I wonder how come the second number is so much
> different on you're system.  Is that with "emacs -Q" run under a
> debugger?  In any case, a full Emacs version running for 10 days under
> GDB causes GDB to have just 200MB of memory here, which is a far cry
> from your 2.5GB.  I wonder why such a large difference.

yes for emacs -Q -D. I can confirm that by the time I get to the end
of the first backtrace I have 2GB rss in a native build, and about
100M RSS for a non-native build. I do have some system libraries
(glib, etc.)  compiled with debug info, but that doesn't explain the
difference.


> Yes, but regenerating the *.eln files takes a few minutes, so why is
> that such a big problem?

It takes a little longer for me and it's wasteful if youre just going
to throw it away anyway.

>> But I hit another problem. When I edited xterm.c and hit `make' emacs
>> was  built but is not able to run, I got a
>>
>> emacs: Trying to load incoherent dumped eln file /12/build/emacs/build-xt/native-lisp/29.0.50-473a9dd2/preloaded/subr-13adf6a6-a6425de0.eln
>
> Look closer at the build process, something probably failed there, so
> the build didn't complete successfully.

[I couldn't spot it, but a ./configure and build fixed it after
recompiling all the c files while retaining the same eln directory (i
think)
]




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

* Re: Possible XInput2 cursor bug ?
  2021-12-12  6:02                                     ` Po Lu
  2021-12-12 14:45                                       ` Madhu
@ 2021-12-13  1:21                                       ` Po Lu
  2021-12-13  4:18                                         ` Madhu
  1 sibling, 1 reply; 26+ messages in thread
From: Po Lu @ 2021-12-13  1:21 UTC (permalink / raw)
  To: Madhu; +Cc: emacs-devel


I tried to fix it for Xt builds again, please test.  Thanks.



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

* Re: Possible XInput2 cursor bug ?
  2021-12-13  1:21                                       ` Po Lu
@ 2021-12-13  4:18                                         ` Madhu
  0 siblings, 0 replies; 26+ messages in thread
From: Madhu @ 2021-12-13  4:18 UTC (permalink / raw)
  To: emacs-devel


* Po Lu <87tufdwb86.fsf@yahoo.com> :
Wrote on Mon, 13 Dec 2021 09:21:45 +0800:

> I tried to fix it for Xt builds again, please test.  Thanks.

Will do. Just a note - as of "Mon Dec 13 04:15:50 AM UTC 2021" the
commits aren't available on master at
git://git.savannah.gnu.org/emacs.git I can see the commit at
https://github.com/emacs-mirror/emacs/commits/master though.
TZ=UTC date -d "Mon, 13 Dec 2021 09:17:55 +0800"
which says it was commited at "Mon Dec 13 01:17:55 AM UTC 2021"




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

end of thread, other threads:[~2021-12-13  4:18 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <87v90wmh6m.fsf.ref@yahoo.com>
2021-11-13 11:24 ` XInput 2 support (again) Po Lu via Emacs development discussions.
2021-11-13 15:04   ` Stefan Monnier
2021-11-14  1:29     ` Po Lu via Emacs development discussions.
2021-11-14 15:29       ` Stefan Monnier
2021-11-14 16:12         ` Eli Zaretskii
2021-11-14 18:11           ` Stefan Monnier
2021-11-14 18:51             ` Eli Zaretskii
2021-11-15  0:26               ` Po Lu
2021-11-15  2:48                 ` Stefan Monnier
2021-11-15  2:54                   ` Po Lu
2021-11-15  3:18                     ` Stefan Monnier
2021-11-15  4:50                       ` Po Lu
2021-12-07 17:23                         ` Possible XInput2 cursor bug ? (Was: Re: XInput 2 support (again)) Madhu
2021-12-08  1:27                           ` Possible XInput2 cursor bug ? Po Lu
2021-12-08  2:33                             ` Madhu
2021-12-08  2:39                               ` Po Lu
2021-12-12  5:11                                 ` Madhu
2021-12-12  5:27                                   ` Po Lu
2021-12-12  6:02                                     ` Po Lu
2021-12-12 14:45                                       ` Madhu
2021-12-13  1:21                                       ` Po Lu
2021-12-13  4:18                                         ` Madhu
2021-12-12  7:26                                   ` Eli Zaretskii
2021-12-12 14:55                                     ` Madhu
2021-12-12 15:08                                       ` Eli Zaretskii
2021-12-12 18:15                                         ` Madhu

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).