From: Po Lu Date: Sun, 7 Aug 2022 05:46:52 +0000 (+0800) Subject: Work around problems setting input focus when a frame is in the background X-Git-Tag: emacs-29.0.90~1616^2~525 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=6f3ade1c08c6cbf56c0dc0d12e9508c261eb42bf;p=emacs.git Work around problems setting input focus when a frame is in the background * 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) --- diff --git a/src/xterm.c b/src/xterm.c index ade5600f4da..8b3d6f77a6c 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -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; }