From 0b817211148498e8e067a5bdb5f646c19cba36d8 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Sun, 19 Jul 2026 22:33:55 +0000 Subject: [PATCH 45/71] =?UTF-8?q?fix(macos):=20tick=20the=20clock=20on=20m?= =?UTF-8?q?ouse=20events=20=E2=80=94=20reliable=20click=20selection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Double-click word selection and triple-click line selection were unreliable (did nothing, selected the wrong line, or left stale selections). Root cause: the engine detects single/double/triple clicks from the wall-clock delta between presses (_currentTime - _lastClick), and _currentTime is advanced only by Terminal::tick(). This frontend ticked only at render time, so at mouse-press time the clock was stale, the inter-click delta was meaningless, and the engine's speed-click counter never reliably reached the word/line case. Match what the Qt frontend does: TerminalSession::sendMouse{Press,Move, Release}Event each call terminal().tick(steady_clock::now()) before forwarding. Do the same in the bridge's three mouse handlers. That is the whole fix — the engine's own click-timing then drives linear/word/line selection as designed; the frontend does not reimplement selection. An earlier draft that fed AppKit's clickCount and built WordWise/FullLine selections directly was reverted in favour of this upstream-aligned tick. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01AeG2jwYMX5gdvvtUnx3PJa --- src/contour_macos/SessionBridge.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/contour_macos/SessionBridge.cpp b/src/contour_macos/SessionBridge.cpp index 6d37c3bb..11f8265f 100644 --- a/src/contour_macos/SessionBridge.cpp +++ b/src/contour_macos/SessionBridge.cpp @@ -214,8 +214,18 @@ void bridge_send_char(TerminalSession* session, uint32_t codepoint, uint32_t mod std::chrono::steady_clock::now()); } +// Every mouse handler ticks the terminal clock to now() before forwarding, exactly as the Qt +// frontend does (TerminalSession::sendMouse*Event). This is load-bearing for selection: the engine +// detects single/double/triple clicks (linear/word/line selection) from the wall-clock delta +// between presses (_currentTime - _lastClick), and _currentTime is ONLY advanced by tick(). On the +// Qt path every mouse event ticks first, so the delta is real; without it _currentTime is stale +// (it only advances at render time here), the delta is meaningless, and double/triple-click never +// reliably reaches the word/line-selection case. Ticking here is the whole fix — the engine's own +// _speedClicks logic then does word/line selection as designed; the frontend does not reimplement it. + void bridge_mouse_press(TerminalSession* session, int x, int y, uint32_t modifiers) { + session->terminal().tick(std::chrono::steady_clock::now()); session->terminal().sendMousePressEvent(toKeyboardModifiers(modifiers).chord, vtbackend::MouseButton::Left, vtbackend::PixelCoordinate { { x }, { y } }, @@ -224,6 +234,7 @@ void bridge_mouse_press(TerminalSession* session, int x, int y, uint32_t modifie void bridge_mouse_move(TerminalSession* session, int x, int y, uint32_t modifiers) { + session->terminal().tick(std::chrono::steady_clock::now()); // The engine derives the cell from the pixel position; pass a best-effort cell too. auto const cell = session->pixelToCell(x, y); session->terminal().sendMouseMoveEvent(toKeyboardModifiers(modifiers).chord, @@ -234,6 +245,7 @@ void bridge_mouse_move(TerminalSession* session, int x, int y, uint32_t modifier void bridge_mouse_release(TerminalSession* session, int x, int y, uint32_t modifiers) { + session->terminal().tick(std::chrono::steady_clock::now()); session->terminal().sendMouseReleaseEvent(toKeyboardModifiers(modifiers).chord, vtbackend::MouseButton::Left, vtbackend::PixelCoordinate { { x }, { y } },