qt: parity batch 2 — mouse input (B8, B19, B25, B26)

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 <ruv@ruv.net>
pull/12846/head
ntomsic 2026-05-20 20:11:27 -05:00
parent 33b5dee468
commit a48ff0fb89
3 changed files with 44 additions and 5 deletions

View File

@ -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,

View File

@ -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

View File

@ -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;
}