]> git.eshelyaron.com Git - emacs.git/commitdiff
Add support for event processing via XInput 2
authorPo Lu <luangruo@yahoo.com>
Sat, 16 Oct 2021 05:15:36 +0000 (13:15 +0800)
committerPo Lu <luangruo@yahoo.com>
Sat, 20 Nov 2021 10:25:09 +0000 (18:25 +0800)
* configure.ac: Add an option to use XInput 2 if available.
* src/Makefile.in (XINPUT_LIBS, XINPUT_CFLAGS): New variables.
(EMACS_CFLAGS): Add Xinput CFLAGS.
(LIBES): Add XInput libs.
* src/xmenu.c (popup_activated_flag): Expose flag if
XInput 2 is available.
* src/xfns.c (x_window): Set XInput 2 event mask.
(setup_xi_event_mask): New function.
(syms_of_xfns): Provide XInput 2 feature.
* src/xterm.c (x_detect_focus_change): Handle XInput 2
GenericEvents.
(handle_one_xevent): Handle XInput 2 events.
(x_term_init): Ask the server for XInput 2 support and set
xkb_desc if available.
(x_delete_terminal): Free XKB kb desc if it exists, and free
XI2 devices if they exist.
(xi_grab_or_ungrab_device)
(xi_reset_scroll_valuators_for_device_id)
(x_free_xi_devices, x_init_master_valuators): New functions.
(x_get_scroll_valuator_delta): New function.
(init_xterm): Don't tell GTK to only use Core Input when built
with XInput 2 support.
* src/xterm.h (struct x_display_info): Add fields for XKB
and XI2 support.
* src/gtkutil.c (xg_event_is_for_menubar): Handle
XIDeviceEvents.
(xg_is_menu_window): New function.
(xg_event_is_for_scrollbar): Handle XIDeviceEvents.
* etc/NEWS: Document changes.

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

(mouse-wheel-text-scale)
(mwheel-scroll): Test for alternate events.
(mouse-wheel--setup-bindings): Set up bindings for alternate
buttons.

configure.ac
etc/NEWS
lisp/mwheel.el
src/Makefile.in
src/gtkutil.c
src/gtkutil.h
src/xfns.c
src/xmenu.c
src/xterm.c
src/xterm.h

index c231c2ceae2ba9fefd1a290364f57bbbca5ebeae..239bf72f716aef3f50d18918af9eec87c6629d90 100644 (file)
@@ -487,6 +487,7 @@ OPTION_DEFAULT_ON([modules],[don't compile with dynamic modules support])
 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 @@ fi
 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 @@ AS_ECHO(["  Does Emacs use -lXaw3d?                                 ${HAVE_XAW3D
   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
index a5ca8fbb2be9ae588310801dfc3de960be03bf40..3cceac5584491c7cf51e11be5bfcbbe97366ffb5 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -30,6 +30,14 @@ The file is typically installed using a file name akin to
 If a constant file name is required, the file can be renamed to
 "emacs.pdmp", and Emacs will find it during startup anyway.
 
+** Emacs now supports use of XInput 2 for input events.
+If your X server has support and you have the XInput 2 development headers
+installed, you can configure Emacs with the option '--with-xinput2' to enable
+this support.
+
+The named feature `xinput2' can be used to test for the presence of
+XInput 2 support from Lisp programs.
+
 \f
 * Startup Changes in Emacs 29.1
 
@@ -224,6 +232,15 @@ The user option 'comint-terminfo-terminal' and variable
 'system-uses-terminfo' can now be set as connection-local variables to
 change the terminal used on a remote host.
 
+** Mwheel
+
+---
+*** New user options for alternate wheel events.
+The options 'mouse-wheel-down-alternate-event', 'mouse-wheel-up-alternate-event',
+'mouse-wheel-left-alternate-event', and 'mouse-wheel-right-alternate-event' have
+been added to better support systems where two kinds of wheel events can be
+received.
+
 \f
 * Changes in Specialized Modes and Packages in Emacs 29.1
 
index 51410e3ef4c013e56201d776d5d889102ab6647a..3d0b8f07cb7dc794d4ead2ce7d19b727916737b9 100644 (file)
   :type 'symbol
   :set 'mouse-wheel-change-button)
 
+(defcustom mouse-wheel-down-alternate-event
+  (when (featurep 'xinput2) 'wheel-up)
+  "Alternative wheel down event to consider."
+  :group 'mouse
+  :type 'symbol
+  :set 'mouse-wheel-change-button)
+
 (defcustom mouse-wheel-up-event
   (if (or (featurep 'w32-win) (featurep 'ns-win))
       'wheel-down
   :type 'symbol
   :set 'mouse-wheel-change-button)
 
+(defcustom mouse-wheel-up-alternate-event
+  (when (featurep 'xinput2) 'wheel-down)
+  "Alternative wheel up event to consider."
+  :group 'mouse
+  :type 'symbol
+  :set 'mouse-wheel-change-button)
+
 (defcustom mouse-wheel-click-event 'mouse-2
   "Event that should be temporarily inhibited after mouse scrolling.
 The mouse wheel is typically on the mouse-2 button, so it may easily
@@ -226,12 +240,20 @@ Also see `mouse-wheel-tilt-scroll'."
     'mouse-6)
   "Event used for scrolling left.")
 
+(defvar mouse-wheel-left-alternate-event
+  (when (featurep 'xinput2) 'wheel-left)
+  "Alternative wheel left event to consider.")
+
 (defvar mouse-wheel-right-event
   (if (or (featurep 'w32-win) (featurep 'ns-win))
       'wheel-right
     'mouse-7)
   "Event used for scrolling right.")
 
+(defvar mouse-wheel-right-alternate-event
+  (when (featurep 'xinput2) 'wheel-right)
+  "Alternative wheel right event to consider.")
+
 (defun mouse-wheel--get-scroll-window (event)
   "Return window for mouse wheel event EVENT.
 If `mouse-wheel-follow-mouse' is non-nil, return the window that
@@ -296,14 +318,16 @@ value of ARG, and the command uses it in subsequent scrolls."
     (condition-case nil
         (unwind-protect
            (let ((button (mwheel-event-button event)))
-              (cond ((and (eq amt 'hscroll) (eq button mouse-wheel-down-event))
+              (cond ((and (eq amt 'hscroll) (memq button (list mouse-wheel-down-event
+                                                               mouse-wheel-down-alternate-event)))
                      (when (and (natnump arg) (> arg 0))
                        (setq mouse-wheel-scroll-amount-horizontal arg))
                      (funcall (if mouse-wheel-flip-direction
                                   mwheel-scroll-left-function
                                 mwheel-scroll-right-function)
                               mouse-wheel-scroll-amount-horizontal))
-                    ((eq button mouse-wheel-down-event)
+                    ((memq button (list mouse-wheel-down-event
+                                        mouse-wheel-down-alternate-event))
                      (condition-case nil (funcall mwheel-scroll-down-function amt)
                        ;; Make sure we do indeed scroll to the beginning of
                        ;; the buffer.
@@ -318,23 +342,27 @@ value of ARG, and the command uses it in subsequent scrolls."
                           ;; for a reason that escapes me.  This problem seems
                           ;; to only affect scroll-down.  --Stef
                           (set-window-start (selected-window) (point-min))))))
-                    ((and (eq amt 'hscroll) (eq button mouse-wheel-up-event))
+                    ((and (eq amt 'hscroll) (memq button (list mouse-wheel-up-event
+                                                               mouse-wheel-up-alternate-event)))
                      (when (and (natnump arg) (> arg 0))
                        (setq mouse-wheel-scroll-amount-horizontal arg))
                      (funcall (if mouse-wheel-flip-direction
                                   mwheel-scroll-right-function
                                 mwheel-scroll-left-function)
                               mouse-wheel-scroll-amount-horizontal))
-                    ((eq button mouse-wheel-up-event)
+                    ((memq button (list mouse-wheel-up-event
+                                        mouse-wheel-up-alternate-event))
                      (condition-case nil (funcall mwheel-scroll-up-function amt)
                        ;; Make sure we do indeed scroll to the end of the buffer.
                        (end-of-buffer (while t (funcall mwheel-scroll-up-function)))))
-                    ((eq button mouse-wheel-left-event) ; for tilt scroll
+                    ((memq button (list mouse-wheel-left-event
+                                        mouse-wheel-left-alternate-event)) ; for tilt scroll
                      (when mouse-wheel-tilt-scroll
                        (funcall (if mouse-wheel-flip-direction
                                     mwheel-scroll-right-function
                                   mwheel-scroll-left-function) amt)))
-                    ((eq button mouse-wheel-right-event) ; for tilt scroll
+                    ((memq button (list mouse-wheel-right-event
+                                        mouse-wheel-right-alternate-event)) ; for tilt scroll
                      (when mouse-wheel-tilt-scroll
                        (funcall (if mouse-wheel-flip-direction
                                     mwheel-scroll-left-function
@@ -378,9 +406,11 @@ value of ARG, and the command uses it in subsequent scrolls."
         (button (mwheel-event-button event)))
     (select-window scroll-window 'mark-for-redisplay)
     (unwind-protect
-        (cond ((eq button mouse-wheel-down-event)
+        (cond ((memq button (list mouse-wheel-down-event
+                                  mouse-wheel-down-alternate-event))
                (text-scale-increase 1))
-              ((eq button mouse-wheel-up-event)
+              ((eq button (list mouse-wheel-up-event
+                                mouse-wheel-up-alternate-event))
                (text-scale-decrease 1)))
       (select-window selected-window))))
 
@@ -432,15 +462,23 @@ an event used for scrolling, such as `mouse-wheel-down-event'."
     (cond
      ;; Bindings for changing font size.
      ((and (consp binding) (eq (cdr binding) 'text-scale))
-      (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event))
-        (mouse-wheel--add-binding `[,(list (caar binding) event)]
-                                  'mouse-wheel-text-scale)))
+      (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event
+                           mouse-wheel-down-alternate-event
+                           mouse-wheel-up-alternate-event))
+        (when event
+          (mouse-wheel--add-binding `[,(list (caar binding) event)]
+                                    'mouse-wheel-text-scale))))
      ;; Bindings for scrolling.
      (t
       (dolist (event (list mouse-wheel-down-event mouse-wheel-up-event
-                           mouse-wheel-left-event mouse-wheel-right-event))
-        (dolist (key (mouse-wheel--create-scroll-keys binding event))
-          (mouse-wheel--add-binding key 'mwheel-scroll)))))))
+                           mouse-wheel-left-event mouse-wheel-right-event
+                           mouse-wheel-down-alternate-event
+                           mouse-wheel-up-alternate-event
+                           mouse-wheel-left-alternate-event
+                           mouse-wheel-right-alternate-event))
+        (when event
+          (dolist (key (mouse-wheel--create-scroll-keys binding event))
+            (mouse-wheel--add-binding key 'mwheel-scroll))))))))
 
 (when mouse-wheel-mode
   (mouse-wheel--setup-bindings))
index 4c5535f8ad9e9126d07754697b7dba327e618da2..0aaaf91d392e47043d1ad3be548aefb476020841 100644 (file)
@@ -258,6 +258,9 @@ XINERAMA_CFLAGS = @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=-Demacs $(MYCPPFLAGS) -I. -I$(srcdir) \
   $(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 = $(LIBS) $(W32_LIBS) $(LIBS_GNUSTEP) $(LIBX_BASE) $(LIBIMAGE) \
    $(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,
index a9eabf47d8f44e95d77495eedbb3ffd69261d272..9e676cd025b65528dc0cd41a8a272906315b5aee 100644 (file)
@@ -47,6 +47,10 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #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 ();
index 31a12cd5d3c75b6898bbdcf226390d34e7a854b6..95dd75b7fad99a0394fcb26e3f0ea5a36ded32ca 100644 (file)
@@ -192,6 +192,10 @@ extern Lisp_Object xg_get_page_setup (void);
 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);
 
index 785ae3baca5c806941185772285af41513984110..b33b40b330b4c4a09f0931c923e88c966f390a53 100644 (file)
@@ -57,6 +57,10 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include <X11/extensions/Xdbe.h>
 #endif
 
+#ifdef HAVE_XINPUT2
+#include <X11/extensions/XInput2.h>
+#endif
+
 #ifdef USE_X_TOOLKIT
 #include <X11/Shell.h>
 
@@ -2912,6 +2916,37 @@ initial_set_up_x_back_buffer (struct frame *f)
   unblock_input ();
 }
 
+#if defined HAVE_XINPUT2 && !defined USE_GTK
+static void
+setup_xi_event_mask (struct frame *f)
+{
+  XIEventMask mask;
+  ptrdiff_t l = XIMaskLen (XI_LASTEVENT);
+  unsigned char *m;
+
+  mask.mask = m = alloca (l);
+  memset (m, 0, l);
+  mask.mask_len = l;
+  mask.deviceid = XIAllMasterDevices;
+
+  XISetMask (m, XI_ButtonPress);
+  XISetMask (m, XI_ButtonRelease);
+  XISetMask (m, XI_KeyPress);
+  XISetMask (m, XI_KeyRelease);
+  XISetMask (m, XI_Motion);
+  XISetMask (m, XI_Enter);
+  XISetMask (m, XI_Leave);
+  XISetMask (m, XI_FocusIn);
+  XISetMask (m, XI_FocusOut);
+  XISetMask (m, XI_PropertyEvent);
+  XISetMask (m, XI_HierarchyChanged);
+  XISetMask (m, XI_DeviceChanged);
+  XISelectEvents (FRAME_X_DISPLAY (f),
+                 FRAME_X_WINDOW (f),
+                 &mask, 1);
+}
+#endif
+
 #ifdef USE_X_TOOLKIT
 
 /* Create and set up the X widget for frame F.  */
@@ -3074,6 +3109,11 @@ x_window (struct frame *f, long window_prompting)
   class_hints.res_class = SSDATA (Vx_resource_class);
   XSetClassHint (FRAME_X_DISPLAY (f), XtWindow (shell_widget), &class_hints);
 
+#ifdef HAVE_XINPUT2
+  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
+    setup_xi_event_mask (f);
+#endif
+
 #ifdef HAVE_X_I18N
   FRAME_XIC (f) = NULL;
   if (use_xim)
@@ -3254,6 +3294,11 @@ x_window (struct frame *f)
     }
 #endif /* HAVE_X_I18N */
 
+#ifdef HAVE_XINPUT2
+  if (FRAME_DISPLAY_INFO (f)->supports_xi2)
+    setup_xi_event_mask (f);
+#endif
+
   validate_x_resource_name ();
 
   class_hints.res_name = SSDATA (Vx_resource_name);
@@ -8038,6 +8083,11 @@ eliminated in future versions of Emacs.  */);
   /* Tell Emacs about this window system.  */
   Fprovide (Qx, Qnil);
 
+#ifdef HAVE_XINPUT2
+  DEFSYM (Qxinput2, "xinput2");
+  Fprovide (Qxinput2, Qnil);
+#endif
+
 #ifdef USE_X_TOOLKIT
   Fprovide (intern_c_string ("x-toolkit"), Qnil);
 #ifdef USE_MOTIF
index ea2cbab2030cb200c90662c22fdb0492f2937199..07255911f9792b320919372855112a8ba0371415 100644 (file)
@@ -105,7 +105,11 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 \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
index 816b6dc5a8b5540b3761ff164bb7940ea7a025f2..63754a2cb61e0613dd82c7b278b38d194b9a3ba6 100644 (file)
@@ -42,6 +42,10 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #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 @@ static bool x_handle_net_wm_state (struct frame *, const XPropertyEvent *);
 static void x_check_fullscreen (struct frame *);
 static void x_check_expected_move (struct frame *, int, int);
 static void x_sync_with_move (struct frame *, int, int, bool);
+#ifndef HAVE_XINPUT2
 static int handle_one_xevent (struct x_display_info *,
                              const XEvent *, int *,
                              struct input_event *);
+#else
+static int handle_one_xevent (struct x_display_info *,
+                             XEvent *, int *,
+                             struct input_event *);
+#endif
 #if ! (defined USE_X_TOOLKIT || defined USE_MOTIF) && defined USE_GTK
 static int x_dispatch_event (XEvent *, Display *);
 #endif
@@ -335,6 +345,224 @@ x_extension_initialize (struct x_display_info *dpyinfo)
   dpyinfo->ext_codes = ext_codes;
 }
 
+
+#ifdef HAVE_XINPUT2
+
+/* Free all XI2 devices on dpyinfo.  */
+static void
+x_free_xi_devices (struct x_display_info *dpyinfo)
+{
+  block_input ();
+
+  if (dpyinfo->num_devices)
+    {
+      for (int i = 0; i < dpyinfo->num_devices; ++i)
+       {
+         XIUngrabDevice (dpyinfo->display, dpyinfo->devices[i].device_id,
+                         CurrentTime);
+         xfree (dpyinfo->devices[i].valuators);
+       }
+
+      xfree (dpyinfo->devices);
+      dpyinfo->devices = NULL;
+      dpyinfo->num_devices = 0;
+    }
+
+  unblock_input ();
+}
+
+/* Setup valuator tracking for XI2 master devices on
+   DPYINFO->display.  */
+
+static void
+x_init_master_valuators (struct x_display_info *dpyinfo)
+{
+  int ndevices;
+  XIDeviceInfo *infos;
+
+  block_input ();
+  x_free_xi_devices (dpyinfo);
+  infos = XIQueryDevice (dpyinfo->display,
+                        XIAllMasterDevices,
+                        &ndevices);
+
+  if (!ndevices)
+    {
+      XIFreeDeviceInfo (infos);
+      unblock_input ();
+      return;
+    }
+
+  int actual_devices = 0;
+  dpyinfo->devices = xmalloc (sizeof *dpyinfo->devices * ndevices);
+
+  for (int i = 0; i < ndevices; ++i)
+    {
+      XIDeviceInfo *device = &infos[i];
+
+      if (device->enabled)
+       {
+         int actual_valuator_count = 0;
+         struct xi_device_t *xi_device = &dpyinfo->devices[actual_devices++];
+         xi_device->device_id = device->deviceid;
+         xi_device->grab = 0;
+         xi_device->valuators =
+           xmalloc (sizeof *xi_device->valuators * device->num_classes);
+
+         for (int c = 0; c < device->num_classes; ++c)
+           {
+             switch (device->classes[c]->type)
+               {
+#ifdef XIScrollClass /* XInput 2.1 */
+               case XIScrollClass:
+                 {
+                   XIScrollClassInfo *info =
+                     (XIScrollClassInfo *) device->classes[c];
+                   struct xi_scroll_valuator_t *valuator =
+                     &xi_device->valuators[actual_valuator_count++];
+
+                   valuator->horizontal
+                     = (info->scroll_type == XIScrollTypeHorizontal);
+                   valuator->invalid_p = true;
+                   valuator->emacs_value = DBL_MIN;
+                   valuator->increment = info->increment;
+                   valuator->number = info->number;
+                   break;
+                 }
+#endif
+               default:
+                 break;
+               }
+           }
+         xi_device->scroll_valuator_count = actual_valuator_count;
+       }
+    }
+
+  dpyinfo->num_devices = actual_devices;
+  XIFreeDeviceInfo (infos);
+  unblock_input ();
+}
+
+/* Return the delta of the scroll valuator VALUATOR_NUMBER under
+   DEVICE_ID in the display DPYINFO with VALUE.  The valuator's
+   valuator will be set to VALUE afterwards.  In case no scroll
+   valuator is found, or if device_id is not known to Emacs, DBL_MAX
+   is returned.  Otherwise, the valuator is returned in
+   VALUATOR_RETURN.  */
+static double
+x_get_scroll_valuator_delta (struct x_display_info *dpyinfo, int device_id,
+                            int valuator_number, double value,
+                            struct xi_scroll_valuator_t **valuator_return)
+{
+  block_input ();
+
+  for (int i = 0; i < dpyinfo->num_devices; ++i)
+    {
+      struct xi_device_t *device = &dpyinfo->devices[i];
+
+      if (device->device_id == device_id)
+       {
+         for (int j = 0; j < device->scroll_valuator_count; ++j)
+           {
+             struct xi_scroll_valuator_t *sv = &device->valuators[j];
+
+             if (sv->number == valuator_number)
+               {
+                 if (sv->invalid_p)
+                   {
+                     sv->current_value = value;
+                     sv->invalid_p = false;
+                     *valuator_return = sv;
+
+                     unblock_input ();
+                     return 0.0;
+                   }
+                 else
+                   {
+                     double delta = (sv->current_value - value) / sv->increment;
+                      sv->current_value = value;
+                     *valuator_return = sv;
+
+                     unblock_input ();
+                     return delta;
+                   }
+               }
+           }
+
+         unblock_input ();
+         return DBL_MAX;
+       }
+    }
+
+  unblock_input ();
+  return DBL_MAX;
+}
+
+static struct xi_device_t *
+xi_device_from_id (struct x_display_info *dpyinfo, int deviceid)
+{
+  for (int i = 0; i < dpyinfo->num_devices; ++i)
+    {
+      if (dpyinfo->devices[i].device_id == deviceid)
+       return &dpyinfo->devices[i];
+    }
+
+  return NULL;
+}
+
+static void
+xi_grab_or_ungrab_device (struct xi_device_t *device,
+                         struct x_display_info *dpyinfo,
+                         Window window)
+{
+  XIEventMask mask;
+  ptrdiff_t l = XIMaskLen (XI_LASTEVENT);
+  unsigned char *m;
+  mask.mask = m = alloca (l);
+  memset (m, 0, l);
+  mask.mask_len = l;
+
+  XISetMask (m, XI_ButtonPress);
+  XISetMask (m, XI_ButtonRelease);
+  XISetMask (m, XI_Motion);
+  XISetMask (m, XI_Enter);
+  XISetMask (m, XI_Leave);
+
+  if (device->grab)
+    {
+      XIGrabDevice (dpyinfo->display, device->device_id, window,
+                   CurrentTime, None, GrabModeAsync,
+                   GrabModeAsync, True, &mask);
+    }
+  else
+    {
+      XIUngrabDevice (dpyinfo->display, device->device_id, CurrentTime);
+    }
+}
+
+static void
+xi_reset_scroll_valuators_for_device_id (struct x_display_info *dpyinfo, int id)
+{
+  struct xi_device_t *device = xi_device_from_id (dpyinfo, id);
+  struct xi_scroll_valuator_t *valuator;
+
+  if (!device)
+    return;
+
+  if (!device->scroll_valuator_count)
+    return;
+
+  for (int i = 0; i < device->scroll_valuator_count; ++i)
+    {
+      valuator = &device->valuators[i];
+      valuator->invalid_p = true;
+    }
+
+  return;
+}
+
+#endif
+
 void
 x_cr_destroy_frame_context (struct frame *f)
 {
@@ -4768,7 +4996,16 @@ static struct frame *
 x_menubar_window_to_frame (struct x_display_info *dpyinfo,
                           const XEvent *event)
 {
-  Window wdesc = event->xany.window;
+  Window wdesc;
+#ifdef HAVE_XINPUT2
+  if (event->type == GenericEvent
+      && dpyinfo->supports_xi2
+      && (event->xcookie.evtype == XI_ButtonPress
+         || event->xcookie.evtype == XI_ButtonRelease))
+    wdesc = ((XIDeviceEvent *) event->xcookie.data)->event;
+  else
+#endif
+    wdesc = event->xany.window;
   Lisp_Object tail, frame;
   struct frame *f;
   struct x_output *x;
@@ -4871,6 +5108,29 @@ x_detect_focus_change (struct x_display_info *dpyinfo, struct frame *frame,
       }
       break;
 
+#ifdef HAVE_XINPUT2
+    case GenericEvent:
+      {
+       XIEvent *xi_event = (XIEvent *) event;
+
+        struct frame *focus_frame = dpyinfo->x_focus_event_frame;
+        int focus_state
+          = focus_frame ? focus_frame->output_data.x->focus_state : 0;
+
+       if (!((xi_event->evtype == XI_Enter
+              || xi_event->evtype == XI_Leave)
+             && (focus_state & FOCUS_EXPLICIT)))
+         x_focus_changed ((xi_event->evtype == XI_Enter
+                           || xi_event->evtype == XI_FocusIn
+                           ? FocusIn : FocusOut),
+                          (xi_event->evtype == XI_Enter
+                           || xi_event->evtype == XI_Leave
+                           ? FOCUS_IMPLICIT : FOCUS_EXPLICIT),
+                          dpyinfo, frame, bufp);
+       break;
+      }
+#endif
+
     case FocusIn:
     case FocusOut:
       /* Ignore transient focus events from hotkeys, window manager
@@ -7975,7 +8235,11 @@ mouse_or_wdesc_frame (struct x_display_info *dpyinfo, int wdesc)
 
 static int
 handle_one_xevent (struct x_display_info *dpyinfo,
+#ifndef HAVE_XINPUT2
                   const XEvent *event,
+#else
+                  XEvent *event,
+#endif
                   int *finish, struct input_event *hold_quit)
 {
   union buffered_input_event inev;
@@ -8001,7 +8265,14 @@ handle_one_xevent (struct x_display_info *dpyinfo,
   inev.ie.kind = NO_EVENT;
   inev.ie.arg = Qnil;
 
-  any = x_any_window_to_frame (dpyinfo, event->xany.window);
+#ifdef HAVE_XINPUT2
+  if (event->type != GenericEvent)
+#endif
+    any = x_any_window_to_frame (dpyinfo, event->xany.window);
+#ifdef HAVE_XINPUT2
+  else
+    any = NULL;
+#endif
 
   if (any && any->wait_event_type == event->type)
     any->wait_event_type = 0; /* Indicates we got it.  */
@@ -8480,6 +8751,10 @@ handle_one_xevent (struct x_display_info *dpyinfo,
       goto OTHER;
 
     case MapNotify:
+#if defined HAVE_XINPUT2 && defined HAVE_GTK3
+      if (xg_is_menu_window (dpyinfo->display, event->xmap.window))
+       popup_activated_flag = 1;
+#endif
       /* We use x_top_window_to_frame because map events can
          come for sub-windows and they don't mean that the
          frame is visible.  */
@@ -9518,6 +9793,785 @@ handle_one_xevent (struct x_display_info *dpyinfo,
     case DestroyNotify:
       xft_settings_event (dpyinfo, event);
       break;
+#ifdef HAVE_XINPUT2
+    case GenericEvent:
+      {
+       if (!dpyinfo->supports_xi2)
+         goto OTHER;
+       if (event->xgeneric.extension != dpyinfo->xi2_opcode)
+         /* Not an XI2 event. */
+         goto OTHER;
+       bool must_free_data = false;
+       XIEvent *xi_event = (XIEvent *) event->xcookie.data;
+       /* Sometimes the event is already claimed by GTK, which
+          will free its data in due course. */
+       if (!xi_event && XGetEventData (dpyinfo->display, &event->xcookie))
+         {
+           must_free_data = true;
+           xi_event = (XIEvent *) event->xcookie.data;
+         }
+
+       XIDeviceEvent *xev = (XIDeviceEvent *) xi_event;
+       XILeaveEvent *leave = (XILeaveEvent *) xi_event;
+       XIEnterEvent *enter = (XIEnterEvent *) xi_event;
+       XIFocusInEvent *focusin = (XIFocusInEvent *) xi_event;
+       XIFocusOutEvent *focusout = (XIFocusOutEvent *) xi_event;
+       XIValuatorState *states;
+       double *values;
+       bool found_valuator = false;
+
+       /* A fake XMotionEvent for x_note_mouse_movement. */
+       XMotionEvent ev;
+       /* A fake XButtonEvent for x_construct_mouse_click. */
+       XButtonEvent bv;
+
+       if (!xi_event)
+         {
+           eassert (!must_free_data);
+           goto OTHER;
+         }
+
+       switch (event->xcookie.evtype)
+         {
+         case XI_FocusIn:
+           any = x_any_window_to_frame (dpyinfo, focusin->event);
+#ifndef USE_GTK
+           /* Some WMs (e.g. Mutter in Gnome Shell), don't unmap
+              minimized/iconified windows; thus, for those WMs we won't get
+              a MapNotify when unminimizing/deconifying.  Check here if we
+              are deiconizing a window (Bug42655).
+
+              But don't do that on GTK since it may cause a plain invisible
+              frame get reported as iconified, compare
+              https://lists.gnu.org/archive/html/emacs-devel/2017-02/msg00133.html.
+              That is fixed above but bites us here again.  */
+           f = any;
+           if (f && FRAME_ICONIFIED_P (f))
+             {
+               SET_FRAME_VISIBLE (f, 1);
+               SET_FRAME_ICONIFIED (f, false);
+               f->output_data.x->has_been_visible = true;
+               inev.ie.kind = DEICONIFY_EVENT;
+               XSETFRAME (inev.ie.frame_or_window, f);
+             }
+#endif /* USE_GTK */
+           x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+           goto XI_OTHER;
+         case XI_FocusOut:
+           any = x_any_window_to_frame (dpyinfo, focusout->event);
+           x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+           goto XI_OTHER;
+         case XI_Enter:
+           any = x_any_window_to_frame (dpyinfo, enter->event);
+           ev.x = lrint (enter->event_x);
+           ev.y = lrint (enter->event_y);
+           ev.window = leave->event;
+
+           x_display_set_last_user_time (dpyinfo, xi_event->time);
+           x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+           xi_reset_scroll_valuators_for_device_id (dpyinfo, enter->deviceid);
+           f = any;
+
+           if (f && x_mouse_click_focus_ignore_position)
+             ignore_next_mouse_click_timeout = xi_event->time + 200;
+
+           /* EnterNotify counts as mouse movement,
+              so update things that depend on mouse position.  */
+           if (f && !f->output_data.x->hourglass_p)
+             x_note_mouse_movement (f, &ev);
+#ifdef USE_GTK
+           /* We may get an EnterNotify on the buttons in the toolbar.  In that
+              case we moved out of any highlighted area and need to note this.  */
+           if (!f && dpyinfo->last_mouse_glyph_frame)
+             x_note_mouse_movement (dpyinfo->last_mouse_glyph_frame, &ev);
+#endif
+           goto XI_OTHER;
+         case XI_Leave:
+           ev.x = lrint (leave->event_x);
+           ev.y = lrint (leave->event_y);
+           ev.window = leave->event;
+           any = x_any_window_to_frame (dpyinfo, leave->event);
+
+           x_display_set_last_user_time (dpyinfo, xi_event->time);
+           x_detect_focus_change (dpyinfo, any, event, &inev.ie);
+           xi_reset_scroll_valuators_for_device_id (dpyinfo, leave->deviceid);
+
+           f = x_top_window_to_frame (dpyinfo, leave->event);
+           if (f)
+             {
+               if (f == hlinfo->mouse_face_mouse_frame)
+                 {
+                   /* If we move outside the frame, then we're
+                      certainly no longer on any text in the frame.  */
+                   clear_mouse_face (hlinfo);
+                   hlinfo->mouse_face_mouse_frame = 0;
+                 }
+
+               /* Generate a nil HELP_EVENT to cancel a help-echo.
+                  Do it only if there's something to cancel.
+                  Otherwise, the startup message is cleared when
+                  the mouse leaves the frame.  */
+               if (any_help_event_p)
+                 do_help = -1;
+             }
+#ifdef USE_GTK
+           /* See comment in EnterNotify above */
+           else if (dpyinfo->last_mouse_glyph_frame)
+             x_note_mouse_movement (dpyinfo->last_mouse_glyph_frame, &ev);
+#endif
+           goto XI_OTHER;
+         case XI_Motion:
+           /* First test if there is some kind of scroll event
+              here! */
+           states = &xev->valuators;
+           values = states->values;
+
+           x_display_set_last_user_time (dpyinfo, xi_event->time);
+
+           for (int i = 0; i < states->mask_len * 8; i++)
+             {
+               if (XIMaskIsSet (states->mask, i))
+                 {
+                   block_input ();
+
+                   struct xi_scroll_valuator_t *val;
+                   double delta;
+
+                   delta = x_get_scroll_valuator_delta (dpyinfo, xev->deviceid,
+                                                        i, *values, &val);
+
+                   if (delta != DBL_MAX)
+                     {
+                       f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+                       found_valuator = true;
+
+                       if (signbit (delta) != signbit (val->emacs_value))
+                         val->emacs_value = 0;
+
+                       val->emacs_value += delta;
+
+                       if (!f)
+                         {
+                           f = x_any_window_to_frame (dpyinfo, xev->event);
+
+                           if (!f)
+                             {
+                               unblock_input ();
+                               goto XI_OTHER;
+                             }
+                         }
+
+                       bool s = signbit (val->emacs_value);
+                       inev.ie.kind = (val->horizontal
+                                       ? HORIZ_WHEEL_EVENT
+                                       : WHEEL_EVENT);
+                       inev.ie.timestamp = xev->time;
+
+                       XSETINT (inev.ie.x, lrint (xev->event_x));
+                       XSETINT (inev.ie.y, lrint (xev->event_y));
+                       XSETFRAME (inev.ie.frame_or_window, f);
+
+                       inev.ie.modifiers = !s ? up_modifier : down_modifier;
+                       inev.ie.modifiers
+                         |= x_x_to_emacs_modifiers (dpyinfo,
+                                                    xev->mods.effective);
+                       inev.ie.arg = Qnil;
+
+                       kbd_buffer_store_event_hold (&inev.ie, hold_quit);
+
+                       val->emacs_value = 0;
+                     }
+                   unblock_input ();
+                   values++;
+                 }
+
+               inev.ie.kind = NO_EVENT;
+             }
+
+           if (found_valuator)
+             goto XI_OTHER;
+
+           ev.x = lrint (xev->event_x);
+           ev.y = lrint (xev->event_y);
+           ev.window = xev->event;
+
+           previous_help_echo_string = help_echo_string;
+           help_echo_string = Qnil;
+
+           if (hlinfo->mouse_face_hidden)
+             {
+               hlinfo->mouse_face_hidden = false;
+               clear_mouse_face (hlinfo);
+             }
+
+           f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+
+#ifdef USE_GTK
+           if (f && xg_event_is_for_scrollbar (f, event))
+             f = 0;
+#endif
+           if (f)
+             {
+               /* Maybe generate a SELECT_WINDOW_EVENT for
+                  `mouse-autoselect-window' but don't let popup menus
+                  interfere with this (Bug#1261).  */
+               if (!NILP (Vmouse_autoselect_window)
+                   && !popup_activated ()
+                   /* Don't switch if we're currently in the minibuffer.
+                      This tries to work around problems where the
+                      minibuffer gets unselected unexpectedly, and where
+                      you then have to move your mouse all the way down to
+                      the minibuffer to select it.  */
+                   && !MINI_WINDOW_P (XWINDOW (selected_window))
+                   /* With `focus-follows-mouse' non-nil create an event
+                      also when the target window is on another frame.  */
+                   && (f == XFRAME (selected_frame)
+                       || !NILP (focus_follows_mouse)))
+                 {
+                   static Lisp_Object last_mouse_window;
+                   Lisp_Object window = window_from_coordinates (f, ev.x, ev.y, 0, false, false);
+
+                   /* A window will be autoselected only when it is not
+                      selected now and the last mouse movement event was
+                      not in it.  The remainder of the code is a bit vague
+                      wrt what a "window" is.  For immediate autoselection,
+                      the window is usually the entire window but for GTK
+                      where the scroll bars don't count.  For delayed
+                      autoselection the window is usually the window's text
+                      area including the margins.  */
+                   if (WINDOWP (window)
+                       && !EQ (window, last_mouse_window)
+                       && !EQ (window, selected_window))
+                     {
+                       inev.ie.kind = SELECT_WINDOW_EVENT;
+                       inev.ie.frame_or_window = window;
+                     }
+
+                   /* Remember the last window where we saw the mouse.  */
+                   last_mouse_window = window;
+                 }
+
+               if (!x_note_mouse_movement (f, &ev))
+                 help_echo_string = previous_help_echo_string;
+             }
+           else
+             {
+#ifndef USE_TOOLKIT_SCROLL_BARS
+               struct scroll_bar *bar
+                 = x_window_to_scroll_bar (xi_event->display, xev->event, 2);
+
+               if (bar)
+                 x_scroll_bar_note_movement (bar, &ev);
+#endif /* USE_TOOLKIT_SCROLL_BARS */
+
+               /* If we move outside the frame, then we're
+                  certainly no longer on any text in the frame.  */
+               clear_mouse_face (hlinfo);
+             }
+
+           /* If the contents of the global variable help_echo_string
+              has changed, generate a HELP_EVENT.  */
+           if (!NILP (help_echo_string)
+               || !NILP (previous_help_echo_string))
+             do_help = 1;
+           goto XI_OTHER;
+         case XI_ButtonRelease:
+         case XI_ButtonPress:
+           {
+             /* If we decide we want to generate an event to be seen
+                by the rest of Emacs, we put it here.  */
+             Lisp_Object tab_bar_arg = Qnil;
+             bool tab_bar_p = false;
+             bool tool_bar_p = false;
+             struct xi_device_t *device;
+
+             /* Ignore emulated scroll events when XI2 native
+                scroll events are present.  */
+             if (dpyinfo->xi2_version >= 1 && xev->detail >= 4
+                 && xev->detail <= 8)
+               goto XI_OTHER;
+
+             device = xi_device_from_id (dpyinfo, xev->deviceid);
+
+             bv.button = xev->detail;
+             bv.type = xev->evtype == XI_ButtonPress ? ButtonPress : ButtonRelease;
+             bv.x = lrint (xev->event_x);
+             bv.y = lrint (xev->event_y);
+             bv.window = xev->event;
+             bv.state = xev->mods.base
+               | xev->mods.effective
+               | xev->mods.latched
+               | xev->mods.locked;
+
+             memset (&compose_status, 0, sizeof (compose_status));
+             dpyinfo->last_mouse_glyph_frame = NULL;
+             x_display_set_last_user_time (dpyinfo, xev->time);
+
+             f = mouse_or_wdesc_frame (dpyinfo, xev->event);
+
+             if (f && xev->evtype == XI_ButtonPress
+                 && !popup_activated ()
+                 && !x_window_to_scroll_bar (xev->display, xev->event, 2)
+                 && !FRAME_NO_ACCEPT_FOCUS (f))
+               {
+                 /* When clicking into a child frame or when clicking
+                    into a parent frame with the child frame selected and
+                    `no-accept-focus' is not set, select the clicked
+                    frame.  */
+                 struct frame *hf = dpyinfo->highlight_frame;
+
+                 if (FRAME_PARENT_FRAME (f) || (hf && frame_ancestor_p (f, hf)))
+                   {
+                     block_input ();
+                     XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f),
+                                     RevertToParent, CurrentTime);
+                     if (FRAME_PARENT_FRAME (f))
+                       XRaiseWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f));
+                     unblock_input ();
+                   }
+               }
+
+#ifdef USE_GTK
+             if (f && xg_event_is_for_scrollbar (f, event))
+               f = 0;
+#endif
+
+             if (f)
+               {
+                 /* Is this in the tab-bar?  */
+                 if (WINDOWP (f->tab_bar_window)
+                     && WINDOW_TOTAL_LINES (XWINDOW (f->tab_bar_window)))
+                   {
+                     Lisp_Object window;
+                     int x = bv.x;
+                     int y = bv.y;
+
+                     window = window_from_coordinates (f, x, y, 0, true, true);
+                     tab_bar_p = EQ (window, f->tab_bar_window);
+
+                     if (tab_bar_p)
+                       tab_bar_arg = handle_tab_bar_click
+                         (f, x, y, xev->evtype == XI_ButtonPress,
+                          x_x_to_emacs_modifiers (dpyinfo, bv.state));
+                   }
+
+#if ! defined (USE_GTK)
+                 /* Is this in the tool-bar?  */
+                 if (WINDOWP (f->tool_bar_window)
+                     && WINDOW_TOTAL_LINES (XWINDOW (f->tool_bar_window)))
+                   {
+                     Lisp_Object window;
+                     int x = bv.x;
+                     int y = bv.y;
+
+                     window = window_from_coordinates (f, x, y, 0, true, true);
+                     tool_bar_p = EQ (window, f->tool_bar_window);
+
+                     if (tool_bar_p && xev->detail < 4)
+                       handle_tool_bar_click
+                         (f, x, y, xev->evtype == XI_ButtonPress,
+                          x_x_to_emacs_modifiers (dpyinfo, bv.state));
+                   }
+#endif /* !USE_GTK */
+
+                 if (!(tab_bar_p && NILP (tab_bar_arg)) && !tool_bar_p)
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+                   if (! popup_activated ())
+#endif
+                     {
+                       if (ignore_next_mouse_click_timeout)
+                         {
+                           if (xev->evtype == XI_ButtonPress
+                               && xev->time > ignore_next_mouse_click_timeout)
+                             {
+                               ignore_next_mouse_click_timeout = 0;
+                               x_construct_mouse_click (&inev.ie, &bv, f);
+                             }
+                           if (xev->evtype == XI_ButtonRelease)
+                             ignore_next_mouse_click_timeout = 0;
+                         }
+                       else
+                         x_construct_mouse_click (&inev.ie, &bv, f);
+
+                       if (!NILP (tab_bar_arg))
+                         inev.ie.arg = tab_bar_arg;
+                     }
+                 if (FRAME_X_EMBEDDED_P (f))
+                   xembed_send_message (f, xev->time,
+                                        XEMBED_REQUEST_FOCUS, 0, 0, 0);
+               }
+
+             if (xev->evtype == XI_ButtonPress)
+               {
+                 dpyinfo->grabbed |= (1 << xev->detail);
+                 device->grab |= (1 << xev->detail);
+                 dpyinfo->last_mouse_frame = f;
+                 if (f && !tab_bar_p)
+                   f->last_tab_bar_item = -1;
+#if ! defined (USE_GTK)
+                 if (f && !tool_bar_p)
+                   f->last_tool_bar_item = -1;
+#endif /* not USE_GTK */
+
+               }
+             else
+               {
+                 dpyinfo->grabbed &= ~(1 << xev->detail);
+                 device->grab &= ~(1 << xev->detail);
+               }
+
+             xi_grab_or_ungrab_device (device, dpyinfo, xev->event);
+
+             if (f)
+               f->mouse_moved = false;
+
+#if defined (USE_GTK)
+             /* No Xt toolkit currently available has support for XI2.
+                So the code here assumes use of GTK.  */
+             f = x_menubar_window_to_frame (dpyinfo, event);
+             if (f /* Gtk+ menus only react to the first three buttons. */
+                 && xev->detail < 3)
+               {
+                 /* What is done with Core Input ButtonPressed is not
+                    possible here, because GenericEvents cannot be saved.  */
+                 bool was_waiting_for_input = waiting_for_input;
+                 /* This hack was adopted from the NS port.  Whether
+                    or not it is actually safe is a different story
+                    altogether.  */
+                 if (waiting_for_input)
+                   waiting_for_input = 0;
+                 set_frame_menubar (f, true);
+                 waiting_for_input = was_waiting_for_input;
+               }
+#endif
+             goto XI_OTHER;
+           }
+         case XI_KeyPress:
+           {
+             int state = xev->mods.base
+               | xev->mods.effective
+               | xev->mods.latched
+               | xev->mods.locked;
+             Lisp_Object c;
+#ifdef HAVE_XKB
+             unsigned int mods_rtrn;
+#endif
+             int keycode = xev->detail;
+             KeySym keysym;
+             char copy_buffer[81];
+             char *copy_bufptr = copy_buffer;
+             unsigned char *copy_ubufptr;
+#ifdef HAVE_XKB
+             int copy_bufsiz = sizeof (copy_buffer);
+#endif
+             ptrdiff_t i;
+             int nchars, len;
+
+#ifdef HAVE_XKB
+             if (dpyinfo->xkb_desc)
+               {
+                 if (!XkbTranslateKeyCode (dpyinfo->xkb_desc, keycode,
+                                           state, &mods_rtrn, &keysym))
+                   goto XI_OTHER;
+               }
+             else
+               {
+#endif
+                 int keysyms_per_keycode_return;
+                 KeySym *ksms = XGetKeyboardMapping (dpyinfo->display, keycode, 1,
+                                                     &keysyms_per_keycode_return);
+                 if (!(keysym = ksms[0]))
+                   {
+                     XFree (ksms);
+                     goto XI_OTHER;
+                   }
+                 XFree (ksms);
+#ifdef HAVE_XKB
+               }
+#endif
+
+             if (keysym == NoSymbol)
+               goto XI_OTHER;
+
+             x_display_set_last_user_time (dpyinfo, xev->time);
+             ignore_next_mouse_click_timeout = 0;
+
+#if defined (USE_X_TOOLKIT) || defined (USE_GTK)
+             /* Dispatch XI_KeyPress events when in menu.  */
+             if (popup_activated ())
+               goto XI_OTHER;
+#endif
+
+             f = x_any_window_to_frame (dpyinfo, xev->event);
+
+             /* If mouse-highlight is an integer, input clears out
+                mouse highlighting.  */
+             if (!hlinfo->mouse_face_hidden && FIXNUMP (Vmouse_highlight)
+                 && (f == 0
+#if ! defined (USE_GTK)
+                     || !EQ (f->tool_bar_window, hlinfo->mouse_face_window)
+#endif
+                     || !EQ (f->tab_bar_window, hlinfo->mouse_face_window))
+                 )
+               {
+                 clear_mouse_face (hlinfo);
+                 hlinfo->mouse_face_hidden = true;
+               }
+
+             if (f != 0)
+               {
+#ifdef USE_GTK
+                 /* Don't pass keys to GTK.  A Tab will shift focus to the
+                    tool bar in GTK 2.4.  Keys will still go to menus and
+                    dialogs because in that case popup_activated is nonzero
+                    (see above).  */
+                 *finish = X_EVENT_DROP;
+#endif
+                 /* If not using XIM/XIC, and a compose sequence is in progress,
+                    we break here.  Otherwise, chars_matched is always 0.  */
+                 if (compose_status.chars_matched > 0 && nbytes == 0)
+                   goto XI_OTHER;
+
+                 memset (&compose_status, 0, sizeof (compose_status));
+
+                 XSETFRAME (inev.ie.frame_or_window, f);
+                 inev.ie.modifiers
+                   = x_x_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), state);
+                 inev.ie.timestamp = xev->time;
+
+                 /* First deal with keysyms which have defined
+                    translations to characters.  */
+                 if (keysym >= 32 && keysym < 128)
+                   /* Avoid explicitly decoding each ASCII character.  */
+                   {
+                     inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+                     inev.ie.code = keysym;
+
+                     goto xi_done_keysym;
+                   }
+
+                 /* Keysyms directly mapped to Unicode characters.  */
+                 if (keysym >= 0x01000000 && keysym <= 0x0110FFFF)
+                   {
+                     if (keysym < 0x01000080)
+                       inev.ie.kind = ASCII_KEYSTROKE_EVENT;
+                     else
+                       inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT;
+                     inev.ie.code = keysym & 0xFFFFFF;
+                     goto xi_done_keysym;
+                   }
+
+                 /* Now non-ASCII.  */
+                 if (HASH_TABLE_P (Vx_keysym_table)
+                     && (c = Fgethash (make_fixnum (keysym),
+                                       Vx_keysym_table,
+                                       Qnil),
+                         FIXNATP (c)))
+                   {
+                     inev.ie.kind = (SINGLE_BYTE_CHAR_P (XFIXNAT (c))
+                                     ? ASCII_KEYSTROKE_EVENT
+                                     : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+                     inev.ie.code = XFIXNAT (c);
+                     goto xi_done_keysym;
+                   }
+
+                 /* Random non-modifier sorts of keysyms.  */
+                 if (((keysym >= XK_BackSpace && keysym <= XK_Escape)
+                      || keysym == XK_Delete
+#ifdef XK_ISO_Left_Tab
+                      || (keysym >= XK_ISO_Left_Tab
+                          && keysym <= XK_ISO_Enter)
+#endif
+                      || IsCursorKey (keysym) /* 0xff50 <= x < 0xff60 */
+                      || IsMiscFunctionKey (keysym) /* 0xff60 <= x < VARIES */
+#ifdef HPUX
+                      /* This recognizes the "extended function
+                         keys".  It seems there's no cleaner way.
+                         Test IsModifierKey to avoid handling
+                         mode_switch incorrectly.  */
+                      || (XK_Select <= keysym && keysym < XK_KP_Space)
+#endif
+#ifdef XK_dead_circumflex
+                      || keysym == XK_dead_circumflex
+#endif
+#ifdef XK_dead_grave
+                      || keysym == XK_dead_grave
+#endif
+#ifdef XK_dead_tilde
+                      || keysym == XK_dead_tilde
+#endif
+#ifdef XK_dead_diaeresis
+                      || keysym == XK_dead_diaeresis
+#endif
+#ifdef XK_dead_macron
+                      || keysym == XK_dead_macron
+#endif
+#ifdef XK_dead_degree
+                      || keysym == XK_dead_degree
+#endif
+#ifdef XK_dead_acute
+                      || keysym == XK_dead_acute
+#endif
+#ifdef XK_dead_cedilla
+                      || keysym == XK_dead_cedilla
+#endif
+#ifdef XK_dead_breve
+                      || keysym == XK_dead_breve
+#endif
+#ifdef XK_dead_ogonek
+                      || keysym == XK_dead_ogonek
+#endif
+#ifdef XK_dead_caron
+                      || keysym == XK_dead_caron
+#endif
+#ifdef XK_dead_doubleacute
+                      || keysym == XK_dead_doubleacute
+#endif
+#ifdef XK_dead_abovedot
+                      || keysym == XK_dead_abovedot
+#endif
+                      || IsKeypadKey (keysym) /* 0xff80 <= x < 0xffbe */
+                      || IsFunctionKey (keysym) /* 0xffbe <= x < 0xffe1 */
+                      /* Any "vendor-specific" key is ok.  */
+                      || (keysym & (1 << 28))
+                      || (keysym != NoSymbol && nbytes == 0))
+                     && ! (IsModifierKey (keysym)
+                           /* The symbols from XK_ISO_Lock
+                              to XK_ISO_Last_Group_Lock
+                              don't have real modifiers but
+                              should be treated similarly to
+                              Mode_switch by Emacs. */
+#if defined XK_ISO_Lock && defined XK_ISO_Last_Group_Lock
+                           || (XK_ISO_Lock <= keysym
+                               && keysym <= XK_ISO_Last_Group_Lock)
+#endif
+                           ))
+                   {
+                     STORE_KEYSYM_FOR_DEBUG (keysym);
+                     /* make_lispy_event will convert this to a symbolic
+                        key.  */
+                     inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT;
+                     inev.ie.code = keysym;
+                     goto xi_done_keysym;
+                   }
+
+#ifdef HAVE_XKB
+                 int overflow = 0;
+                 KeySym sym = keysym;
+
+                 if (dpyinfo->xkb_desc)
+                   {
+                     if (!(nbytes = XkbTranslateKeySym (dpyinfo->display, &sym,
+                                                        state & ~mods_rtrn, copy_bufptr,
+                                                        copy_bufsiz, &overflow)))
+                       goto XI_OTHER;
+                   }
+                 else
+#else
+                   {
+                     block_input ();
+                     char *str = XKeysymToString (keysym);
+                     if (!str)
+                       {
+                         unblock_input ();
+                         goto XI_OTHER;
+                       }
+                     nbytes = strlen (str) + 1;
+                     copy_bufptr = alloca (nbytes);
+                     strcpy (copy_bufptr, str);
+                     unblock_input ();
+                   }
+#endif
+#ifdef HAVE_XKB
+                 if (overflow)
+                   {
+                     overflow = 0;
+                     copy_bufptr = alloca (copy_bufsiz + overflow);
+                     keysym = sym;
+                     if (!(nbytes = XkbTranslateKeySym (dpyinfo->display, &sym,
+                                                        state & ~mods_rtrn, copy_bufptr,
+                                                        copy_bufsiz + overflow, &overflow)))
+                       goto XI_OTHER;
+
+                     if (overflow)
+                       goto XI_OTHER;
+                   }
+#endif
+
+                 for (i = 0, nchars = 0; i < nbytes; i++)
+                   {
+                     if (ASCII_CHAR_P (copy_bufptr[i]))
+                       nchars++;
+                     STORE_KEYSYM_FOR_DEBUG (copy_bufptr[i]);
+                   }
+
+                 if (nchars < nbytes)
+                   {
+                     /* Decode the input data.  */
+
+                     setup_coding_system (Vlocale_coding_system, &coding);
+                     coding.src_multibyte = false;
+                     coding.dst_multibyte = true;
+                     /* The input is converted to events, thus we can't
+                        handle composition.  Anyway, there's no XIM that
+                        gives us composition information.  */
+                     coding.common_flags &= ~CODING_ANNOTATION_MASK;
+
+                     SAFE_NALLOCA (coding.destination, MAX_MULTIBYTE_LENGTH,
+                                   nbytes);
+                     coding.dst_bytes = MAX_MULTIBYTE_LENGTH * nbytes;
+                     coding.mode |= CODING_MODE_LAST_BLOCK;
+                     decode_coding_c_string (&coding, (unsigned char *) copy_bufptr,
+                                             nbytes, Qnil);
+                     nbytes = coding.produced;
+                     nchars = coding.produced_char;
+                     copy_bufptr = (char *) coding.destination;
+                   }
+
+                 copy_ubufptr = (unsigned char *) copy_bufptr;
+
+                 /* Convert the input data to a sequence of
+                    character events.  */
+                 for (i = 0; i < nbytes; i += len)
+                   {
+                     int ch;
+                     if (nchars == nbytes)
+                       ch = copy_ubufptr[i], len = 1;
+                     else
+                       ch = string_char_and_length (copy_ubufptr + i, &len);
+                     inev.ie.kind = (SINGLE_BYTE_CHAR_P (ch)
+                                     ? ASCII_KEYSTROKE_EVENT
+                                     : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+                     inev.ie.code = ch;
+                     kbd_buffer_store_buffered_event (&inev, hold_quit);
+                   }
+
+                 inev.ie.kind = NO_EVENT;
+                 goto xi_done_keysym;
+               }
+             goto XI_OTHER;
+           }
+         case XI_KeyRelease:
+           x_display_set_last_user_time (dpyinfo, xev->time);
+           goto XI_OTHER;
+         case XI_PropertyEvent:
+         case XI_HierarchyChanged:
+         case XI_DeviceChanged:
+           x_init_master_valuators (dpyinfo);
+           goto XI_OTHER;
+         default:
+           goto XI_OTHER;
+         }
+      xi_done_keysym:
+       if (must_free_data)
+         XFreeEventData (dpyinfo->display, &event->xcookie);
+       goto done_keysym;
+      XI_OTHER:
+       if (must_free_data)
+         XFreeEventData (dpyinfo->display, &event->xcookie);
+       goto OTHER;
+      }
+#endif
 
     default:
     OTHER:
@@ -13199,6 +14253,40 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name)
     dpyinfo->supports_xdbe = true;
 #endif
 
+#ifdef HAVE_XINPUT2
+  dpyinfo->supports_xi2 = false;
+  int rc;
+  int major = 2;
+#ifdef XI_BarrierHit /* XInput 2.3 */
+  int minor = 3;
+#elif defined XI_TouchBegin /* XInput 2.2 */
+  int minor = 2;
+#elif defined XIScrollClass /* XInput 1.1 */
+  int minor = 1;
+#else /* Some old version of XI2 we're not interested in. */
+  int minor = 0;
+#endif
+  int fer, fee;
+
+  if (XQueryExtension (dpyinfo->display, "XInputExtension",
+                      &dpyinfo->xi2_opcode, &fer, &fee))
+    {
+      rc = XIQueryVersion (dpyinfo->display, &major, &minor);
+      if (rc == Success)
+       {
+         dpyinfo->supports_xi2 = true;
+         x_init_master_valuators (dpyinfo);
+       }
+    }
+  dpyinfo->xi2_version = minor;
+#endif
+
+#ifdef HAVE_XKB
+  dpyinfo->xkb_desc = XkbGetMap (dpyinfo->display,
+                                XkbAllComponentsMask,
+                                XkbUseCoreKbd);
+#endif
+
 #if defined USE_CAIRO || defined HAVE_XFT
   {
     /* If we are using Xft, the following precautions should be made:
@@ -13631,6 +14719,14 @@ x_delete_terminal (struct terminal *terminal)
       XrmDestroyDatabase (dpyinfo->rdb);
 #endif
 
+#ifdef HAVE_XKB
+      if (dpyinfo->xkb_desc)
+       XkbFreeKeyboard (dpyinfo->xkb_desc, XkbAllComponentsMask, True);
+#endif
+#ifdef HAVE_XINPUT2
+      if (dpyinfo->supports_xi2)
+       x_free_xi_devices (dpyinfo);
+#endif
 #ifdef USE_GTK
       xg_display_close (dpyinfo->display);
 #else
@@ -13790,9 +14886,12 @@ x_initialize (void)
 void
 init_xterm (void)
 {
-  /* Emacs can handle only core input events, so make sure
-     Gtk doesn't use Xinput or Xinput2 extensions.  */
+#ifndef HAVE_XINPUT2
+  /* Emacs can handle only core input events when built without XI2
+     support, so make sure Gtk doesn't use Xinput or Xinput2
+     extensions.  */
   xputenv ("GDK_CORE_DEVICE_EVENTS=1");
+#endif
 }
 #endif
 
index 9d9534dd629b157a7c2038d97dbe4afa935bb01f..7abe168bc6f5f9e192f97f58bb2e28b59455c125 100644 (file)
@@ -88,6 +88,10 @@ typedef GtkWidget *xt_or_gtk_widget;
 #include <X11/Xlib-xcb.h>
 #endif
 
+#ifdef HAVE_XKB
+#include <X11/XKBlib.h>
+#endif
+
 #include "dispextern.h"
 #include "termhooks.h"
 
@@ -163,6 +167,28 @@ struct color_name_cache_entry
   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 @@ struct x_display_info
 #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 @@ struct x_display_info
 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;