GNU bug report logs - #78899
30.1; garbage inserted in the terminal buffer when quitting Emacs

Previous Next

Package: emacs;

Reported by: Vincent Lefevre <vincent <at> vinc17.net>

Date: Thu, 26 Jun 2025 00:39:01 UTC

Severity: normal

Found in version 30.1

To reply to this bug, email your comments to 78899 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Thu, 26 Jun 2025 00:39:01 GMT) Full text and rfc822 format available.

Acknowledgement sent to Vincent Lefevre <vincent <at> vinc17.net>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Thu, 26 Jun 2025 00:39:02 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Vincent Lefevre <vincent <at> vinc17.net>
To: bug-gnu-emacs <at> gnu.org
Subject: 30.1; garbage inserted in the terminal buffer when quitting Emacs
Date: Thu, 26 Jun 2025 02:37:58 +0200
I ran "emacs -nw some_file" on my Android phone via ssh in xterm,
where emacs is provided by Termux.

Just after I quit Emacs with C-x C-c, I got the following in the
terminal buffer, which became input at the shell prompt:

^[[<35;42;25M35;40;24M35;37;24M35;42;25M35;33;23M

I think that this was the first time this occurred.

Note that there were no issues with the network (this is on the
local network at home).

In GNU Emacs 30.1 (build 1, aarch64-unknown-linux-android) of 2025-06-18
 built on localhost
Configured using:
 'configure --disable-dependency-tracking
 --prefix=/data/data/com.termux/files/usr
 --libdir=/data/data/com.termux/files/usr/lib
 --includedir=/data/data/com.termux/files/usr/include
 --sbindir=/data/data/com.termux/files/usr/bin --disable-rpath
 --disable-rpath-hack --host=aarch64-linux-android --disable-autodepend
 --with-dumping=none --with-gif=no --with-gnutls --with-jpeg=no
 --with-modules --with-pdumper=yes --with-png=no --with-tiff=no
 --with-xml2 --with-xpm=no --with-tree-sitter --without-dbus
 --without-gconf --without-gsettings --without-lcms2 --without-selinux
 --without-x emacs_cv_alternate_stack=yes emacs_cv_sanitize_address=yes
 emacs_cv_prog_cc_no_pie=no ac_cv_lib_elf_elf_begin=no
 gl_cv_func_dup2_works=no ac_cv_func_setrlimit=no --disable-nls
 --enable-shared --enable-static
 --libexecdir=/data/data/com.termux/files/usr/libexec 'CFLAGS=
 -fstack-protector-strong -Oz' 'CPPFLAGS=
 -isystem/data/data/com.termux/files/usr/include/c++/v1
 -isystem/data/data/com.termux/files/usr/include'
 'LDFLAGS=-L/data/data/com.termux/files/usr/lib
 -Wl,-rpath=/data/data/com.termux/files/usr/lib -Wl,--enable-new-dtags
 -Wl,--as-needed -Wl,-z,relro,-z,now''

Configured features:
GMP GNUTLS LIBXML2 MODULES NOTIFY INOTIFY PDUMPER SECCOMP SQLITE3
THREADS TREE_SITTER XIM ZLIB

Important settings:
  value of $LANG: en_US.UTF-8
  locale-coding-system: utf-8-unix

Major mode: Fundamental

Minor modes in effect:
  display-time-mode: t
  xterm-mouse-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  show-paren-mode: t
  electric-indent-mode: t
  menu-bar-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  font-lock-mode: t
  blink-cursor-mode: t
  minibuffer-regexp-mode: t
  column-number-mode: t
  line-number-mode: t
  indent-tabs-mode: t
  transient-mark-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t

Load-path shadows:
None found.

Features:
(shadow sort mail-extr cl-extra help-mode tool-bar warnings icons
emacsbug message mailcap yank-media puny dired dnd dired-loaddefs rfc822
mml mml-sec password-cache epa derived epg rfc6068 epg-config gnus-util
text-property-search time-date subr-x mm-decode mm-bodies mm-encode
mail-parse rfc2231 mailabbrev gmm-utils mailheader sendmail rfc2047
rfc2045 ietf-drums mm-util mail-prsvr mail-utils vc-dispatcher vc-svn
term/xterm xterm byte-opt gv bytecomp byte-compile time image cus-load
cc-styles cc-align cc-engine cc-vars cc-defs regexp-opt cl-loaddefs
cl-lib xt-mouse rmc iso-transl tooltip cconv eldoc paren electric
uniquify ediff-hook vc-hooks lisp-float-type elisp-mode tabulated-list
replace newcomment text-mode lisp-mode prog-mode register page tab-bar
menu-bar rfn-eshadow isearch easymenu timer select mouse jit-lock
font-lock syntax font-core term/tty-colors frame minibuffer nadvice seq
simple cl-generic indonesian philippine cham georgian utf-8-lang
misc-lang vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms
cp51932 hebrew greek romanian slovak czech european ethiopic indian
cyrillic chinese composite emoji-zwj charscript charprop case-table
epa-hook jka-cmpr-hook help abbrev obarray oclosure cl-preloaded button
loaddefs theme-loaddefs faces cus-face macroexp files window
text-properties overlay sha1 md5 base64 format env code-pages mule
custom widget keymap hashtable-print-readable backquote threads inotify
multi-tty make-network-process emacs)

-- 
Vincent Lefèvre <vincent <at> vinc17.net> - Web: <https://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <https://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / Pascaline project (LIP, ENS-Lyon)




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Thu, 26 Jun 2025 06:19:01 GMT) Full text and rfc822 format available.

Message #8 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Vincent Lefevre <vincent <at> vinc17.net>, Po Lu <luangruo <at> yahoo.com>
Cc: 78899 <at> debbugs.gnu.org
Subject: Re: bug#78899: 30.1;
 garbage inserted in the terminal buffer when quitting Emacs
Date: Thu, 26 Jun 2025 09:18:45 +0300
> Date: Thu, 26 Jun 2025 02:37:58 +0200
> From: Vincent Lefevre <vincent <at> vinc17.net>
> 
> I ran "emacs -nw some_file" on my Android phone via ssh in xterm,
> where emacs is provided by Termux.
> 
> Just after I quit Emacs with C-x C-c, I got the following in the
> terminal buffer, which became input at the shell prompt:
> 
> ^[[<35;42;25M35;40;24M35;37;24M35;42;25M35;33;23M
> 
> I think that this was the first time this occurred.
> 
> Note that there were no issues with the network (this is on the
> local network at home).

That could be some kind of command we send to the terminal, which the
terminal doesn't support?

Po Lu, any ideas?




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Thu, 26 Jun 2025 11:38:02 GMT) Full text and rfc822 format available.

Message #11 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Pip Cet <pipcet <at> protonmail.com>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: Po Lu <luangruo <at> yahoo.com>, 78899 <at> debbugs.gnu.org,
 Vincent Lefevre <vincent <at> vinc17.net>
Subject: Re: bug#78899: 30.1;
 garbage inserted in the terminal buffer when quitting Emacs
Date: Thu, 26 Jun 2025 11:37:12 +0000
"Eli Zaretskii" <eliz <at> gnu.org> writes:

>> Date: Thu, 26 Jun 2025 02:37:58 +0200
>> From: Vincent Lefevre <vincent <at> vinc17.net>
>>
>> I ran "emacs -nw some_file" on my Android phone via ssh in xterm,
>> where emacs is provided by Termux.
>>
>> Just after I quit Emacs with C-x C-c, I got the following in the
>> terminal buffer, which became input at the shell prompt:
>>
>> ^[[<35;42;25M35;40;24M35;37;24M35;42;25M35;33;23M
>>
>> I think that this was the first time this occurred.
>>
>> Note that there were no issues with the network (this is on the
>> local network at home).
>
> That could be some kind of command we send to the terminal, which the
> terminal doesn't support?
>
> Po Lu, any ideas?

Those are mouse movements reported by xterm.  I can reproduce this by
using a slow connection, enabling xterm-mouse-mode, quitting Emacs, and
moving the mouse while that process is being sent.

I think what happens is that we send the escape sequence to disable
those just before exiting, without waiting for that escape sequence to
be processed by the terminal, so xterm keeps sending them to the next
application for a while.

Note that xterm--query-name-and-version fails on slow connections, too:
the timeout is set to 0.1 s, and if we don't get a response in that time,
we get an asynchronous throw to a tag that no longer has a catch.  This
produces the error message:

No catch for tag: result, "XTerm(400)"

IIUC, the xterm--query-name-and-version bug hides the other bug:
usually, if you have a slow connection, xterm mouse mode won't be
enabled automatically because we fail to recognize the terminal.  If you
have a fast connection, such as a local display, the window for mouse
movements to be reported is small.

We could possibly fix this by asking for another report from xterm upon
termination and waiting for the response?

Pip





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Thu, 26 Jun 2025 12:36:01 GMT) Full text and rfc822 format available.

Message #14 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Pip Cet <pipcet <at> protonmail.com>
Cc: luangruo <at> yahoo.com, 78899 <at> debbugs.gnu.org, vincent <at> vinc17.net
Subject: Re: bug#78899: 30.1;
 garbage inserted in the terminal buffer when quitting Emacs
Date: Thu, 26 Jun 2025 15:35:27 +0300
> Date: Thu, 26 Jun 2025 11:37:12 +0000
> From: Pip Cet <pipcet <at> protonmail.com>
> Cc: Vincent Lefevre <vincent <at> vinc17.net>, Po Lu <luangruo <at> yahoo.com>, 78899 <at> debbugs.gnu.org
> 
> We could possibly fix this by asking for another report from xterm upon
> termination and waiting for the response?

Yes, I think something like that would make sense.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Mon, 30 Jun 2025 22:49:02 GMT) Full text and rfc822 format available.

Message #17 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Pip Cet <pipcet <at> protonmail.com>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: luangruo <at> yahoo.com, 78899 <at> debbugs.gnu.org, vincent <at> vinc17.net
Subject: Re: bug#78899: 30.1;
 garbage inserted in the terminal buffer when quitting Emacs
Date: Mon, 30 Jun 2025 22:47:45 +0000
"Eli Zaretskii" <eliz <at> gnu.org> writes:

>> Date: Thu, 26 Jun 2025 11:37:12 +0000
>> From: Pip Cet <pipcet <at> protonmail.com>
>> Cc: Vincent Lefevre <vincent <at> vinc17.net>, Po Lu <luangruo <at> yahoo.com>, 78899 <at> debbugs.gnu.org
>>
>> We could possibly fix this by asking for another report from xterm upon
>> termination and waiting for the response?
>
> Yes, I think something like that would make sense.

It turns out that the request-response cycle to ensure the remote xterm
has caught up to our output is a little tricky: we don't want to use the
command loop like xterm--query does, because we're usually called from
within kill-emacs and won't return to the command loop.  We also can't
do it in C, because the code to read from a terminal without blocking is
highly system-dependent.

So that leaves read-event.  I went to some trouble to collect all events
in a list, removing only those that constitute our response escape
sequence and pushing the rest to unread-command-events; I believe that's
the right thing to do because we might want to auto-disable
xterm-mouse-mode when we detect a slow terminal one day, as the old code
effectively did.  However, maybe the low-level key thing would be a
better alternative and allow restricting input to one terminal only?

Here are the two patches; I'm aware of a whitespace issue, and we need
to decide on a sensible timeout for xterm-sync: I used 2.0 seconds
because I think the most common scenario for slow xterm connections is a
Tor connection, and Tor appears to add up to about 1.2 seconds of
(round-trip) latency.

From 4cc6d43fd3f105b15f8079cf647c83bb511db339 Mon Sep 17 00:00:00 2001
From: Pip Cet <pipcet <at> protonmail.com>
Subject: [PATCH 1/2] Make xterm--query-name-and-version use a callback pattern
 (bug#78899)

The previous code would throw without a containing catch on slow
terminal connections.

* lisp/term/xterm.el (xterm--query-name-and-version): Accept
'callback' argument.  Call it.
(xterm--init): Handle asynchronous terminal id responses.
---
 lisp/term/xterm.el | 44 +++++++++++++++++++++++---------------------
 1 file changed, 23 insertions(+), 21 deletions(-)

diff --git a/lisp/term/xterm.el b/lisp/term/xterm.el
index 4f23a909b69..4fe27775364 100644
--- a/lisp/term/xterm.el
+++ b/lisp/term/xterm.el
@@ -904,19 +904,19 @@ xterm--query
               (push (aref (car handler) (setq i (1- i)))
                     unread-command-events))))))))
 
-(defun xterm--query-name-and-version ()
-  "Get the terminal name and version string (XTVERSION)."
+(defun xterm--query-name-and-version (callback)
+  "Get the terminal name and version string (XTVERSION).
+CALLBACK is called asynchronously when a response is received."
   ;; Reduce query timeout time. The default value causes a noticeable
   ;; startup delay on terminals that ignore the query.
-  (let ((xterm-query-timeout 0.1))
-    (catch 'result
-      (xterm--query
-       "\e[>0q"
-       `(("\eP>|" . ,(lambda ()
-                       ;; The reply should be: \e P > | STRING \e \\
-                       (let ((str (xterm--read-string ?\e ?\\)))
-                         (throw 'result str))))))
-      nil)))
+  (let ((xterm-query-timeout nil))
+    (xterm--query
+     "\e[>0q"
+     `(("\eP>|" . ,(lambda ()
+                     ;; The reply should be: \e P > | STRING \e \\
+                     (let ((str (xterm--read-string ?\e ?\\)))
+                       (funcall callback str))))))
+    nil))
 
 (defun xterm--push-map (map basemap)
   ;; Use inheritance to let the main keymaps override those defaults.
@@ -965,16 +965,18 @@ xterm--init
 
   (when xterm-set-window-title
     (xterm--init-frame-title))
-  (when (and (not xterm-mouse-mode-called)
-             ;; Only automatically enable xterm mouse on terminals
-             ;; confirmed to still support all critical editing
-             ;; workflows (bug#74833).
-             (or (string-match-p xterm--auto-xt-mouse-allowed-types
-                                 (tty-type (selected-frame)))
-                 (and-let* ((name-and-version (xterm--query-name-and-version)))
-                   (string-match-p xterm--auto-xt-mouse-allowed-names
-                                   name-and-version))))
-    (xterm-mouse-mode 1))
+  (when (not xterm-mouse-mode-called)
+    ;; Only automatically enable xterm mouse on terminals
+    ;; confirmed to still support all critical editing
+    ;; workflows (bug#74833).
+    (if (string-match-p xterm--auto-xt-mouse-allowed-types
+                        (tty-type (selected-frame)))
+        (xterm-mouse-mode 1)
+      (xterm--query-name-and-version
+       (lambda (name-and-version)
+         (when (string-match-p xterm--auto-xt-mouse-allowed-names
+                               name-and-version)
+           (xterm-mouse-mode 1))))))
   ;; Unconditionally enable bracketed paste mode: terminals that don't
   ;; support it just ignore the sequence.
   (xterm--init-bracketed-paste-mode)
-- 
2.50.0

From 8d9bc5b59ba34252b9cefc23fe673e6345a3e6fd Mon Sep 17 00:00:00 2001
From: Pip Cet <pipcet <at> protonmail.com>
Subject: [PATCH 2/2] Don't let xterm mouse events arrive after we quit
 (bug#78899)

* lisp/term/xterm.el (xterm-sync): New function.
* lisp/xt-mouse.el (turn-on-xterm-mouse-tracking-on-terminal):
Register 'kill-emacs' hook function.
(xterm-sync): Declare.
(turn-off-xterm-mouse-tracking-on-terminal): Call 'xterm-sync' before
we stop processing mouse events.
---
 lisp/term/xterm.el | 40 ++++++++++++++++++++++++++++++++++++++++
 lisp/xt-mouse.el   | 10 ++++++++--
 2 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/lisp/term/xterm.el b/lisp/term/xterm.el
index 4fe27775364..cbc8a2b8398 100644
--- a/lisp/term/xterm.el
+++ b/lisp/term/xterm.el
@@ -918,6 +918,46 @@ xterm--query-name-and-version
                        (funcall callback str))))))
     nil))
 
+(defun xterm-sync (terminal &optional timeout)
+  "Ensure that the TERMINAL is in a synchronized state, but obey TIMEOUT."
+  ;; This function tries very hard to handle all events that do not belong
+  ;; to the escape sequence we are looking for.  It's usually called only
+  ;; when Emacs quits, so this might be overkill.
+  (setq timeout (or timeout 2.0))
+  (let ((i 0)
+        events all-events)
+    ;; request Primary DA from terminal
+    (send-string-to-terminal "\e[0c" terminal)
+    ;; build two lists: ALL-EVENTS contains all events, while EVENTS
+    ;; associates character events (only) to their index in ALL-EVENTS.
+    ;; We filter out the expected escape sequence ("\e[?") from the
+    ;; ALL-EVENTS list.
+    (with-timeout (timeout
+                   (setq unread-command-events
+                         (append unread-command-events
+                                 (nreverse all-events))))
+      (while (not
+              (pcase events
+                (`((?? . ,i0)
+                   (?\[ . ,i1)
+                   (?\e . ,i2) . ,_)
+                 (setq all-events (nreverse all-events))
+                 (pop (nthcdr i0 all-events))
+                 (pop (nthcdr i1 all-events))
+                 (pop (nthcdr i2 all-events))
+                 t)))
+        (let ((event (read-event)))
+          (push event all-events)
+          (when (characterp event)
+            (push (cons event i) events))
+          (incf i)))
+      ;; read the rest of the response
+      (xterm--read-string ?c)
+      ;; replay events that preceded or occurred within the escape
+      ;; sequence.
+      (setq unread-command-events
+            (append unread-command-events all-events)))))
+
 (defun xterm--push-map (map basemap)
   ;; Use inheritance to let the main keymaps override those defaults.
   ;; This way we don't override terminfo-derived settings or settings
diff --git a/lisp/xt-mouse.el b/lisp/xt-mouse.el
index 89f9bbab608..884ae3ab274 100644
--- a/lisp/xt-mouse.el
+++ b/lisp/xt-mouse.el
@@ -528,10 +528,14 @@ turn-on-xterm-mouse-tracking-on-terminal
                    (signal (car err) (cdr err)))))
         (push enable (terminal-parameter nil 'tty-mode-set-strings))
         (push disable (terminal-parameter nil 'tty-mode-reset-strings))
+        (add-hook 'kill-emacs-hook (lambda ()
+                                     (turn-off-xterm-mouse-tracking-on-terminal terminal)))
         (set-terminal-parameter terminal 'xterm-mouse-mode t)
         (set-terminal-parameter terminal 'xterm-mouse-utf-8
                                 xterm-mouse-utf-8)))))
 
+(declare-function xterm-sync "xterm" (terminal &optional timeout))
+
 (defun turn-off-xterm-mouse-tracking-on-terminal (terminal)
   "Disable xterm mouse tracking on TERMINAL."
   ;; Only send the disable command to those terminals to which we've already
@@ -544,8 +548,10 @@ turn-off-xterm-mouse-tracking-on-terminal
     ;; to send it too few times (or to fail to let xterm-mouse events
     ;; pass by untranslated).
     (condition-case err
-        (send-string-to-terminal xterm-mouse-tracking-disable-sequence
-                                 terminal)
+        (progn
+          (send-string-to-terminal xterm-mouse-tracking-disable-sequence
+                                   terminal)
+          (xterm-sync terminal))
       ;; FIXME: This should use a dedicated error signal.
       (error (if (equal (cadr err) "Terminal is currently suspended")
                  nil
-- 
2.50.0





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Tue, 01 Jul 2025 11:54:02 GMT) Full text and rfc822 format available.

Message #20 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Pip Cet <pipcet <at> protonmail.com>, Jared Finder <jared <at> finder.org>,
 Stefan Monnier <monnier <at> iro.umontreal.ca>
Cc: luangruo <at> yahoo.com, 78899 <at> debbugs.gnu.org, vincent <at> vinc17.net
Subject: Re: bug#78899: 30.1;
 garbage inserted in the terminal buffer when quitting Emacs
Date: Tue, 01 Jul 2025 14:53:28 +0300
> Date: Mon, 30 Jun 2025 22:47:45 +0000
> From: Pip Cet <pipcet <at> protonmail.com>
> Cc: vincent <at> vinc17.net, luangruo <at> yahoo.com, 78899 <at> debbugs.gnu.org
> 
> "Eli Zaretskii" <eliz <at> gnu.org> writes:
> 
> >> Date: Thu, 26 Jun 2025 11:37:12 +0000
> >> From: Pip Cet <pipcet <at> protonmail.com>
> >> Cc: Vincent Lefevre <vincent <at> vinc17.net>, Po Lu <luangruo <at> yahoo.com>, 78899 <at> debbugs.gnu.org
> >>
> >> We could possibly fix this by asking for another report from xterm upon
> >> termination and waiting for the response?
> >
> > Yes, I think something like that would make sense.
> 
> It turns out that the request-response cycle to ensure the remote xterm
> has caught up to our output is a little tricky: we don't want to use the
> command loop like xterm--query does, because we're usually called from
> within kill-emacs and won't return to the command loop.  We also can't
> do it in C, because the code to read from a terminal without blocking is
> highly system-dependent.
> 
> So that leaves read-event.  I went to some trouble to collect all events
> in a list, removing only those that constitute our response escape
> sequence and pushing the rest to unread-command-events; I believe that's
> the right thing to do because we might want to auto-disable
> xterm-mouse-mode when we detect a slow terminal one day, as the old code
> effectively did.  However, maybe the low-level key thing would be a
> better alternative and allow restricting input to one terminal only?
> 
> Here are the two patches; I'm aware of a whitespace issue, and we need
> to decide on a sensible timeout for xterm-sync: I used 2.0 seconds
> because I think the most common scenario for slow xterm connections is a
> Tor connection, and Tor appears to add up to about 1.2 seconds of
> (round-trip) latency.

Thanks.  Jared and Stefan, any comments?




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Tue, 01 Jul 2025 22:10:03 GMT) Full text and rfc822 format available.

Message #23 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Stefan Monnier <monnier <at> iro.umontreal.ca>
To: Pip Cet <pipcet <at> protonmail.com>
Cc: luangruo <at> yahoo.com, Eli Zaretskii <eliz <at> gnu.org>, vincent <at> vinc17.net,
 78899 <at> debbugs.gnu.org
Subject: Re: bug#78899: 30.1; garbage inserted in the terminal buffer when
 quitting Emacs
Date: Tue, 01 Jul 2025 18:09:21 -0400
> * lisp/term/xterm.el (xterm--query-name-and-version): Accept
> 'callback' argument.  Call it.
> (xterm--init): Handle asynchronous terminal id responses.

Hmm... you may want to check the code history, because I have a vague
recollection that we had something along these lines at some point and
it bumped into other problems, probably when ELisp code runs between
`xterm--query-name-and-version` and its callback, and sends other things
to the terminal or reads from the terminal.

Maybe I misremember, of course.

> +(defun xterm-sync (terminal &optional timeout)
> +  "Ensure that the TERMINAL is in a synchronized state, but obey TIMEOUT."
> +  ;; This function tries very hard to handle all events that do not belong
> +  ;; to the escape sequence we are looking for.  It's usually called only
> +  ;; when Emacs quits, so this might be overkill.
> +  (setq timeout (or timeout 2.0))
> +  (let ((i 0)
> +        events all-events)
> +    ;; request Primary DA from terminal
> +    (send-string-to-terminal "\e[0c" terminal)
> +    ;; build two lists: ALL-EVENTS contains all events, while EVENTS
> +    ;; associates character events (only) to their index in ALL-EVENTS.
> +    ;; We filter out the expected escape sequence ("\e[?") from the
> +    ;; ALL-EVENTS list.
> +    (with-timeout (timeout
> +                   (setq unread-command-events
> +                         (append unread-command-events
> +                                 (nreverse all-events))))
> +      (while (not
> +              (pcase events
> +                (`((?? . ,i0)
> +                   (?\[ . ,i1)
> +                   (?\e . ,i2) . ,_)
> +                 (setq all-events (nreverse all-events))
> +                 (pop (nthcdr i0 all-events))
> +                 (pop (nthcdr i1 all-events))
> +                 (pop (nthcdr i2 all-events))
> +                 t)))
> +        (let ((event (read-event)))
> +          (push event all-events)
> +          (when (characterp event)
> +            (push (cons event i) events))
> +          (incf i)))
> +      ;; read the rest of the response
> +      (xterm--read-string ?c)
> +      ;; replay events that preceded or occurred within the escape
> +      ;; sequence.
> +      (setq unread-command-events
> +            (append unread-command-events all-events)))))

Can we consolidate the two "nreverse + append to u-c-e", maybe to an
unwind-handler?
Also, I have the impression that we should *prepend* rather than append
(in practice u-c-e should be nil at that point so it likely doesn't
matter, tho).

Oh, and `(xterm-sync TERMINAL)` sounds like a very generic
functionality, whereas the code is much more specialized and presumes
we've send a specific escape sequence.  I'd use a longer name that makes
it more clear what it's doing: "xterm-sync" says what it's for but not
what it really does.

> @@ -544,8 +548,10 @@ turn-off-xterm-mouse-tracking-on-terminal
>      ;; to send it too few times (or to fail to let xterm-mouse events
>      ;; pass by untranslated).
>      (condition-case err
> -        (send-string-to-terminal xterm-mouse-tracking-disable-sequence
> -                                 terminal)
> +        (progn
> +          (send-string-to-terminal xterm-mouse-tracking-disable-sequence
> +                                   terminal)
> +          (xterm-sync terminal))
>        ;; FIXME: This should use a dedicated error signal.
>        (error (if (equal (cadr err) "Terminal is currently suspended")
>                   nil

Do we need the `xterm-sync` in all cases?
IIUC we need it when we "leave" a terminal, but not when we merely turn
the mode off.


        Stefan





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Wed, 02 Jul 2025 10:58:02 GMT) Full text and rfc822 format available.

Message #26 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Pip Cet <pipcet <at> protonmail.com>
To: Stefan Monnier <monnier <at> iro.umontreal.ca>
Cc: luangruo <at> yahoo.com, Eli Zaretskii <eliz <at> gnu.org>, vincent <at> vinc17.net,
 78899 <at> debbugs.gnu.org
Subject: Re: bug#78899: 30.1;
 garbage inserted in the terminal buffer when quitting Emacs
Date: Wed, 02 Jul 2025 10:57:41 +0000
"Stefan Monnier" <monnier <at> iro.umontreal.ca> writes:

>> * lisp/term/xterm.el (xterm--query-name-and-version): Accept
>> 'callback' argument.  Call it.
>> (xterm--init): Handle asynchronous terminal id responses.
>
> Hmm... you may want to check the code history, because I have a vague
> recollection that we had something along these lines at some point and
> it bumped into other problems, probably when ELisp code runs between
> `xterm--query-name-and-version` and its callback, and sends other things
> to the terminal or reads from the terminal.
>
> Maybe I misremember, of course.

Thanks!

Maybe a conditional throw would be better?  I don't think it's a problem
that we don't recognize xterms which are so slow to respond that
mouse-mode would be painful to use, anyway (it certainly is over tor).

>> +(defun xterm-sync (terminal &optional timeout)
>> +  "Ensure that the TERMINAL is in a synchronized state, but obey TIMEOUT."
>> +  ;; This function tries very hard to handle all events that do not belong
>> +  ;; to the escape sequence we are looking for.  It's usually called only
>> +  ;; when Emacs quits, so this might be overkill.
>> +  (setq timeout (or timeout 2.0))
>> +  (let ((i 0)
>> +        events all-events)
>> +    ;; request Primary DA from terminal
>> +    (send-string-to-terminal "\e[0c" terminal)
>> +    ;; build two lists: ALL-EVENTS contains all events, while EVENTS
>> +    ;; associates character events (only) to their index in ALL-EVENTS.
>> +    ;; We filter out the expected escape sequence ("\e[?") from the
>> +    ;; ALL-EVENTS list.
>> +    (with-timeout (timeout
>> +                   (setq unread-command-events
>> +                         (append unread-command-events
>> +                                 (nreverse all-events))))
>> +      (while (not
>> +              (pcase events
>> +                (`((?? . ,i0)
>> +                   (?\[ . ,i1)
>> +                   (?\e . ,i2) . ,_)
>> +                 (setq all-events (nreverse all-events))
>> +                 (pop (nthcdr i0 all-events))
>> +                 (pop (nthcdr i1 all-events))
>> +                 (pop (nthcdr i2 all-events))
>> +                 t)))
>> +        (let ((event (read-event)))
>> +          (push event all-events)
>> +          (when (characterp event)
>> +            (push (cons event i) events))
>> +          (incf i)))
>> +      ;; read the rest of the response
>> +      (xterm--read-string ?c)
>> +      ;; replay events that preceded or occurred within the escape
>> +      ;; sequence.
>> +      (setq unread-command-events
>> +            (append unread-command-events all-events)))))
>
> Can we consolidate the two "nreverse + append to u-c-e", maybe to an
> unwind-handler?

That would make sense, yes.  Premature optimization there.  (Moving the
mouse during xterm-sync does cause "a lot" of input, but it's nowhere
near enough to warrant this microoptimization).

> Also, I have the impression that we should *prepend* rather than append
> (in practice u-c-e should be nil at that point so it likely doesn't
> matter, tho).

You're right.  There are two cases, but prepending is the right thing to
do in both of them :-)

> Oh, and `(xterm-sync TERMINAL)` sounds like a very generic
> functionality, whereas the code is much more specialized and presumes
> we've send a specific escape sequence.

Is it specialized? It ensures that commands sent before the function
call affect input received after the function call. The precise escape
sequence (which is sent from within xterm-sync, not before) doesn't
matter, we just need something that will trigger a response from the
terminal.

> I'd use a longer name that makes it more clear what it's doing:
> "xterm-sync" says what it's for but not what it really does.

I agree; this function isn't generally useful, because there is no way
to actually do what it tries to do in all cases.  It's good enough to
prevent mouse events from showing up in the usual case (a single xterm,
no fake input from timers, no silly users hitting ESC [ ? c just for fun
just before xterm-sync runs), but that's about it.

>> @@ -544,8 +548,10 @@ turn-off-xterm-mouse-tracking-on-terminal
>>      ;; to send it too few times (or to fail to let xterm-mouse events
>>      ;; pass by untranslated).
>>      (condition-case err
>> -        (send-string-to-terminal xterm-mouse-tracking-disable-sequence
>> -                                 terminal)
>> +        (progn
>> +          (send-string-to-terminal xterm-mouse-tracking-disable-sequence
>> +                                   terminal)
>> +          (xterm-sync terminal))
>>        ;; FIXME: This should use a dedicated error signal.
>>        (error (if (equal (cadr err) "Terminal is currently suspended")
>>                   nil
>
> Do we need the `xterm-sync` in all cases?
> IIUC we need it when we "leave" a terminal, but not when we merely turn
> the mode off.

I'm not sure.  Do we care about:

(progn
 (xterm-mouse-mode 0)
 (discard-input)
 (read-event))

producing extra text (and returning 27)?

Thanks again for the suggestions.

Here's the current version of the patch.  I tried doing something about
suspending ttys, too, but calling (suspend-tty) currently aborts for me
on an xterm.

From 72ca13cb048a0fb328b6ff05d890759f3ab99ed4 Mon Sep 17 00:00:00 2001
From: Pip Cet <pipcet <at> protonmail.com>
Subject: [PATCH 1/2] Fix throw-without-catch on slow xterms (bug#78899)

* lisp/term/xterm.el (xterm--query-name-and-version): Don't throw if
the 'catch' has gone out of scope.
---
 lisp/term/xterm.el | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/lisp/term/xterm.el b/lisp/term/xterm.el
index 4f23a909b69..73dc15b1c25 100644
--- a/lisp/term/xterm.el
+++ b/lisp/term/xterm.el
@@ -908,14 +908,17 @@ xterm--query-name-and-version
   "Get the terminal name and version string (XTVERSION)."
   ;; Reduce query timeout time. The default value causes a noticeable
   ;; startup delay on terminals that ignore the query.
-  (let ((xterm-query-timeout 0.1))
+  (let ((too-late nil)
+        (xterm-query-timeout 0.1))
     (catch 'result
       (xterm--query
        "\e[>0q"
        `(("\eP>|" . ,(lambda ()
                        ;; The reply should be: \e P > | STRING \e \\
                        (let ((str (xterm--read-string ?\e ?\\)))
-                         (throw 'result str))))))
+                         (unless too-late
+                           (throw 'result str)))))))
+      (setq too-late t)
       nil)))
 
 (defun xterm--push-map (map basemap)
-- 
2.50.0

From 4335c11c174742e1c162c8eb1f37acd1cb29b65e Mon Sep 17 00:00:00 2001
From: Pip Cet <pipcet <at> protonmail.com>
Subject: [PATCH 2/2] Avoid unhandled mouse events on slow xterms (bug#78899)

* lisp/xt-mouse.el (turn-on-xterm-mouse-tracking-on-terminal):
Register 'kill-emacs' hook function.
(xterm-mouse--sync): New function.
(turn-off-xterm-mouse-tracking-on-terminal): Call
'xterm-mouse--sync' when disabling mouse tracking.
---
 lisp/xt-mouse.el | 53 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 51 insertions(+), 2 deletions(-)

diff --git a/lisp/xt-mouse.el b/lisp/xt-mouse.el
index 89f9bbab608..057ed7e18e8 100644
--- a/lisp/xt-mouse.el
+++ b/lisp/xt-mouse.el
@@ -528,10 +528,57 @@ turn-on-xterm-mouse-tracking-on-terminal
                    (signal (car err) (cdr err)))))
         (push enable (terminal-parameter nil 'tty-mode-set-strings))
         (push disable (terminal-parameter nil 'tty-mode-reset-strings))
+        (add-hook 'kill-emacs-hook
+                  (lambda ()
+                    (turn-off-xterm-mouse-tracking-on-terminal terminal)))
         (set-terminal-parameter terminal 'xterm-mouse-mode t)
         (set-terminal-parameter terminal 'xterm-mouse-utf-8
                                 xterm-mouse-utf-8)))))
 
+(declare-function xterm--read-string "xterm" (term1 &optional term2))
+
+(defun xterm-mouse--sync (terminal &optional timeout)
+  "Ensure that the TERMINAL is in a synchronized state, but obey TIMEOUT.
+
+When the sequence to disable mouse tracking has been sent and this
+function returns successfully, no further mouse events should be
+produced."
+  ;; This function tries very hard to handle all events that do not belong
+  ;; to the escape sequence we are looking for.
+  (setq timeout (or timeout 2.0))
+  (let ((i 0)
+        (skip (length unread-command-events))
+        events all-events)
+    ;; request Primary DA from terminal
+    (send-string-to-terminal "\e[0c" terminal)
+    ;; build two lists: ALL-EVENTS contains all events, while EVENTS
+    ;; associates character events (only) to their index in ALL-EVENTS.
+    ;; We filter out the expected escape sequence ("\e[?") from the
+    ;; ALL-EVENTS list.
+    (unwind-protect
+        (with-timeout (timeout nil)
+          (while (not
+                  (pcase events
+                    (`((?? . ,i0)
+                       (?\[ . ,i1)
+                       (?\e . ,i2) . ,_)
+                     (when (> i2 skip)
+                       (pop (nthcdr (- i i2) all-events))
+                       (pop (nthcdr (- i i1) all-events))
+                       (pop (nthcdr (- i i0) all-events))
+                       t))))
+            (let ((event (read-event)))
+              (push event all-events)
+              (incf i)
+              (when (characterp event)
+                (push (cons event i) events))))
+          ;; read the rest of the response
+          (xterm--read-string ?c))
+      ;; replay events that preceded or occurred within the escape
+      ;; sequence.
+      (setq unread-command-events
+            (append (nreverse all-events) unread-command-events)))))
+
 (defun turn-off-xterm-mouse-tracking-on-terminal (terminal)
   "Disable xterm mouse tracking on TERMINAL."
   ;; Only send the disable command to those terminals to which we've already
@@ -544,8 +591,10 @@ turn-off-xterm-mouse-tracking-on-terminal
     ;; to send it too few times (or to fail to let xterm-mouse events
     ;; pass by untranslated).
     (condition-case err
-        (send-string-to-terminal xterm-mouse-tracking-disable-sequence
-                                 terminal)
+        (progn
+          (send-string-to-terminal xterm-mouse-tracking-disable-sequence
+                                   terminal)
+          (xterm-mouse--sync terminal))
       ;; FIXME: This should use a dedicated error signal.
       (error (if (equal (cadr err) "Terminal is currently suspended")
                  nil
-- 
2.50.0






Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Sat, 05 Jul 2025 20:21:02 GMT) Full text and rfc822 format available.

Message #29 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Jared Finder <jared <at> finder.org>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: luangruo <at> yahoo.com, Pip Cet <pipcet <at> protonmail.com>, vincent <at> vinc17.net,
 Stefan Monnier <monnier <at> iro.umontreal.ca>, 78899 <at> debbugs.gnu.org
Subject: Re: bug#78899: 30.1; garbage inserted in the terminal buffer when
 quitting Emacs
Date: Sat, 05 Jul 2025 13:20:30 -0700
(I appear to have gotten dropped off the bug thread.)

On 2025-07-01 04:53, Eli Zaretskii wrote:
>> Date: Mon, 30 Jun 2025 22:47:45 +0000
>> From: Pip Cet <pipcet <at> protonmail.com>
>> Cc: vincent <at> vinc17.net, luangruo <at> yahoo.com, 78899 <at> debbugs.gnu.org
> 
> Thanks.  Jared and Stefan, any comments?

Does the most recent proposed patch work when Emacs is used across 
multiple terminals?  I expect it does not because kill-emacs-hook is 
only run when Emacs is fully killed.  I think it'd be more appropriate 
to add additional functionality to the terminal parameter 
tty-reset-strings to ensure the message is processed.  But there's a 
scary message there about these escape strings and the emergency escape 
that I do not understand the ramifications of.

  -- MJF




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#78899; Package emacs. (Wed, 09 Jul 2025 11:42:02 GMT) Full text and rfc822 format available.

Message #32 received at 78899 <at> debbugs.gnu.org (full text, mbox):

From: Pip Cet <pipcet <at> protonmail.com>
To: Jared Finder <jared <at> finder.org>
Cc: luangruo <at> yahoo.com, Eli Zaretskii <eliz <at> gnu.org>, vincent <at> vinc17.net,
 Stefan Monnier <monnier <at> iro.umontreal.ca>, 78899 <at> debbugs.gnu.org
Subject: Re: bug#78899: 30.1;
 garbage inserted in the terminal buffer when quitting Emacs
Date: Wed, 09 Jul 2025 11:40:53 +0000
"Jared Finder" <jared <at> finder.org> writes:

> On 2025-07-01 04:53, Eli Zaretskii wrote:
>>> Date: Mon, 30 Jun 2025 22:47:45 +0000
>>> From: Pip Cet <pipcet <at> protonmail.com>
>>> Cc: vincent <at> vinc17.net, luangruo <at> yahoo.com, 78899 <at> debbugs.gnu.org
>>
>> Thanks.  Jared and Stefan, any comments?
>
> Does the most recent proposed patch work when Emacs is used across
> multiple terminals?

Probably not, no.  It doesn't even work for suspending a terminal.
Working on something better, based on your suggestion below.

I'm not sure whether I'm doing it right, but if I create a second
terminal with emacsclient -nw and suspend it with C-z, Emacs crashes.
I've worked around that for now, but if it's not just a local problem,
we should probably fix it.

(I also had xterm crash once while working on the selection code.
Unfortunately, all I have is the single-line segfault report in dmesg,
which isn't very helpful.)

> I expect it does not because kill-emacs-hook is
> only run when Emacs is fully killed.  I think it'd be more appropriate
> to add additional functionality to the terminal parameter
> tty-reset-strings to ensure the message is processed.

I agree that it would be nice to push a function to the
tty-mode-reset-strings terminal parameter, and run it from C if we can
(we can't run Lisp from within an Emergency Escape).

This requires reordering two calls: suspend-tty-functions does the
actual suspending in "emacsclient -nw" instances, so it can only be run
after we reset the tty modes, and shut_down_emacs should not inhibit
Lisp until after it has reset the sys modes .  There don't appear to be
any other users that expect the order to be different.

> But there's a scary message there about these escape strings and the
> emergency escape that I do not understand the ramifications of.

I think I understand what's being said there: we definitely need an
additional flag to prevent any potentially dangerous activity such as
running Lisp, or even inspecting Lisp data that might have mark bits
that we might fail to account for.

I resurrected inhibit_lisp_code for this purpose.

I'm still seeing other problems on slow xterms: xterm--query's
synchronous branch fails to work if other input arrives after the
(discard-input) but before the response to our query, which means that
if you start typing before the initial xterm--query calls complete,
you'll get the background description as input.

Since the delay of running multiple synchronous queries is also
significant, I think it would be best to switch to asynchronous
xterm--query in all but two cases: the new synchronization method needs
to be synchronous to ensure proper ordering, and the
gui-backend-get-selection method needs to be synchronous because the API
is.

There are also some minor improvements:

* turn-off-xterm-mouse-tracking-on-terminal now removes the sequences
successfully.
* xterm--query now uninstalls the input-decode-map binding when the
timeout expires.

Here's a snapshot (including the workaround to make frame suspension
works).

diff --git a/lisp/term/xterm.el b/lisp/term/xterm.el
index 4f23a909b69..9618e44ea30 100644
--- a/lisp/term/xterm.el
+++ b/lisp/term/xterm.el
@@ -743,9 +743,10 @@ xterm-standard-colors
 
 (defun xterm--report-background-handler ()
   ;; The reply should be: \e ] 11 ; rgb: NUMBER1 / NUMBER2 / NUMBER3 \e \\
-  (let ((str (xterm--read-string ?\e ?\\)))
-    (when (string-match
-           "rgb:\\([a-f0-9]+\\)/\\([a-f0-9]+\\)/\\([a-f0-9]+\\)" str)
+  (let ((str (xterm--read-string "\e\\")))
+    (when (and str
+               (string-match
+                "rgb:\\([a-f0-9]+\\)/\\([a-f0-9]+\\)/\\([a-f0-9]+\\)" str))
       (let ((recompute-faces
              (xterm-maybe-set-dark-background-mode
               (string-to-number (match-string 1 str) 16)
@@ -763,14 +764,10 @@ xterm--report-background-handler
 
 (defun xterm--version-handler ()
   ;; The reply should be: \e [ > NUMBER1 ; NUMBER2 ; NUMBER3 c
-  ;; If the timeout is completely removed for read-event, this
-  ;; might hang for terminals that pretend to be xterm, but don't
-  ;; respond to this escape sequence.  RMS' opinion was to remove
-  ;; it completely.  That might be right, but let's first try to
-  ;; see if by using a longer timeout we get rid of most issues.
-  (let ((str (xterm--read-string ?c)))
+  (let ((str (xterm--read-string "c")))
     ;; Since xterm-280, the terminal type (NUMBER1) is now 41 instead of 0.
-    (when (string-match "\\([0-9]+\\);\\([0-9]+\\);[01]" str)
+    (when (and str
+               (string-match "\\([0-9]+\\);\\([0-9]+\\);[01]" str))
       (let ((version (string-to-number (match-string 2 str))))
         (when (and (> version 2000)
                    (or (equal (match-string 1 str) "1")
@@ -816,6 +813,22 @@ xterm--version-handler
           ;;(xterm--init-activate-get-selection)
           (xterm--init-activate-set-selection))))))
 
+(defun xterm--name-and-version-handler ()
+  ;; The reply should be: \e [ > NUMBER1 ; NUMBER2 ; NUMBER3 c
+  ;; If the timeout is completely removed for read-event, this
+  ;; might hang for terminals that pretend to be xterm, but don't
+  ;; respond to this escape sequence.  RMS' opinion was to remove
+  ;; it completely.  That might be right, but let's first try to
+  ;; see if by using a longer timeout we get rid of most issues.
+  (let ((str (xterm--read-string "\e\\")))
+    (and str
+         (not xterm-mouse-mode-called)
+         ;; Only automatically enable xterm mouse on terminals
+         ;; confirmed to still support all critical editing
+         ;; workflows (bug#74833).
+         (string-match-p xterm--auto-xt-mouse-allowed-names str)
+         (xterm-mouse-mode 1))))
+
 (defvar xterm-query-timeout 2
   "Seconds to wait for an answer from the terminal.
 Can be nil to mean \"no timeout\".")
@@ -823,100 +836,42 @@ xterm-query-timeout
 (defvar xterm-query-redisplay-timeout 0.2
   "Seconds to wait before allowing redisplay during terminal query." )
 
-(defun xterm--read-event-for-query ()
+(defun xterm--read-event-for-query (end-time)
   "Like `read-event', but inhibit redisplay.
 
 By not redisplaying right away for xterm queries, we can avoid
 unsightly flashing during initialization.  Give up and redisplay
 anyway if we've been waiting a little while."
-  (let ((start-time (current-time)))
+  (let* ((timeout (float-time (time-subtract end-time (current-time))))
+         (first-timeout (min xterm-query-redisplay-timeout timeout))
+         (second-timeout (- timeout first-timeout)))
     (or (let ((inhibit-redisplay t))
-          (read-event nil nil xterm-query-redisplay-timeout))
-        (read-event nil nil
-                    (and xterm-query-timeout
-			 (max 0 (float-time
-				 (time-subtract
-				  xterm-query-timeout
-				  (time-since start-time)))))))))
-
-(defun xterm--read-string (term1 &optional term2)
-  "Read a string with terminating characters.
-This uses `xterm--read-event-for-query' internally."
-  (let ((str "")
-        chr last)
-    (while (and (setq last chr
-                      chr (xterm--read-event-for-query))
-                (if term2
-                    (not (and (equal last term1) (equal chr term2)))
-                  (not (equal chr term1))))
-      (setq str (concat str (string chr))))
-    (if term2
-        (substring str 0 -1)
-      str)))
-
-(defun xterm--query (query handlers &optional no-async)
+          (read-event nil nil (max 0 first-timeout)))
+        (read-event nil nil (max 0 second-timeout)))))
+
+(defun xterm--query (query handlers)
   "Send QUERY string to the terminal and watch for a response.
 HANDLERS is an alist with elements of the form (STRING . FUNCTION).
 We run the first FUNCTION whose STRING matches the input events."
-  ;; We used to query synchronously, but the need to use `discard-input' is
-  ;; rather annoying (bug#6758).  Maybe we could always use the asynchronous
-  ;; approach, but it's less tested.
-  ;; FIXME: Merge the two branches.
-  (let ((register
-         (lambda (handlers)
-           (dolist (handler handlers)
-             (define-key input-decode-map (car handler)
-               (lambda (&optional _prompt)
-                 ;; Unregister the handler, since we don't expect
-                 ;; further answers.
-                 (dolist (handler handlers)
-                   (define-key input-decode-map (car handler) nil))
-                 (funcall (cdr handler))
-                 []))))))
-    (if (and (or (null xterm-query-timeout) (input-pending-p))
-             (not no-async))
-        (progn
-          (funcall register handlers)
-          (send-string-to-terminal query))
-      ;; Pending input can be mistakenly returned by the calls to
-      ;; read-event below: discard it.
-      (discard-input)
-      (send-string-to-terminal query)
-      (while handlers
-        (let ((handler (pop handlers))
-              (i 0))
-          (while (and (< i (length (car handler)))
-                      (let ((evt (xterm--read-event-for-query)))
-                        (if (and (null evt) (= i 0) (not no-async))
-                            ;; Timeout on the first event: fallback on async.
-                            (progn
-                              (funcall register (cons handler handlers))
-                              (setq handlers nil)
-                              nil)
-                          (or (eq evt (aref (car handler) i))
-                              (progn (if evt (push evt unread-command-events))
-                                     nil)))))
-            (setq i (1+ i)))
-          (if (= i (length (car handler)))
-              (progn (setq handlers nil)
-                     (funcall (cdr handler)))
-            (while (> i 0)
-              (push (aref (car handler) (setq i (1- i)))
-                    unread-command-events))))))))
-
-(defun xterm--query-name-and-version ()
-  "Get the terminal name and version string (XTVERSION)."
-  ;; Reduce query timeout time. The default value causes a noticeable
-  ;; startup delay on terminals that ignore the query.
-  (let ((xterm-query-timeout 0.1))
-    (catch 'result
-      (xterm--query
-       "\e[>0q"
-       `(("\eP>|" . ,(lambda ()
-                       ;; The reply should be: \e P > | STRING \e \\
-                       (let ((str (xterm--read-string ?\e ?\\)))
-                         (throw 'result str))))))
-      nil)))
+  (let (unregister-functions)
+    (dolist (handler handlers)
+      (let* ((binding
+              (lambda (&optional _prompt)
+                ;; Unregister the handlers, since we don't expect
+                ;; further answers.
+                (mapc #'funcall unregister-functions)
+                (funcall (cdr handler))
+                []))
+             (unregister
+              (lambda ()
+                (when (eq (lookup-key input-decode-map (car handler))
+                          binding)
+                  (define-key input-decode-map (car handler) nil)))))
+        (define-key input-decode-map (car handler) binding)
+        (push unregister unregister-functions)))
+    (send-string-to-terminal query)
+    (run-with-timer xterm-query-timeout nil
+                    (lambda () (mapc #'funcall unregister-functions)))))
 
 (defun xterm--push-map (map basemap)
   ;; Use inheritance to let the main keymaps override those defaults.
@@ -965,16 +920,9 @@ xterm--init
 
   (when xterm-set-window-title
     (xterm--init-frame-title))
-  (when (and (not xterm-mouse-mode-called)
-             ;; Only automatically enable xterm mouse on terminals
-             ;; confirmed to still support all critical editing
-             ;; workflows (bug#74833).
-             (or (string-match-p xterm--auto-xt-mouse-allowed-types
-                                 (tty-type (selected-frame)))
-                 (and-let* ((name-and-version (xterm--query-name-and-version)))
-                   (string-match-p xterm--auto-xt-mouse-allowed-names
-                                   name-and-version))))
-    (xterm-mouse-mode 1))
+  (when (not xterm-mouse-mode-called)
+    (xterm--query "\e[>0q"
+                  '(("\eP>|" . xterm--name-and-version-handler))))
   ;; Unconditionally enable bracketed paste mode: terminals that don't
   ;; support it just ignore the sequence.
   (xterm--init-bracketed-paste-mode)
@@ -1053,6 +1001,33 @@ xterm--selection-char
     ('CLIPBOARD "c")
     (_ (error "Invalid selection type: %S" type))))
 
+(defun xterm--read-string (sequence &optional unread-all-events)
+  "Read input until we see SEQUENCE.  Return string of the input characters
+before its appearance, or nil if we hit a timeout.  Non-characters are
+stored in unread-command-events.  If UNREAD-ALL-EVENTS is non-nil, all
+events are stored there."
+  (setq sequence (nreverse (append sequence nil)))
+  (let ((end-time (time-add (current-time) xterm-query-timeout))
+        (n (length sequence))
+        events chars ret)
+    (unwind-protect
+        (catch 'failure
+          (while (not (equal (take n chars) sequence))
+            (let ((event (xterm--read-event-for-query end-time)))
+              (if event
+                  (push event events)
+                (throw 'failure nil))
+              (when (characterp event)
+                (push event chars))))
+          (setq ret (concat (nreverse (nthcdr n chars)))))
+      (dolist (event events)
+        (cond ((eq event (car sequence))
+               (pop sequence))
+              ((or (not (characterp event))
+                   (not (stringp ret))
+                   unread-all-events)
+               (push event unread-command-events)))))))
+
 (cl-defmethod gui-backend-get-selection
     (type data-type
      &context (window-system nil)
@@ -1064,27 +1039,16 @@ gui-backend-get-selection
                (eql nil)))
   (unless (eq data-type 'STRING)
     (error "Unsupported data type %S" data-type))
-  (let ((query (concat "\e]52;" (xterm--selection-char type) ";")))
-    (with-temp-buffer
+  (with-temp-buffer
+    (let* ((query (concat "\e]52;" (xterm--selection-char type) ";")))
       (set-buffer-multibyte nil)
-      (xterm--query
-       ;; Use ST as query terminator to get ST as reply terminator (bug#36879).
-       (concat query "?\e\\")
-       (list (cons query
-                   (lambda ()
-                     ;; Read data up to the string terminator, ST.
-                     (let (char last)
-                       (while (and (setq char (read-char
-                                               nil nil
-                                               xterm-query-timeout))
-                                   (not (and (eq char ?\\)
-                                             (eq last ?\e))))
-                         (when last
-                           (insert last))
-                         (setq last char))))))
-       'no-async)
-      (base64-decode-region (point-min) (point-max))
-      (decode-coding-region (point-min) (point-max) 'utf-8-unix t))))
+      (send-string-to-terminal (concat query "?\e\\"))
+      (xterm--read-string query t)
+      (let ((str (xterm--read-string "\e\\")))
+        (when str
+          (insert str)
+          (base64-decode-region (point-min) (point-max))
+          (decode-coding-region (point-min) (point-max) 'utf-8-unix t))))))
 
 (cl-defmethod gui-backend-set-selection
     (type data
diff --git a/lisp/xt-mouse.el b/lisp/xt-mouse.el
index 89f9bbab608..4a4cb3a37b3 100644
--- a/lisp/xt-mouse.el
+++ b/lisp/xt-mouse.el
@@ -387,6 +387,9 @@ xterm-mouse-mode-called
   "If `xterm-mouse-mode' has been called already.
 This can be used to detect if xterm-mouse-mode was explicitly set.")
 
+(defvar xterm-mouse--terminals (make-hash-table :test 'eq :weakness 'key)
+  "Hash table of cleanups to be performed when xterm-mouse-mode is disabled.")
+
 ;;;###autoload
 (define-minor-mode xterm-mouse-mode
   "Toggle XTerm mouse mode.
@@ -507,6 +510,7 @@ xterm-mouse--tracking-sequence
 
 (defun turn-on-xterm-mouse-tracking-on-terminal (&optional terminal)
   "Enable xterm mouse tracking on TERMINAL."
+  (setq terminal (or terminal (frame-terminal)))
   (when (and xterm-mouse-mode (eq t (terminal-live-p terminal))
 	     ;; Avoid the initial terminal which is not a termcap device.
 	     ;; FIXME: is there more elegant way to detect the initial
@@ -519,19 +523,35 @@ turn-on-xterm-mouse-tracking-on-terminal
         (define-key input-decode-map "\e[M" 'xterm-mouse-translate)
         (define-key input-decode-map "\e[<" 'xterm-mouse-translate-extended))
       (let ((enable (xterm-mouse-tracking-enable-sequence))
-            (disable (xterm-mouse-tracking-disable-sequence)))
+            (disable (xterm-mouse-tracking-disable-sequence))
+            (sync (lambda () (xterm-mouse--sync terminal))))
         (condition-case err
             (send-string-to-terminal enable terminal)
           ;; FIXME: This should use a dedicated error signal.
           (error (if (equal (cadr err) "Terminal is currently suspended")
                      nil ; The sequence will be sent upon resume.
                    (signal (car err) (cdr err)))))
-        (push enable (terminal-parameter nil 'tty-mode-set-strings))
-        (push disable (terminal-parameter nil 'tty-mode-reset-strings))
+        (push enable (terminal-parameter terminal 'tty-mode-set-strings))
+        (push sync (terminal-parameter terminal 'tty-mode-reset-strings))
+        (push disable (terminal-parameter terminal 'tty-mode-reset-strings))
+        (puthash terminal (list enable disable sync) xterm-mouse--terminals)
         (set-terminal-parameter terminal 'xterm-mouse-mode t)
         (set-terminal-parameter terminal 'xterm-mouse-utf-8
                                 xterm-mouse-utf-8)))))
 
+(declare-function xterm--read-string "xterm" (sequence &optional unread-all-events))
+
+(defun xterm-mouse--sync (terminal)
+  "Ensure that the TERMINAL is in a synchronized state, but obey
+xterm-query-timeout.
+
+When the sequence to disable mouse tracking has been sent and this
+function returns successfully, no further mouse events should be
+produced."
+  (send-string-to-terminal "\e[0c" terminal)
+  (xterm--read-string "\e[?" t)
+  (xterm--read-string "c"))
+
 (defun turn-off-xterm-mouse-tracking-on-terminal (terminal)
   "Disable xterm mouse tracking on TERMINAL."
   ;; Only send the disable command to those terminals to which we've already
@@ -544,18 +564,22 @@ turn-off-xterm-mouse-tracking-on-terminal
     ;; to send it too few times (or to fail to let xterm-mouse events
     ;; pass by untranslated).
     (condition-case err
-        (send-string-to-terminal xterm-mouse-tracking-disable-sequence
-                                 terminal)
+        (progn
+          (send-string-to-terminal xterm-mouse-tracking-disable-sequence
+                                   terminal)
+          (xterm-mouse--sync terminal))
       ;; FIXME: This should use a dedicated error signal.
       (error (if (equal (cadr err) "Terminal is currently suspended")
                  nil
                (signal (car err) (cdr err)))))
-    (setf (terminal-parameter nil 'tty-mode-set-strings)
-          (remq xterm-mouse-tracking-enable-sequence
-                (terminal-parameter nil 'tty-mode-set-strings)))
-    (setf (terminal-parameter nil 'tty-mode-reset-strings)
-          (remq xterm-mouse-tracking-disable-sequence
-                (terminal-parameter nil 'tty-mode-reset-strings)))
+    (dolist (el (gethash terminal xterm-mouse--terminals))
+      (setf (terminal-parameter terminal 'tty-mode-set-strings)
+            (remq el
+                  (terminal-parameter terminal 'tty-mode-set-strings)))
+      (setf (terminal-parameter terminal 'tty-mode-reset-strings)
+            (remq el
+                  (terminal-parameter terminal 'tty-mode-reset-strings))))
+    (remhash terminal xterm-mouse--terminals)
     (set-terminal-parameter terminal 'xterm-mouse-mode nil)))
 
 (provide 'xt-mouse)
diff --git a/src/dispnew.c b/src/dispnew.c
index d65a7cbc1f1..025b1fbd99a 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -3455,8 +3455,8 @@ frames_in_reverse_z_order (struct frame *f, bool visible_only)
   struct frame *root = root_frame (f);
   Lisp_Object frames = frames_with_root (root, visible_only);
   frames = CALLN (Fsort, frames, QClessp, Qframe__z_order_lessp);
-  eassert (FRAMEP (XCAR (frames)));
-  eassert (XFRAME (XCAR (frames)) == root);
+  eassert (NILP (frames) || FRAMEP (XCAR (frames)));
+  eassert (NILP (frames) || XFRAME (XCAR (frames)) == root);
   return frames;
 }
 
@@ -3516,7 +3516,7 @@ is_tty_root_frame_with_visible_child (struct frame *f)
   if (!is_tty_root_frame (f))
     return false;
   Lisp_Object z_order = frames_in_reverse_z_order (f, true);
-  return CONSP (XCDR (z_order));
+  return CONSP (z_order) && CONSP (XCDR (z_order));
 }
 
 /* Return the index of the first enabled row in MATRIX, or -1 if there
diff --git a/src/emacs.c b/src/emacs.c
index cf8f4bd63f7..ac81886f206 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -460,6 +460,8 @@ terminate_due_to_signal (int sig, int backtrace_limit)
 	      Fkill_emacs (make_fixnum (sig), Qnil);
 	    }
 
+	  /* Prevent running of Lisp code from now on.  */
+	  inhibit_lisp_code = Qt;
           shut_down_emacs (sig, Qnil);
           emacs_backtrace (backtrace_limit);
         }
@@ -3093,6 +3095,7 @@ shut_down_emacs (int sig, Lisp_Object stuff)
   fflush (stdout);
   reset_all_sys_modes ();
 #endif
+  inhibit_lisp_code = Qt;
 
   stuff_buffered_input (stuff);
 
diff --git a/src/eval.c b/src/eval.c
index 4c514001d9d..d7d3b109bc6 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -3187,6 +3187,8 @@ safe_eval (Lisp_Object sexp)
   return safe_calln (Qeval, sexp, Qt);
 }
 
+Lisp_Object inhibit_lisp_code;
+
 /* Apply a C subroutine SUBR to the NUMARGS evaluated arguments in ARG_VECTOR
    and return the result of evaluation.  */
 
@@ -4554,6 +4556,9 @@ syms_of_eval (void)
   staticpro (&list_of_t);
   list_of_t = list1 (Qt);
 
+  inhibit_lisp_code = Qnil;
+  staticpro (&inhibit_lisp_code);
+
   defsubr (&Sor);
   defsubr (&Sand);
   defsubr (&Sif);
diff --git a/src/keyboard.c b/src/keyboard.c
index fcb66f4c58a..ed346135aaa 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -12293,7 +12293,10 @@ handle_interrupt (bool in_signal_handler)
 	  fflush (stdout);
 	}
 
+      Lisp_Object old_inhibit_lisp_code = inhibit_lisp_code;
+      inhibit_lisp_code = Qt;
       reset_all_sys_modes ();
+      inhibit_lisp_code = old_inhibit_lisp_code;
 
 #ifdef SIGTSTP
 /*
diff --git a/src/term.c b/src/term.c
index 8aa47322d19..cd6ed09c29a 100644
--- a/src/term.c
+++ b/src/term.c
@@ -194,6 +194,10 @@ tty_send_additional_strings (struct terminal *terminal, Lisp_Object sym)
           if (tty->termscript)
 	    fwrite (SDATA (string), 1, sbytes, tty->termscript);
         }
+      else if (NILP (inhibit_lisp_code) && FUNCTIONP (string))
+	{
+	  safe_calln (string);
+	}
     }
 }
 
@@ -2415,14 +2419,16 @@ DEFUN ("suspend-tty", Fsuspend_tty, Ssuspend_tty, 0, 1, 0,
 
   if (f)
     {
-      /* First run `suspend-tty-functions' and then clean up the tty
-	 state because `suspend-tty-functions' might need to change
-	 the tty state.  */
+      /* The Emacs server uses `suspend-tty-functions' to perform the
+	 actual suspension of secondary terminals.  Therefore, we must
+	 run them after resetting the terminal state.  */
       Lisp_Object term;
       XSETTERMINAL (term, t);
-      CALLN (Frun_hook_with_args, Qsuspend_tty_functions, term);
 
       reset_sys_modes (t->display_info.tty);
+
+      CALLN (Frun_hook_with_args, Qsuspend_tty_functions, term);
+
       delete_keyboard_wait_descriptor (fileno (f));
 
 #ifndef MSDOS
@@ -4861,6 +4867,8 @@ vfatal (const char *str, va_list ap)
 maybe_fatal (bool must_succeed, struct terminal *terminal,
 	     const char *str1, const char *str2, ...)
 {
+  Lisp_Object old_inhibit_lisp_code = inhibit_lisp_code;
+  inhibit_lisp_code = Qt;
   va_list ap;
   va_start (ap, str2);
 
@@ -4875,6 +4883,8 @@ maybe_fatal (bool must_succeed, struct terminal *terminal,
     vfatal (str2, ap);
   else
     verror (str1, ap);
+
+  inhibit_lisp_code = old_inhibit_lisp_code;
 }
 
 void
diff --git a/src/terminal.c b/src/terminal.c
index 668b8845029..6a2fa12f748 100644
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -412,10 +412,15 @@ DEFUN ("delete-terminal", Fdelete_terminal, Sdelete_terminal, 0, 2, 0,
   else
     safe_calln (Qrun_hook_with_args, Qdelete_terminal_functions, terminal);
 
+  Lisp_Object old_inhibit_lisp_code = inhibit_lisp_code;
+  if (EQ (force, Qnoelisp))
+    inhibit_lisp_code = Qt;
   if (t->delete_terminal_hook)
     (*t->delete_terminal_hook) (t);
   else
     delete_terminal (t);
+  if (EQ (force, Qnoelisp))
+    inhibit_lisp_code = old_inhibit_lisp_code;
 
   return Qnil;
 }





This bug report was last modified 9 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.