]> git.eshelyaron.com Git - emacs.git/commitdiff
Improve X event timestamp tracking
authorDaniel Colascione <dancol@dancol.org>
Sun, 7 Aug 2022 03:42:36 +0000 (23:42 -0400)
committerDaniel Colascione <dancol@dancol.org>
Sun, 7 Aug 2022 03:44:07 +0000 (23:44 -0400)
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.

lisp/server.el
src/frame.c
src/gtkutil.c
src/gtkutil.h
src/termhooks.h
src/xterm.c

index a06f2f952fd2b3f8d32e7832e6851dcc21e0098f..cd3a8f80f0eb32074afbda4b45df7e643d75dbec 100644 (file)
@@ -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'.")
index 25d71e0769f5ac0aa53e7b39ccbae0bf3e407b3c..084df8ef216fdf566b163770b78ae30de0bcbfa7 100644 (file)
@@ -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;
+}
+
 \f
 /***********************************************************************
                        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);
index a6bba096a43b30a279b8d5a024751e94f366ae45..b2af5ff5c2b30666b795544b19ad52e7396f43e6 100644 (file)
@@ -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)
index 190d66283142d816c096a66f21b9283ea0882b20..bca7ea81761e37af34b8fc8cc3b311bd1cb79787 100644 (file)
@@ -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);
 
index c5f1e286e927ff6121e9755f5fe94726ff887a52..2c204afeca3077af43b3a8a2868b60f590a98b96 100644 (file)
@@ -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
index 97985c8d9e7459505a59277d6699b11e6038144e..29295cfe7bbc8538c87615065fc4b63c4773837c 100644 (file)
@@ -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;