From: Po Lu <luangruo@yahoo.com>
To: emacs-devel@gnu.org
Subject: Re: XInput 2 support
Date: Sat, 16 Oct 2021 19:38:03 +0800 [thread overview]
Message-ID: <87wnmddwtg.fsf@yahoo.com> (raw)
In-Reply-To: <87h7dhfm5x.fsf@yahoo.com> (Po Lu's message of "Sat, 16 Oct 2021 15:45:14 +0800")
[-- Attachment #1: Type: text/plain, Size: 191 bytes --]
Po Lu <luangruo@yahoo.com> writes:
> Okay, I asked someone to test and he reported a problem with the menu
> bar. This updated patch should fix it.
That didn't work either. This should.
[-- 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: 35061 bytes --]
From 7aa00bdcd92af89e5ff9f3c562e06e0ab40dc58a 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/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
(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
---
configure.ac | 22 ++
src/Makefile.in | 7 +-
src/gtkutil.c | 23 +-
src/xfns.c | 56 ++++
src/xterm.c | 776 +++++++++++++++++++++++++++++++++++++++++++++++-
src/xterm.h | 14 +
6 files changed, 891 insertions(+), 7 deletions(-)
diff --git a/configure.ac b/configure.ac
index 9ab0314428..0af8b65dd7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -485,6 +485,7 @@ AC_DEFUN
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([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)])],
@@ -4203,6 +4204,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
@@ -5959,6 +5980,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 6d75e3537a..2b3e1f37e0 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -256,6 +256,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@
@@ -372,7 +375,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) $(LCMS2_CFLAGS) \
+ $(XINPUT_CFLAGS) $(WEBKIT_CFLAGS) $(LCMS2_CFLAGS) \
$(SETTINGS_CFLAGS) $(FREETYPE_CFLAGS) $(FONTCONFIG_CFLAGS) \
$(HARFBUZZ_CFLAGS) $(LIBOTF_CFLAGS) $(M17N_FLT_CFLAGS) $(DEPFLAGS) \
$(LIBSYSTEMD_CFLAGS) $(JSON_CFLAGS) \
@@ -522,7 +525,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 e87845caf7..0304f02434 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
@@ -3575,6 +3579,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
@@ -3583,7 +3599,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;
diff --git a/src/xfns.c b/src/xfns.c
index 785ae3baca..4fef8005a0 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,32 @@ 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);
+ 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 +3284,32 @@ 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);
+ 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/xterm.c b/src/xterm.c
index 961c61c245..0435ad341c 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
@@ -4665,7 +4675,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;
+#endif
+ else
+ wdesc = event->xany.window;
Lisp_Object tail, frame;
struct frame *f;
struct x_output *x;
@@ -4768,6 +4787,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
@@ -7872,7 +7914,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;
@@ -7898,7 +7944,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. */
@@ -9343,6 +9396,685 @@ 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;
+
+ /* 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:
+ ev.x = lrint (xev->event_x);
+ ev.y = lrint (xev->event_y);
+ ev.window = xev->event;
+
+ x_display_set_last_user_time (dpyinfo, xi_event->time);
+ 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;
+
+ 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);
+ 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);
+
+ 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)
+ {
+ /* 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:
+
+ 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:
@@ -13013,6 +13745,35 @@ #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;
+#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;
+ }
+ 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:
@@ -13445,6 +14206,10 @@ 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 USE_GTK
xg_display_close (dpyinfo->display);
#else
@@ -13604,9 +14369,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 de6ea50385..6d5f28c76e 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"
@@ -474,6 +478,16 @@ #define MAX_CLIP_RECTS 2
#ifdef HAVE_XDBE
bool supports_xdbe;
#endif
+
+#ifdef HAVE_XINPUT2
+ bool supports_xi2;
+ int xi2_version;
+ int xi2_opcode;
+#endif
+
+#ifdef HAVE_XKB
+ XkbDescPtr xkb_desc;
+#endif
};
#ifdef HAVE_X_I18N
--
2.31.1
next prev parent reply other threads:[~2021-10-16 11:38 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <87r1clh6nq.fsf.ref@yahoo.com>
2021-10-16 5:37 ` XInput 2 support Po Lu
2021-10-16 6:25 ` Eli Zaretskii
2021-10-16 6:29 ` Po Lu
2021-10-16 6:45 ` Po Lu
2021-10-16 7:45 ` Po Lu
2021-10-16 11:38 ` Po Lu [this message]
2021-10-17 10:09 ` Alan Third
2021-10-17 12:10 ` Po Lu via Emacs development discussions.
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87wnmddwtg.fsf@yahoo.com \
--to=luangruo@yahoo.com \
--cc=emacs-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.