From a48ff0fb89cde170f1aa8e53a431780d4ada45b4 Mon Sep 17 00:00:00 2001 From: ntomsic Date: Wed, 20 May 2026 20:11:27 -0500 Subject: [PATCH] =?UTF-8?q?qt:=20parity=20batch=202=20=E2=80=94=20mouse=20?= =?UTF-8?q?input=20(B8,=20B19,=20B25,=20B26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four fixes from PARITY.md tier 1 covering mouse-related actions: B8 + B26 — MOUSE_VISIBILITY no longer clobbers the cursor shape. GhosttySurface now tracks the requested shape (m_cursorShape) and visibility (m_mouseVisible) separately. setShape() sets the shape AND applies it if visible; setMouseVisible() flips visibility while preserving the shape. The action handler calls these instead of QWidget::setCursor directly. Previously, un-hiding the cursor reset it to ArrowCursor and lost any earlier MOUSE_SHAPE request from a running program. B19 — Mouse buttons 4-11 (back/forward/etc.) now delivered. Qt::ExtraButton1..ExtraButton8 map to GHOSTTY_MOUSE_FOUR..ELEVEN. Previously every side button fell to GHOSTTY_MOUSE_UNKNOWN and was silently dropped. macOS handles NSEvent buttonNumber 3-10; GTK handles GDK button 4-11 (apprt/gtk/class/surface.zig:3713). B25 — MOUSE_SHAPE was applied via setCursor with no preservation; now flows through setShape() which records the request. Combined with B26 above, the cursor shape requested by a program (e.g. vim's IBeam, hover-over-link Pointer) is preserved across hide/show cycles. B29 (XKB live layout) deferred to a follow-up — the right fix needs to listen for keyboard-layout changes from Qt or wl_keyboard, not read XKB_DEFAULT_LAYOUT once at process start. Co-Authored-By: claude-flow --- qt/src/GhosttySurface.cpp | 23 +++++++++++++++++++++++ qt/src/GhosttySurface.h | 14 ++++++++++++++ qt/src/MainWindow.cpp | 12 +++++++----- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/qt/src/GhosttySurface.cpp b/qt/src/GhosttySurface.cpp index 909810813..f1bf4c0ed 100644 --- a/qt/src/GhosttySurface.cpp +++ b/qt/src/GhosttySurface.cpp @@ -319,6 +319,17 @@ void GhosttySurface::flashBorder() { }); } +void GhosttySurface::setShape(Qt::CursorShape shape) { + m_cursorShape = shape; + if (m_mouseVisible) setCursor(shape); +} + +void GhosttySurface::setMouseVisible(bool visible) { + if (m_mouseVisible == visible) return; + m_mouseVisible = visible; + setCursor(visible ? m_cursorShape : Qt::BlankCursor); +} + // A small translucent overlay label (key-sequence / resize display). static QLabel *makeOverlayLabel(QWidget *parent) { auto *label = new QLabel(parent); @@ -712,6 +723,18 @@ void GhosttySurface::sendMouseButton(QMouseEvent *ev, case Qt::LeftButton: button = GHOSTTY_MOUSE_LEFT; break; case Qt::RightButton: button = GHOSTTY_MOUSE_RIGHT; break; case Qt::MiddleButton: button = GHOSTTY_MOUSE_MIDDLE; break; + // Side / extra buttons (back, forward, etc.). macOS handles + // NSEvent buttonNumber 3-10 and GTK handles GDK button 4-11; + // Qt's ExtraButton1..ExtraButton8 cover the same hardware. The + // libghostty C ABI defines FOUR..ELEVEN, so map by index. + case Qt::ExtraButton1: button = GHOSTTY_MOUSE_FOUR; break; + case Qt::ExtraButton2: button = GHOSTTY_MOUSE_FIVE; break; + case Qt::ExtraButton3: button = GHOSTTY_MOUSE_SIX; break; + case Qt::ExtraButton4: button = GHOSTTY_MOUSE_SEVEN; break; + case Qt::ExtraButton5: button = GHOSTTY_MOUSE_EIGHT; break; + case Qt::ExtraButton6: button = GHOSTTY_MOUSE_NINE; break; + case Qt::ExtraButton7: button = GHOSTTY_MOUSE_TEN; break; + case Qt::ExtraButton8: button = GHOSTTY_MOUSE_ELEVEN; break; default: button = GHOSTTY_MOUSE_UNKNOWN; break; } ghostty_surface_mouse_button(m_surface, state, button, diff --git a/qt/src/GhosttySurface.h b/qt/src/GhosttySurface.h index 6e1cf9506..39b4d1199 100644 --- a/qt/src/GhosttySurface.h +++ b/qt/src/GhosttySurface.h @@ -103,6 +103,15 @@ public: void setBellTitle(bool marked) { m_bellTitle = marked; } bool bellTitle() const { return m_bellTitle; } + // Set the cursor shape from the libghostty MOUSE_SHAPE action. + // Tracks the requested shape so MOUSE_VISIBILITY toggles can hide + // and restore without forgetting it. macOS+GTK preserve shape + // across visibility changes; the previous Qt code clobbered it + // with Qt::ArrowCursor on un-hide. + void setShape(Qt::CursorShape shape); + // Hide or show the mouse cursor without changing its shape. + void setMouseVisible(bool visible); + protected: bool event(QEvent *) override; void paintEvent(QPaintEvent *) override; @@ -197,6 +206,11 @@ private: bool m_notifyOnCommand = false; // one-shot: notify on next cmd finish bool m_bellFlash = false; // bell `border` flash in progress bool m_bellTitle = false; // unacknowledged bell `title` mark + // Last requested cursor shape (from MOUSE_SHAPE) and visibility + // (from MOUSE_VISIBILITY). Tracked separately so toggling + // visibility doesn't reset the shape. + Qt::CursorShape m_cursorShape = Qt::IBeamCursor; + bool m_mouseVisible = true; // Tracks whether the prior inputMethodEvent reported active preedit. // Used to distinguish a real post-composition commit (forward to the // terminal) from the duplicate ASCII commit that Wayland's diff --git a/qt/src/MainWindow.cpp b/qt/src/MainWindow.cpp index 8fb01530a..22da145a2 100644 --- a/qt/src/MainWindow.cpp +++ b/qt/src/MainWindow.cpp @@ -1511,7 +1511,7 @@ bool MainWindow::onAction(ghostty_app_t, ghostty_target_s target, const Qt::CursorShape shape = mouseShapeToCursor(action.action.mouse_shape); post(src, [srcp, shape]() { - if (srcp) srcp->setCursor(shape); + if (srcp) srcp->setShape(shape); }); return true; } @@ -1570,10 +1570,12 @@ bool MainWindow::onAction(ghostty_app_t, ghostty_target_s target, case GHOSTTY_ACTION_MOUSE_VISIBILITY: { if (!src) return false; - const bool hidden = - action.action.mouse_visibility == GHOSTTY_MOUSE_HIDDEN; - post(src, [srcp, hidden]() { - if (srcp) srcp->setCursor(hidden ? Qt::BlankCursor : Qt::ArrowCursor); + const bool visible = + action.action.mouse_visibility != GHOSTTY_MOUSE_HIDDEN; + post(src, [srcp, visible]() { + // setMouseVisible preserves the requested shape so toggling + // doesn't reset to ArrowCursor. + if (srcp) srcp->setMouseVisible(visible); }); return true; }