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)
This commit is contained in:
Po Lu 2022-08-07 13:46:52 +08:00
parent e0616f2d3c
commit 6f3ade1c08

View 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;
}