From 16b8948d79e27a37d223eb171c21e6b78d8b5a7a Mon Sep 17 00:00:00 2001
From: Po Lu <luangruo@yahoo.com>
Date: Mon, 8 Aug 2022 11:44:53 +0800
Subject: [PATCH] Improve handling of pointer focus under the input extension

* src/xfns.c (x_relative_mouse_position)
(Fx_mouse_absolute_pixel_position, compute_tip_xy): Use
x_query_pointer instead of XQueryPointer.
* src/xterm.c (xi_populate_device_from_info): Set new attachment
field.
(xi_handle_focus_change): Set client pointer.
(xi_focus_handle_for_device, xi_handle_interaction): Use
attached keyboard device to handle focus.
(x_query_pointer): New function.
(XTmouse_position, x_scroll_bar_report_motion)
(x_horizontal_scroll_bar_report_motion, handle_one_xevent): Use
x_query_pointer instead of XQueryPointer.
(x_term_init): Initialize client pointer device.
* src/xterm.h (struct xi_device_t): New field `attachment'.
(struct x_display_info): New field `client_pointer_device'.
---
 src/xfns.c  |  38 ++++++------
 src/xterm.c | 171 ++++++++++++++++++++++++++++++++++++++++++----------
 src/xterm.h |  31 +++++++++-
 3 files changed, 186 insertions(+), 54 deletions(-)

diff --git a/src/xfns.c b/src/xfns.c
index 672097c0d80..2845ecca6a9 100644
--- a/src/xfns.c
+++ b/src/xfns.c
@@ -609,24 +609,24 @@ x_relative_mouse_position (struct frame *f, int *x, int *y)
 
   block_input ();
 
-  XQueryPointer (FRAME_X_DISPLAY (f),
-                 FRAME_DISPLAY_INFO (f)->root_window,
+  x_query_pointer (FRAME_X_DISPLAY (f),
+		   FRAME_DISPLAY_INFO (f)->root_window,
 
-                 /* The root window which contains the pointer.  */
-                 &root,
+		   /* The root window which contains the pointer.  */
+		   &root,
 
-                 /* Window pointer is on, not used  */
-                 &dummy_window,
+		   /* Window pointer is on, not used  */
+		   &dummy_window,
 
-                 /* The position on that root window.  */
-                 x, y,
+		   /* The position on that root window.  */
+		   x, y,
 
-                 /* x/y in dummy_window coordinates, not used.  */
-                 &dummy, &dummy,
+		   /* x/y in dummy_window coordinates, not used.  */
+		   &dummy, &dummy,
 
-                 /* Modifier keys and pointer buttons, about which
-                    we don't care.  */
-                 (unsigned int *) &dummy);
+		   /* Modifier keys and pointer buttons, about which
+		      we don't care.  */
+		   (unsigned int *) &dummy);
 
   XTranslateCoordinates (FRAME_X_DISPLAY (f),
 
@@ -6823,10 +6823,10 @@ selected frame's display.  */)
     return Qnil;
 
   block_input ();
-  XQueryPointer (FRAME_X_DISPLAY (f),
-		 FRAME_DISPLAY_INFO (f)->root_window,
-                 &root, &dummy_window, &x, &y, &dummy, &dummy,
-                 (unsigned int *) &dummy);
+  x_query_pointer (FRAME_X_DISPLAY (f),
+		   FRAME_DISPLAY_INFO (f)->root_window,
+		   &root, &dummy_window, &x, &y, &dummy, &dummy,
+		   (unsigned int *) &dummy);
   unblock_input ();
 
   return Fcons (make_fixnum (x), make_fixnum (y));
@@ -8382,8 +8382,8 @@ compute_tip_xy (struct frame *f, Lisp_Object parms, Lisp_Object dx,
       Lisp_Object frame, attributes, monitor, geometry;
 
       block_input ();
-      XQueryPointer (FRAME_X_DISPLAY (f), FRAME_DISPLAY_INFO (f)->root_window,
-		     &root, &child, root_x, root_y, &win_x, &win_y, &pmask);
+      x_query_pointer (FRAME_X_DISPLAY (f), FRAME_DISPLAY_INFO (f)->root_window,
+		       &root, &child, root_x, root_y, &win_x, &win_y, &pmask);
       unblock_input ();
 
       XSETFRAME (frame, f);
diff --git a/src/xterm.c b/src/xterm.c
index 54bf6566572..c1f74f68666 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -5291,6 +5291,7 @@ xi_populate_device_from_info (struct xi_device_t *xi_device,
   xi_device->direct_p = false;
 #endif
   xi_device->name = build_string (device->name);
+  xi_device->attachment = device->attachment;
 
   for (c = 0; c < device->num_classes; ++c)
     {
@@ -12474,7 +12475,9 @@ x_dnd_begin_drag_and_drop (struct frame *f, Time time, Atom xaction,
    focus is switched to a given frame.  This situation is handled by
    keeping track of each master device's focus frame, the time of the
    last interaction with that frame, and always keeping the focus on
-   the most recently selected frame.  */
+   the most recently selected frame.  We also use the pointer of the
+   device that is keeping the current frame focused in functions like
+   `mouse-position'.  */
 
 static void
 xi_handle_focus_change (struct x_display_info *dpyinfo)
@@ -12493,6 +12496,8 @@ xi_handle_focus_change (struct x_display_info *dpyinfo)
   new = NULL;
   time = 0;
 
+  dpyinfo->client_pointer_device = -1;
+
   for (i = 0; i < dpyinfo->num_devices; ++i)
     {
       device = &dpyinfo->devices[i];
@@ -12503,6 +12508,14 @@ xi_handle_focus_change (struct x_display_info *dpyinfo)
 	  new = device->focus_frame;
 	  time = device->focus_frame_time;
 	  source = device;
+
+	  /* Use this device for future calls to `mouse-position' etc.
+	     If it is a keyboard, use its attached pointer.  */
+
+	  if (device->use == XIMasterKeyboard)
+	    dpyinfo->client_pointer_device = device->attachment;
+	  else
+	    dpyinfo->client_pointer_device = device->device_id;
 	}
 
       if (device->focus_implicit_frame
@@ -12511,6 +12524,14 @@ xi_handle_focus_change (struct x_display_info *dpyinfo)
 	  new = device->focus_implicit_frame;
 	  time = device->focus_implicit_time;
 	  source = device;
+
+	  /* Use this device for future calls to `mouse-position' etc.
+	     If it is a keyboard, use its attached pointer.  */
+
+	  if (device->use == XIMasterKeyboard)
+	    dpyinfo->client_pointer_device = device->attachment;
+	  else
+	    dpyinfo->client_pointer_device = device->device_id;
 	}
     }
 
@@ -12601,6 +12622,12 @@ xi_focus_handle_for_device (struct x_display_info *dpyinfo,
       if (!event->focus)
 	break;
 
+      if (device->use == XIMasterPointer)
+	device = xi_device_from_id (dpyinfo, device->attachment);
+
+      if (!device)
+	break;
+
       device->focus_implicit_frame = mentioned_frame;
       device->focus_implicit_time = event->time;
       break;
@@ -12609,6 +12636,12 @@ xi_focus_handle_for_device (struct x_display_info *dpyinfo,
       if (!event->focus)
 	break;
 
+      if (device->use == XIMasterPointer)
+	device = xi_device_from_id (dpyinfo, device->attachment);
+
+      if (!device)
+	break;
+
       device->focus_implicit_frame = NULL;
       break;
     }
@@ -12646,6 +12679,13 @@ xi_handle_interaction (struct x_display_info *dpyinfo,
 {
   bool change;
 
+  /* If DEVICE is a pointer, use its attached keyboard device.  */
+  if (device->use == XIMasterPointer)
+    device = xi_device_from_id (dpyinfo, device->attachment);
+
+  if (!device)
+    return;
+
   change = false;
 
   if (device->focus_frame == f)
@@ -13042,6 +13082,68 @@ get_keysym_name (int keysym)
   return value;
 }
 
+/* Like XQueryPointer, but always use the right client pointer
+   device.  */
+
+Bool
+x_query_pointer (Display *dpy, Window w, Window *root_return,
+		 Window *child_return, int *root_x_return,
+		 int *root_y_return, int *win_x_return,
+		 int *win_y_return, unsigned int *mask_return)
+{
+  struct x_display_info *dpyinfo;
+  Bool rc;
+  bool had_errors;
+#ifdef HAVE_XINPUT2
+  XIModifierState modifiers;
+  XIButtonState buttons;
+  XIGroupState group; /* Unused.  */
+  double root_x, root_y, win_x, win_y;
+  unsigned int state;
+#endif
+
+  dpyinfo = x_display_info_for_display (dpy);
+#ifdef HAVE_XINPUT2
+  if (dpyinfo && dpyinfo->client_pointer_device != -1)
+    {
+      /* Catch errors caused by the device going away.  This is not
+	 very expensive, since XIQueryPointer will sync anyway.  */
+      x_catch_errors (dpy);
+      rc = XIQueryPointer (dpyinfo->display,
+			   dpyinfo->client_pointer_device,
+			   w, root_return, child_return,
+			   &root_x, &root_y, &win_x, &win_y,
+			   &buttons, &modifiers, &group);
+      had_errors = x_had_errors_p (dpy);
+      x_uncatch_errors_after_check ();
+
+      if (had_errors)
+	rc = XQueryPointer (dpyinfo->display, w, root_return,
+			    child_return, root_x_return,
+			    root_y_return, win_x_return,
+			    win_y_return, mask_return);
+      else
+	{
+	  state = 0;
+
+	  xi_convert_button_state (&buttons, &state);
+	  *mask_return = state | modifiers.effective;
+
+	  *root_x_return = lrint (root_x);
+	  *root_y_return = lrint (root_y);
+	  *win_x_return = lrint (win_x);
+	  *win_y_return = lrint (win_y);
+	}
+    }
+  else
+#endif
+    rc = XQueryPointer (dpy, w, root_return, child_return,
+			root_x_return, root_y_return, win_x_return,
+			win_y_return, mask_return);
+
+  return rc;
+}
+
 /* Mouse clicks and mouse movement.  Rah.
 
    Formerly, we used PointerMotionHintMask (in standard_event_mask)
@@ -13308,20 +13410,20 @@ XTmouse_position (struct frame **fp, int insist, Lisp_Object *bar_window,
       dpyinfo->last_mouse_scroll_bar = NULL;
 
       /* Figure out which root window we're on.  */
-      XQueryPointer (FRAME_X_DISPLAY (*fp),
-		     DefaultRootWindow (FRAME_X_DISPLAY (*fp)),
-		     /* The root window which contains the pointer.  */
-		     &root,
-		     /* Trash which we can't trust if the pointer is on
-			a different screen.  */
-		     &dummy_window,
-		     /* The position on that root window.  */
-		     &root_x, &root_y,
-		     /* More trash we can't trust.  */
-		     &dummy, &dummy,
-		     /* Modifier keys and pointer buttons, about which
-			we don't care.  */
-		     (unsigned int *) &dummy);
+      x_query_pointer (FRAME_X_DISPLAY (*fp),
+		       DefaultRootWindow (FRAME_X_DISPLAY (*fp)),
+		       /* The root window which contains the pointer.  */
+		       &root,
+		       /* Trash which we can't trust if the pointer is on
+			  a different screen.  */
+		       &dummy_window,
+		       /* The position on that root window.  */
+		       &root_x, &root_y,
+		       /* More trash we can't trust.  */
+		       &dummy, &dummy,
+		       /* Modifier keys and pointer buttons, about which
+			  we don't care.  */
+		       (unsigned int *) &dummy);
 
       /* Now we have a position on the root; find the innermost window
 	 containing the pointer.  */
@@ -15894,17 +15996,17 @@ x_scroll_bar_report_motion (struct frame **fp, Lisp_Object *bar_window,
 
   /* Get the mouse's position relative to the scroll bar window, and
      report that.  */
-  if (XQueryPointer (FRAME_X_DISPLAY (f), w,
+  if (x_query_pointer (FRAME_X_DISPLAY (f), w,
 
-		     /* Root, child, root x and root y.  */
-		     &dummy_window, &dummy_window,
-		     &dummy_coord, &dummy_coord,
+		       /* Root, child, root x and root y.  */
+		       &dummy_window, &dummy_window,
+		       &dummy_coord, &dummy_coord,
 
-		     /* Position relative to scroll bar.  */
-		     &win_x, &win_y,
+		       /* Position relative to scroll bar.  */
+		       &win_x, &win_y,
 
-		     /* Mouse buttons and modifier keys.  */
-		     &dummy_mask))
+		       /* Mouse buttons and modifier keys.  */
+		       &dummy_mask))
     {
       int top_range = VERTICAL_SCROLL_BAR_TOP_RANGE (f, bar->height);
 
@@ -15963,17 +16065,17 @@ x_horizontal_scroll_bar_report_motion (struct frame **fp, Lisp_Object *bar_windo
 
   /* Get the mouse's position relative to the scroll bar window, and
      report that.  */
-  if (XQueryPointer (FRAME_X_DISPLAY (f), w,
+  if (x_query_pointer (FRAME_X_DISPLAY (f), w,
 
-		     /* Root, child, root x and root y.  */
-		     &dummy_window, &dummy_window,
-		     &dummy_coord, &dummy_coord,
+		       /* Root, child, root x and root y.  */
+		       &dummy_window, &dummy_window,
+		       &dummy_coord, &dummy_coord,
 
-		     /* Position relative to scroll bar.  */
-		     &win_x, &win_y,
+		       /* Position relative to scroll bar.  */
+		       &win_x, &win_y,
 
-		     /* Mouse buttons and modifier keys.  */
-		     &dummy_mask))
+		       /* Mouse buttons and modifier keys.  */
+		       &dummy_mask))
     {
       int left_range = HORIZONTAL_SCROLL_BAR_LEFT_RANGE (f, bar->width);
 
@@ -22202,7 +22304,10 @@ handle_one_xevent (struct x_display_info *dpyinfo,
 		      if (info)
 			{
 			  if (device && info->enabled)
-			    device->use = info->use;
+			    {
+			      device->use = info->use;
+			      device->attachment = info->attachment;
+			    }
 			  else if (device)
 			    disabled[n_disabled++] = hev->info[i].deviceid;
 
@@ -27816,6 +27921,8 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name)
   int minor = 0;
 #endif
 
+  dpyinfo->client_pointer_device = -1;
+
   if (XQueryExtension (dpyinfo->display, "XInputExtension",
 		       &dpyinfo->xi2_opcode, &xi_first_event,
 		       &xi_first_error))
diff --git a/src/xterm.h b/src/xterm.h
index 7be0f2ede65..e97f3d4c831 100644
--- a/src/xterm.h
+++ b/src/xterm.h
@@ -249,6 +249,10 @@ struct xi_device_t
   /* Whether or not the device is grabbed and its use.  */
   int grab, use;
 
+  /* The attached device.  Only valid if USE is some kind of master
+     device.  */
+  int attachment;
+
 #ifdef HAVE_XINPUT2_2
   /* Whether or not this device is a direct touch device.  */
   bool direct_p;
@@ -708,13 +712,27 @@ struct x_display_info
 
 #ifdef HAVE_XINPUT2
   bool supports_xi2;
+
+  /* The minor version of the input extension.  (Major is always
+     2.x.) */
   int xi2_version;
+
+  /* The generic event opcode of XI2 events.  */
   int xi2_opcode;
 
+  /* The number of devices on this display known to Emacs.  */
   int num_devices;
+
+  /* Array of all input extension devices on this display known to
+     Emacs.  */
   struct xi_device_t *devices;
 
+  /* Pending keystroke time.  */
   Time pending_keystroke_time;
+
+  /* Pending keystroke source.  If a core KeyPress event arrives with
+     the same timestamp as pending_keystroke_time, it will be treated
+     as originating from this device.  */
   int pending_keystroke_source;
 
 #if defined USE_GTK && !defined HAVE_GTK3
@@ -724,6 +742,10 @@ struct x_display_info
      input method) core key event.  */
   bool pending_keystroke_time_special_p;
 #endif
+
+  /* The client pointer.  We keep a record client-side to avoid
+     calling XISetClientPointer all the time.  */
+  int client_pointer_device;
 #endif
 
 #ifdef HAVE_XKB
@@ -1599,11 +1621,14 @@ extern Lisp_Object x_cr_export_frames (Lisp_Object, cairo_surface_type_t);
 #ifdef HAVE_XRENDER
 extern void x_xrender_color_from_gc_background (struct frame *, GC,
 						XRenderColor *, bool);
-extern void x_xr_ensure_picture (struct frame *f);
-extern void x_xr_apply_ext_clip (struct frame *f, GC gc);
-extern void x_xr_reset_ext_clip (struct frame *f);
+extern void x_xr_ensure_picture (struct frame *);
+extern void x_xr_apply_ext_clip (struct frame *, GC);
+extern void x_xr_reset_ext_clip (struct frame *);
 #endif
 
+extern Bool x_query_pointer (Display *, Window, Window *, Window *, int *,
+			     int *, int *, int *, unsigned int *);
+
 #ifdef HAVE_GTK3
 extern void x_scroll_bar_configure (GdkEvent *);
 #endif
-- 
2.39.5