Skip to content

Commit a46c784

Browse files
committed
Forward scroll wheel events to TUI apps with mouse tracking
When a terminal application enables mouse tracking (e.g. opencode, htop, less), scroll wheel events were always handled by ghostel's viewport scrolling and never forwarded to the application. This made scrolling inside TUI apps non-functional. Try to forward wheel-up/down as mouse button 4/5 press events via the existing mouse encoder. When mouse tracking is active the encoder sends the event to the app; when inactive it returns nil and we fall back to the previous viewport scroll behaviour. Fixes #60
1 parent 512a4db commit a46c784

2 files changed

Lines changed: 106 additions & 16 deletions

File tree

ghostel.el

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,31 +1070,53 @@ pasted using bracketed paste."
10701070
(when (and ghostel--process (process-live-p ghostel--process))
10711071
(process-send-string ghostel--process "\f"))))
10721072

1073-
(defun ghostel--scroll-up (&optional _event)
1074-
"Scroll the terminal viewport up (into scrollback)."
1073+
(defun ghostel--forward-scroll-event (event button)
1074+
"Try to forward a scroll EVENT as mouse BUTTON to the terminal.
1075+
Return non-nil if the event was forwarded (mouse tracking is active)."
1076+
(when (and event ghostel--term ghostel--process
1077+
(process-live-p ghostel--process)
1078+
(not ghostel--copy-mode-active))
1079+
(let* ((posn (event-start event))
1080+
(col-row (posn-col-row posn))
1081+
(col (car col-row))
1082+
(row (cdr col-row)))
1083+
(ghostel--mouse-event ghostel--term
1084+
0 ; press
1085+
button
1086+
row col
1087+
(ghostel--mouse-mods event)))))
1088+
1089+
(defun ghostel--scroll-up (&optional event)
1090+
"Scroll the terminal viewport up (into scrollback).
1091+
When the terminal has mouse tracking enabled, forward EVENT as a
1092+
scroll event to the running application instead."
10751093
(interactive "e")
10761094
(if ghostel--copy-mode-full-buffer
10771095
(scroll-down 3)
10781096
(when ghostel--term
1079-
(ghostel--scroll ghostel--term -3)
1080-
(if ghostel--copy-mode-active
1081-
(let ((inhibit-read-only t))
1082-
(ghostel--redraw ghostel--term ghostel-full-redraw))
1083-
(setq ghostel--force-next-redraw t)
1084-
(ghostel--invalidate)))))
1097+
(unless (ghostel--forward-scroll-event event 4) ; button 4 = scroll up
1098+
(ghostel--scroll ghostel--term -3)
1099+
(if ghostel--copy-mode-active
1100+
(let ((inhibit-read-only t))
1101+
(ghostel--redraw ghostel--term ghostel-full-redraw))
1102+
(setq ghostel--force-next-redraw t)
1103+
(ghostel--invalidate))))))
10851104

1086-
(defun ghostel--scroll-down (&optional _event)
1087-
"Scroll the terminal viewport down."
1105+
(defun ghostel--scroll-down (&optional event)
1106+
"Scroll the terminal viewport down.
1107+
When the terminal has mouse tracking enabled, forward EVENT as a
1108+
scroll event to the running application instead."
10881109
(interactive "e")
10891110
(if ghostel--copy-mode-full-buffer
10901111
(scroll-up 3)
10911112
(when ghostel--term
1092-
(ghostel--scroll ghostel--term 3)
1093-
(if ghostel--copy-mode-active
1094-
(let ((inhibit-read-only t))
1095-
(ghostel--redraw ghostel--term ghostel-full-redraw))
1096-
(setq ghostel--force-next-redraw t)
1097-
(ghostel--invalidate)))))
1113+
(unless (ghostel--forward-scroll-event event 5) ; button 5 = scroll down
1114+
(ghostel--scroll ghostel--term 3)
1115+
(if ghostel--copy-mode-active
1116+
(let ((inhibit-read-only t))
1117+
(ghostel--redraw ghostel--term ghostel-full-redraw))
1118+
(setq ghostel--force-next-redraw t)
1119+
(ghostel--invalidate))))))
10981120

10991121
(defun ghostel-copy-mode-scroll-up ()
11001122
"Scroll the terminal viewport up by a page in copy mode."

test/ghostel-test.el

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,6 +1648,72 @@ cell, so the visual line width must equal the terminal column count."
16481648
(should-not scroll-bottom-called)
16491649
(should-not ghostel--force-next-redraw))))
16501650

1651+
(ert-deftest ghostel-test-scroll-forwards-mouse-tracking ()
1652+
"Scroll-up/down forward events when mouse tracking is active."
1653+
(let ((ghostel--term 'fake)
1654+
(ghostel--process 'fake)
1655+
(ghostel--copy-mode-active nil)
1656+
(ghostel--copy-mode-full-buffer nil)
1657+
(ghostel--force-next-redraw nil)
1658+
(mouse-event-args nil)
1659+
(scroll-called nil)
1660+
;; Fake wheel-up event at row 5, col 10
1661+
(fake-event `(wheel-up (,(selected-window) 1 (10 . 5) 0))))
1662+
;; Mouse tracking active: ghostel--mouse-event returns non-nil
1663+
(cl-letf (((symbol-function 'ghostel--mouse-event)
1664+
(lambda (_term action button row col mods)
1665+
(setq mouse-event-args (list action button row col mods))
1666+
t))
1667+
((symbol-function 'ghostel--scroll)
1668+
(lambda (_term _delta) (setq scroll-called t)))
1669+
((symbol-function 'process-live-p) (lambda (_p) t)))
1670+
(ghostel--scroll-up fake-event)
1671+
(should mouse-event-args)
1672+
(should (equal 0 (nth 0 mouse-event-args))) ; action = press
1673+
(should (equal 4 (nth 1 mouse-event-args))) ; button 4 = scroll up
1674+
(should (equal 5 (nth 2 mouse-event-args))) ; row
1675+
(should (equal 10 (nth 3 mouse-event-args))) ; col
1676+
(should-not scroll-called))
1677+
;; Reset and test scroll-down with a wheel-down event
1678+
(setq mouse-event-args nil scroll-called nil)
1679+
(let ((fake-down-event `(wheel-down (,(selected-window) 1 (10 . 5) 0))))
1680+
(cl-letf (((symbol-function 'ghostel--mouse-event)
1681+
(lambda (_term action button row col mods)
1682+
(setq mouse-event-args (list action button row col mods))
1683+
t))
1684+
((symbol-function 'ghostel--scroll)
1685+
(lambda (_term _delta) (setq scroll-called t)))
1686+
((symbol-function 'process-live-p) (lambda (_p) t)))
1687+
(ghostel--scroll-down fake-down-event)
1688+
(should mouse-event-args)
1689+
(should (equal 5 (nth 1 mouse-event-args))) ; button 5 = scroll down
1690+
(should-not scroll-called)))))
1691+
1692+
(ert-deftest ghostel-test-scroll-fallback-no-mouse-tracking ()
1693+
"Scroll-up/down fall back to viewport scroll when mouse tracking is off."
1694+
(let ((ghostel--term 'fake)
1695+
(ghostel--process 'fake)
1696+
(ghostel--copy-mode-active nil)
1697+
(ghostel--copy-mode-full-buffer nil)
1698+
(ghostel--force-next-redraw nil)
1699+
(scroll-delta nil)
1700+
(fake-up-event `(wheel-up (,(selected-window) 1 (10 . 5) 0)))
1701+
(fake-down-event `(wheel-down (,(selected-window) 1 (10 . 5) 0))))
1702+
(cl-letf (((symbol-function 'ghostel--mouse-event)
1703+
(lambda (_term _action _button _row _col _mods) nil))
1704+
((symbol-function 'ghostel--scroll)
1705+
(lambda (_term delta) (setq scroll-delta delta)))
1706+
((symbol-function 'ghostel--invalidate) #'ignore)
1707+
((symbol-function 'process-live-p) (lambda (_p) t)))
1708+
(ghostel--scroll-up fake-up-event)
1709+
(should (equal -3 scroll-delta))
1710+
(should ghostel--force-next-redraw)
1711+
;; Reset and test scroll-down fallback
1712+
(setq scroll-delta nil ghostel--force-next-redraw nil)
1713+
(ghostel--scroll-down fake-down-event)
1714+
(should (equal 3 scroll-delta))
1715+
(should ghostel--force-next-redraw))))
1716+
16511717
(ert-deftest ghostel-test-control-key-bindings ()
16521718
"All non-exception C-<letter> keys should be bound in ghostel-mode-map."
16531719
(dolist (c (number-sequence ?a ?z))
@@ -1868,6 +1934,8 @@ cell, so the visual line width must equal the terminal column count."
18681934
ghostel-test-scroll-on-input-self-insert
18691935
ghostel-test-scroll-on-input-send-event
18701936
ghostel-test-scroll-on-input-disabled
1937+
ghostel-test-scroll-forwards-mouse-tracking
1938+
ghostel-test-scroll-fallback-no-mouse-tracking
18711939
ghostel-test-control-key-bindings
18721940
ghostel-test-meta-key-bindings
18731941
ghostel-test-copy-mode-recenter

0 commit comments

Comments
 (0)