From: Daniel Colascione Date: Sun, 7 Aug 2022 03:42:36 +0000 (-0400) Subject: Improve X event timestamp tracking X-Git-Tag: emacs-29.0.90~1447^2~353 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=4b98a79a508ebdc719abfcf51ee6de32e46d0e1c;p=emacs.git Improve X event timestamp tracking Fix two problems with our handling of X timestamps 1) We're not properly updating the X interaction timestamp after receiving certain input events, and 2) X events sent in response to emacsclient commands get stale timestamps because the timestamp tracking doesn't take into account that interactions with the user can occur outside the X input event channel. * src/xterm.c: (x_display_set_last_user_time_1): New function. (x_display_set_last_user_time): Call it. (x_ewmh_activate_frame): Refactor. (x_focus_frame): Don't call XSetInputFocus if we can use EWMH activation. (server_timestamp_predicate): New function. (x_get_server_time): New function. (x_note_oob_interaction): New function. (x_create_terminal): Register new function as terminal hook. * src/termhooks.h: New hook: note_oob_interaction_hook. * src/gtkutil.h: (xg_set_user_timestamp): Declare. * src/gtkutil.c: (xg_set_user_timestamp): New function. * src/frame.c: (Fframe_note_oob_interaction): New function. (syms_of_frame): Register it. * lisp/server.el: (server-switch-buffer): Call frame-note-oob-interaction when user requests frame be raised. --- diff --git a/lisp/server.el b/lisp/server.el index a06f2f952fd..cd3a8f80f0e 100644 --- a/lisp/server.el +++ b/lisp/server.el @@ -1721,7 +1721,9 @@ be a cons cell (LINENUMBER . COLUMNNUMBER)." ;; a minibuffer/dedicated-window (if there's no other). (error (pop-to-buffer next-buffer))))))) (when server-raise-frame - (select-frame-set-input-focus (window-frame))))) + (let ((frame (window-frame))) + (frame-note-oob-interaction frame) + (select-frame-set-input-focus frame))))) (defvar server-stop-automatically nil "Internal status variable for `server-stop-automatically'.") diff --git a/src/frame.c b/src/frame.c index 25d71e0769f..084df8ef216 100644 --- a/src/frame.c +++ b/src/frame.c @@ -5942,6 +5942,25 @@ This function is for internal use only. */) return f->was_invisible ? Qt : Qnil; } + +DEFUN ("frame-note-oob-interaction", + Fframe_note_oob_interaction, + Sframe_note_oob_interaction, 0, 1, 0, + doc: /* Note that the user has interacted with a frame. +This function is useful when the user interacts with Emacs out-of-band +(e.g., via the server) and we want to pretend for purposes of Emacs +interacting with the window system that the last interaction time was +the time of that out-of-band interaction, not the time of the last +window system input event delivered to that frame. */) + (Lisp_Object frame) +{ + struct frame *f = decode_any_frame (frame); + if (FRAME_LIVE_P (f) && + FRAME_TERMINAL (f)->note_oob_interaction_hook) + FRAME_TERMINAL (f)->note_oob_interaction_hook (f); + return Qnil; +} + /*********************************************************************** Multimonitor data @@ -6626,6 +6645,7 @@ iconify the top level frame instead. */); defsubr (&Sframe_window_state_change); defsubr (&Sset_frame_window_state_change); defsubr (&Sframe_scale_factor); + defsubr (&Sframe_note_oob_interaction); #ifdef HAVE_WINDOW_SYSTEM defsubr (&Sx_get_resource); diff --git a/src/gtkutil.c b/src/gtkutil.c index a6bba096a43..b2af5ff5c2b 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -6658,6 +6658,17 @@ xg_filter_key (struct frame *frame, XEvent *xkey) } #endif +#ifndef HAVE_PGTK +void +xg_set_user_timestamp (struct frame *frame, guint32 time) +{ + GtkWidget *widget = FRAME_GTK_OUTER_WIDGET (frame); + GdkWindow *window = gtk_widget_get_window (widget); + eassert (window); + gdk_x11_window_set_user_time (window, time); +} +#endif + #if GTK_CHECK_VERSION (3, 10, 0) static void xg_widget_style_updated (GtkWidget *widget, gpointer user_data) diff --git a/src/gtkutil.h b/src/gtkutil.h index 190d6628314..bca7ea81761 100644 --- a/src/gtkutil.h +++ b/src/gtkutil.h @@ -224,6 +224,10 @@ extern bool xg_is_menu_window (Display *dpy, Window); extern bool xg_filter_key (struct frame *frame, XEvent *xkey); #endif +#ifndef HAVE_PGTK +extern void xg_set_user_timestamp (struct frame *frame, guint32 time); +#endif + /* Mark all callback data that are Lisp_Objects during GC. */ extern void xg_mark_data (void); diff --git a/src/termhooks.h b/src/termhooks.h index c5f1e286e92..2c204afeca3 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -860,6 +860,13 @@ struct terminal will be considered as grabbed. */ bool (*any_grab_hook) (Display_Info *); #endif + + /* Called to note that the user has interacted with a window system + frame outside the window system and that we should update the + window system's notion of the user's last interaction time with + that frame. */ + void (*note_oob_interaction_hook) (struct frame *); + } GCALIGNED_STRUCT; INLINE bool diff --git a/src/xterm.c b/src/xterm.c index 97985c8d9e7..29295cfe7bb 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -6581,12 +6581,6 @@ x_set_frame_alpha (struct frame *f) x_stop_ignoring_errors (dpyinfo); } -/*********************************************************************** - Starting and ending an update - ***********************************************************************/ - -#if defined HAVE_XSYNC && !defined USE_GTK - /* Wait for an event matching PREDICATE to show up in the event queue, or TIMEOUT to elapse. @@ -6640,6 +6634,12 @@ x_if_event (Display *dpy, XEvent *event_return, } } +/*********************************************************************** + Starting and ending an update + ***********************************************************************/ + +#if defined HAVE_XSYNC && !defined USE_GTK + /* Return the monotonic time corresponding to the high-resolution server timestamp TIMESTAMP. Return 0 if the necessary information is not available. */ @@ -7521,26 +7521,25 @@ static void x_check_font (struct frame *, struct font *); user time. We don't sanitize timestamps from events sent by the X server itself because some Lisp might have set the user time to a ridiculously large value, and this way a more reasonable timestamp - can be obtained upon the next event. */ + can be obtained upon the next event. If EXPLICIT_FRAME is NULL, + update the focused frame's timestamp; otherwise, update + EXPLICIT_FRAME's. */ static void -x_display_set_last_user_time (struct x_display_info *dpyinfo, Time time, - bool send_event) +x_display_set_last_user_time_1 (struct x_display_info *dpyinfo, Time time, + bool send_event, + struct frame *explicit_frame) { -#ifndef USE_GTK - struct frame *focus_frame; + struct frame *frame; Time old_time; -#if defined HAVE_XSYNC +#if defined HAVE_XSYNC && !defined USE_GTK uint64_t monotonic_time; #endif - focus_frame = dpyinfo->x_focus_frame; + frame = explicit_frame ? explicit_frame : dpyinfo->x_focus_frame; old_time = dpyinfo->last_user_time; -#endif -#ifdef ENABLE_CHECKING eassert (time <= X_ULONG_MAX); -#endif if (!send_event || time > dpyinfo->last_user_time) dpyinfo->last_user_time = time; @@ -7567,23 +7566,35 @@ x_display_set_last_user_time (struct x_display_info *dpyinfo, Time time, } #endif -#ifndef USE_GTK - /* Don't waste bandwidth if the time hasn't actually changed. */ - if (focus_frame && old_time != dpyinfo->last_user_time) + /* Don't waste bandwidth if the time hasn't actually changed. + Update anyway if we're updating the timestamp for a non-focused + frame, since the event loop might not have gotten around to + updating that frame's timestamp. */ + if (frame && (explicit_frame || old_time != dpyinfo->last_user_time)) { time = dpyinfo->last_user_time; - while (FRAME_PARENT_FRAME (focus_frame)) - focus_frame = FRAME_PARENT_FRAME (focus_frame); + while (FRAME_PARENT_FRAME (frame)) + frame = FRAME_PARENT_FRAME (frame); - if (FRAME_X_OUTPUT (focus_frame)->user_time_window != None) +#if defined USE_GTK + xg_set_user_timestamp (frame, time); +#else + if (FRAME_X_OUTPUT (frame)->user_time_window != None) XChangeProperty (dpyinfo->display, - FRAME_X_OUTPUT (focus_frame)->user_time_window, + FRAME_X_OUTPUT (frame)->user_time_window, dpyinfo->Xatom_net_wm_user_time, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &time, 1); - } #endif + } +} + +static void +x_display_set_last_user_time (struct x_display_info *dpyinfo, Time time, + bool send_event) +{ + x_display_set_last_user_time_1 (dpyinfo, time, send_event, NULL); } #ifdef USE_GTK @@ -25883,9 +25894,11 @@ xembed_request_focus (struct frame *f) XEMBED_REQUEST_FOCUS, 0, 0, 0); } -/* Activate frame with Extended Window Manager Hints */ +/* Activate frame with Extended Window Manager Hints -static void +Return whether we were successful in doing so. */ + +static bool x_ewmh_activate_frame (struct frame *f) { XEvent msg; @@ -25893,8 +25906,7 @@ x_ewmh_activate_frame (struct frame *f) dpyinfo = FRAME_DISPLAY_INFO (f); - if (FRAME_VISIBLE_P (f) - && x_wm_supports (f, dpyinfo->Xatom_net_active_window)) + if (x_wm_supports (f, dpyinfo->Xatom_net_active_window)) { /* See the documentation at https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html @@ -25914,7 +25926,9 @@ x_ewmh_activate_frame (struct frame *f) XSendEvent (dpyinfo->display, dpyinfo->root_window, False, (SubstructureRedirectMask | SubstructureNotifyMask), &msg); + return true; } + return false; } static Lisp_Object @@ -25952,16 +25966,14 @@ x_focus_frame (struct frame *f, bool noactivate) events. See XEmbed Protocol Specification at https://freedesktop.org/wiki/Specifications/xembed-spec/ */ xembed_request_focus (f); - else + else if (noactivate || + (!FRAME_PARENT_FRAME (f) && !x_ewmh_activate_frame (f))) { /* Ignore any BadMatch error this request might result in. */ x_ignore_errors_for_next_request (dpyinfo); XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), RevertToParent, CurrentTime); x_stop_ignoring_errors (dpyinfo); - - if (!noactivate) - x_ewmh_activate_frame (f); } } @@ -28576,6 +28588,55 @@ x_have_any_grab (struct x_display_info *dpyinfo) } #endif +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; +} + +static bool +x_get_server_time (struct frame *f, Time *time) +{ + struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); + Atom property_atom = dpyinfo->Xatom_EMACS_SERVER_TIME_PROP; + XEvent event; + + XChangeProperty (dpyinfo->display, FRAME_OUTER_WINDOW (f), + property_atom, XA_ATOM, 32, + PropModeReplace, (unsigned char *) &property_atom, 1); + + if (x_if_event (dpyinfo->display, &event, server_timestamp_predicate, + (XPointer) &(XID[]) {FRAME_OUTER_WINDOW (f), property_atom}, + dtotimespec (XFLOAT_DATA (Vx_wait_for_event_timeout)))) + return false; + *time = event.xproperty.time; + return true; +} + +static void +x_note_oob_interaction (struct frame *f) +{ + while (FRAME_PARENT_FRAME (f)) + f = FRAME_PARENT_FRAME (f); + if (FRAME_LIVE_P (f)) + { + Time server_time; + if (!x_get_server_time (f, &server_time)) + error ("Timed out waiting for server timestamp"); + x_display_set_last_user_time_1 ( + FRAME_DISPLAY_INFO (f), server_time, false, f); + } +} + /* Create a struct terminal, initialize it with the X11 specific functions and make DISPLAY->TERMINAL point to it. */ @@ -28646,6 +28707,7 @@ x_create_terminal (struct x_display_info *dpyinfo) #ifdef HAVE_XINPUT2 terminal->any_grab_hook = x_have_any_grab; #endif + terminal->note_oob_interaction_hook = x_note_oob_interaction; /* Other hooks are NULL by default. */ return terminal;