]> git.eshelyaron.com Git - emacs.git/commitdiff
Work around problems setting input focus when a frame is in the background
authorPo Lu <luangruo@yahoo.com>
Sun, 7 Aug 2022 05:46:52 +0000 (13:46 +0800)
committerPo Lu <luangruo@yahoo.com>
Thu, 20 Oct 2022 11:30:50 +0000 (19:30 +0800)
* src/xterm.c (server_timestamp_predicate, x_get_server_time):
New functions.
(x_ewmh_activate_frame, x_focus_frame, syms_of_xterm): Apply
various workarounds for window manager "focus stealing
prevention".  (bug#57012)

src/xterm.c

index ade5600f4da3ff63162a75bbea140acf62fd4d57..8b3d6f77a6c804a827ee8d4ed89953094097b4c4 100644 (file)
@@ -27134,6 +27134,64 @@ xembed_request_focus (struct frame *f)
                         XEMBED_REQUEST_FOCUS, 0, 0, 0);
 }
 
+static Bool
+server_timestamp_predicate (Display *display, XEvent *xevent,
+                           XPointer arg)
+{
+  XID *args = (XID *) arg;
+
+  if (xevent->type == PropertyNotify
+      && xevent->xproperty.window == args[0]
+      && xevent->xproperty.atom == args[1])
+    return True;
+
+  return False;
+}
+
+/* Get the server time.  The X server is guaranteed to deliver the
+   PropertyNotify event, so there is no reason to use x_if_event.  */
+
+static Time
+x_get_server_time (struct frame *f)
+{
+  Atom property_atom;
+  XEvent property_dummy;
+  struct x_display_info *dpyinfo;
+  XID client_data[2];
+#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME
+  uint_fast64_t current_monotonic_time;
+#endif
+
+  /* If the server time is the same as the monotonic time, avoid a
+     roundtrip by using that instead.  */
+
+#if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME
+  if (FRAME_DISPLAY_INFO (f)->server_time_monotonic_p)
+    {
+      current_monotonic_time = x_sync_current_monotonic_time ();
+
+      if (current_monotonic_time)
+       /* Truncate the time to CARD32.  */
+       return (current_monotonic_time / 1000) & X_ULONG_MAX;
+    }
+#endif
+
+  dpyinfo = FRAME_DISPLAY_INFO (f);
+  property_atom = dpyinfo->Xatom_EMACS_SERVER_TIME_PROP;
+  client_data[0] = FRAME_OUTER_WINDOW (f);
+  client_data[1] = property_atom;
+
+  XChangeProperty (dpyinfo->display, FRAME_OUTER_WINDOW (f),
+                  property_atom, XA_ATOM, 32,
+                  PropModeReplace,
+                  (unsigned char *) &property_atom, 1);
+
+  XIfEvent (dpyinfo->display, &property_dummy,
+           server_timestamp_predicate, (XPointer) client_data);
+
+  return property_dummy.xproperty.time;
+}
+
 /* Activate frame with Extended Window Manager Hints */
 
 static void
@@ -27141,6 +27199,7 @@ x_ewmh_activate_frame (struct frame *f)
 {
   XEvent msg;
   struct x_display_info *dpyinfo;
+  Time time;
 
   dpyinfo = FRAME_DISPLAY_INFO (f);
 
@@ -27161,6 +27220,43 @@ x_ewmh_activate_frame (struct frame *f)
       msg.xclient.data.l[3] = 0;
       msg.xclient.data.l[4] = 0;
 
+      /* No frame is currently focused on that display, so apply any
+        bypass for focus stealing prevention that the user has
+        specified.  */
+      if (!dpyinfo->x_focus_frame)
+       {
+         if (EQ (Vx_allow_focus_stealing, Qimitate_pager))
+           msg.xclient.data.l[0] = 2;
+         else if (EQ (Vx_allow_focus_stealing, Qnewer_time))
+           {
+             block_input ();
+             time = x_get_server_time (f);
+#ifdef USE_GTK
+             x_set_gtk_user_time (f, time);
+#endif
+             /* Temporarily override dpyinfo->x_focus_frame so the
+                user time property is set on the right window.  */
+             dpyinfo->x_focus_frame = f;
+             x_display_set_last_user_time (dpyinfo, time, true, true);
+             dpyinfo->x_focus_frame = NULL;
+             unblock_input ();
+
+             msg.xclient.data.l[1] = time;
+           }
+         else if (EQ (Vx_allow_focus_stealing, Qraise_and_focus))
+           {
+             time = x_get_server_time (f);
+
+             x_ignore_errors_for_next_request (dpyinfo);
+             XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f),
+                             RevertToParent, time);
+             XRaiseWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f));
+             x_stop_ignoring_errors (dpyinfo);
+
+             return;
+           }
+       }
+
       XSendEvent (dpyinfo->display, dpyinfo->root_window,
                  False, (SubstructureRedirectMask
                          | SubstructureNotifyMask), &msg);
@@ -30649,6 +30745,9 @@ With MS Windows, Haiku windowing or Nextstep, the value is t.  */);
   Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier));
   DEFSYM (QXdndSelection, "XdndSelection");
   DEFSYM (Qx_selection_alias_alist, "x-selection-alias-alist");
+  DEFSYM (Qimitate_pager, "imitate-pager");
+  DEFSYM (Qnewer_time, "newer-time");
+  DEFSYM (Qraise_and_focus, "raise-and-focus");
 
   DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym,
     doc: /* Which keys Emacs uses for the ctrl modifier.
@@ -30902,4 +31001,24 @@ connection setup.  */);
   /* The default value of this variable is chosen so that updating the
      tool bar does not require a call to _XReply.  */
   Vx_fast_selection_list = list1 (QCLIPBOARD);
+
+  DEFVAR_LISP ("x-allow-focus-stealing", Vx_allow_focus_stealing,
+    doc: /* How to bypass window manager focus stealing prevention.
+
+Some window managers prevent `x-focus-frame' from activating the given
+frame when Emacs is in the background, which is especially prone to
+cause problems when the Emacs server wants to activate itself.  This
+variable specifies the strategy used to activate frames when that is
+the case, and has several valid values (any other value means to not
+bypass window manager focus stealing prevention):
+
+  - The symbol `imitate-pager', which means to pretend that Emacs is a
+    pager.
+
+  - The symbol `newer-time', which means to fetch the current time
+    from the X server and use it to activate the frame.
+
+  - The symbol `raise-and-focus', which means to raise the window and
+    focus it manually.  */);
+  Vx_allow_focus_stealing = Qnewer_time;
 }