Don't always enable xterm-mouse-mode (bug#74833)

Many terminals set the environment variable TERM to "xterm" even when they
don't support all functionality in xterm.  This means that enabling
xterm-mouse-mode can break critical editing workflows like copy/paste.  This
adds checks for the specific terminal Emacs is run in and only enables
xterm-mouse-mode on terminals knows to support all critical editing workflows.

* etc/NEWS: Update announcement
* lisp/term/xterm.el (xterm--auto-xt-mouse-allowed-names)
(xterm--auto-xt-mouse-allowed-types): New variables to control
what terminals automatically enable xterm-mouse-mode.
(xterm--report-background-handler, xterm--version-handler): Use
xterm--read-string.
(xterm--read-string, xterm--query-name-and-version): New
function.
(xterm--init): Check what terminal is running and if
xterm-mouse-mode was manually called.
* lisp/xt-mouse.el (xterm-mouse-mode-called): New variable.
(xterm-mouse-mode): Set xterm-mouse-mode-called.  Mention
automatic call by xterm--init.  Delete outdated comment text.
This commit is contained in:
Jared Finder 2025-01-01 22:36:25 -08:00 committed by Eli Zaretskii
parent b63ba892f9
commit ba6429081a
3 changed files with 103 additions and 28 deletions

View file

@ -34,11 +34,15 @@ incorrectly in rare cases.
* Startup Changes in Emacs 31.1
** When run inside xterm, 'xterm-mouse-mode' is turned on by default.
This means that the mouse will work by default inside xterm terminals.
If your terminal does not behave properly with xterm mouse tracking
enabled, you can disable mouse tracking by putting '(xterm-mouse-mode
-1)' in your init file.
** In compatible terminals, 'xterm-mouse-mode' is turned on by default.
For these terminals the mouse will work by default. A compatible
terminal is one that supports Emacs seting and getting the OS selection
data (a.k.a. the clipboard) and mouse button and motion events. With
xterm-mouse-mode enabled, you must use Emacs keybindings to copy to the
OS selection instead of terminal-specific keybindings.
You can keep the old behavior by putting `(xterm-mouse-mode -1)' in your
init file.
* Changes in Emacs 31.1

View file

@ -83,6 +83,39 @@ capabilities, and only when that terminal understands bracketed paste."
(defconst xterm-paste-ending-sequence "\e[201~"
"Characters sent by the terminal to end a bracketed paste.")
(defconst xterm--auto-xt-mouse-allowed-names
(mapconcat (lambda (s) (concat "^" s "\\>"))
'("Konsole"
"WezTerm"
;; "XTerm" ;Disabled because OSC52 support is opt-in only.
"iTerm2" ;OSC52 support has opt-in/out UI on first usage
"kitty")
"\\|")
"Regexp for terminals that automatically enable `xterm-mouse-mode' at startup.
This will get matched against the terminal's XTVERSION string.
It is expected that any matching terminal supports the following
functionality:
\"Set selection data\" (OSC52): Allows Emacs to set the OS clipboard.
\"Get selection data\" (OSC52 or bracketed paste): Allows Emacs to get
the contents of the OS clipboard.
\"Basic mouse mode\" (DECSET1000): Allows Emacs to get events on mouse
clicks.
\"Mouse motion mode\" (DECSET1003): Allows Emacs to get event on mouse
motion.
Also see `xterm--auto-xt-mouse-allowed-types' which mtches against the
value of TERM instead.")
(defconst xterm--auto-xt-mouse-allowed-types
(mapconcat (lambda (s) (concat "^" s "$"))
'("alacritty"
"contour")
"\\|")
"Like `xterm--auto-xt-mouse-allowed-names', but for the terminal's type.
This will get matched against the environment variable \"TERM\".")
(defun xterm--pasted-text ()
"Handle the rest of a terminal paste operation.
Return the pasted text as a string."
@ -707,11 +740,8 @@ Return the pasted text as a string."
"Names of 16 standard xterm/aixterm colors, their numbers, and RGB values.")
(defun xterm--report-background-handler ()
(let ((str "")
chr)
;; The reply should be: \e ] 11 ; rgb: NUMBER1 / NUMBER2 / NUMBER3 \e \\
(while (and (setq chr (xterm--read-event-for-query)) (not (equal chr ?\\)))
(setq str (concat str (string chr))))
;; 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 ((recompute-faces
@ -730,16 +760,13 @@ Return the pasted text as a string."
(tty-set-up-initial-frame-faces))))))
(defun xterm--version-handler ()
(let ((str "")
chr)
;; 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.
(while (and (setq chr (xterm--read-event-for-query)) (not (equal chr ?c)))
(setq str (concat str (string chr))))
;; 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)))
;; Since xterm-280, the terminal type (NUMBER1) is now 41 instead of 0.
(when (string-match "\\([0-9]+\\);\\([0-9]+\\);[01]" str)
(let ((version (string-to-number (match-string 2 str))))
@ -810,6 +837,21 @@ anyway if we've been waiting a little while."
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)
"Send QUERY string to the terminal and watch for a response.
HANDLERS is an alist with elements of the form (STRING . FUNCTION).
@ -860,6 +902,20 @@ We run the first FUNCTION whose STRING matches the input events."
(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)))
(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
@ -907,7 +963,15 @@ We run the first FUNCTION whose STRING matches the input events."
(when xterm-set-window-title
(xterm--init-frame-title))
(when xterm-mouse-mode
(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))
;; Unconditionally enable bracketed paste mode: terminals that don't
;; support it just ignore the sequence.

View file

@ -362,19 +362,26 @@ which is the \"1006\" extension implemented in Xterm >= 277."
(set-terminal-parameter nil 'xterm-mouse-frame frame)
(setq last-input-event event)))))
;;;###autoload
(defvar xterm-mouse-mode-called nil
"If `xterm-mouse-mode' has been called already.
This can be used to detect if xterm-mouse-mode was explicitly set.")
;;;###autoload
(define-minor-mode xterm-mouse-mode
"Toggle XTerm mouse mode.
Turn it on to use Emacs mouse commands, and off to use xterm mouse commands.
This works in terminal emulators compatible with xterm. It only
works for simple uses of the mouse. Basically, only non-modified
single clicks are supported. When turned on, the normal xterm
mouse functionality for such clicks is still available by holding
down the SHIFT key while pressing the mouse button."
Turn it on to use Emacs mouse commands, and off to use xterm mouse
commands. This works in terminal emulators compatible with xterm. When
turned on, the normal xterm mouse functionality for such clicks is still
available by holding down the SHIFT key while pressing the mouse button.
On text terminals that Emacs knows are compatible with the mouse as well
as other critical editing functionality, this is automatically turned on
at startup. See Info node `(elisp)Terminal-Specific' and `xterm--init'."
:global t :group 'mouse
:init-value t
:version "31.1"
(setq xterm-mouse-mode-called t)
(funcall (if xterm-mouse-mode 'add-hook 'remove-hook)
'terminal-init-xterm-hook
'turn-on-xterm-mouse-tracking-on-terminal)