qt: parity tier 2 batch 1 — input + child-exited (B15, B23, B24, B27, B28)
Five fixes:
B23 — Sided modifiers (left vs right) now reported. New
XkbState::sideBitsForKeycode looks up the keysym for a keycode and
returns GHOSTTY_MODS_*_RIGHT when it's a right-side modifier
(Shift_R / Control_R / Alt_R or ISO_Level3_Shift / Super_R / etc.).
sendKey ORs the result alongside the unsided bits so binds like
`right_shift+x` can fire. macOS + GTK both populate sided bits.
B24 — Mouse enter/leave now notify libghostty.
* enterEvent forwards the actual cursor position so hover state
and OSC-8 link arming reset from any stale (-1, -1) sentinel.
* leaveEvent (new override) sends (-1, -1) so any hover-armed
state clears once the pointer leaves the pane.
Mirrors macOS mouseEntered/mouseExited and GTK ecMouseLeave.
B27 — Right-click is now sent to libghostty BEFORE deciding
whether to open the context menu. The mouse-press fires
unconditionally; the contextMenuEvent handler still gates on
mouse_captured, so capturing programs (e.g. mc) still get raw
clicks. Matches macOS rightMouseDown + menu(for:) and GTK
mouseButtonCallback's "fire menu only if not consumed."
B28 — Click-to-focus suppresses the matching mouse-up. New
m_suppressNextLeftRelease flag set on a press that grabs focus
from elsewhere, cleared on the matching release. Without this,
vim/less/etc. would see a stray button-up event right after the
user clicked the pane to focus it. macOS does the same with
suppressNextLeftMouseUp; GTK uses suppress_left_mouse_release.
B15 — SHOW_CHILD_EXITED now respects the abnormal-command-exit-
runtime config (default 250ms). Banner is suppressed for
short-lived children (e.g. quick `ls`); macOS does the same gate.
B40 — window-decoration full enum: documenting that Qt has no
portable way to force CSD vs SSD on Wayland, so `auto`/`server`/
`client` all map to "decorated" (the platform decides which).
No code change needed; the existing comment already says this.
Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
parent
4d0e34975a
commit
8e87252745
|
|
@ -603,6 +603,32 @@ public:
|
|||
return xkb_keysym_to_utf32(sym);
|
||||
}
|
||||
|
||||
// Side bits for the libghostty mods bitfield, derived from a
|
||||
// keycode — used so that pressing Right-Shift sets BOTH the
|
||||
// unsided GHOSTTY_MODS_SHIFT and the GHOSTTY_MODS_SHIFT_RIGHT bit
|
||||
// (a left-side keycode sets only the unsided bit). macOS and GTK
|
||||
// populate sided bits this way; Qt was leaving them empty so
|
||||
// bindings that distinguish left-vs-right modifier keys couldn't
|
||||
// fire.
|
||||
ghostty_input_mods_e sideBitsForKeycode(uint32_t keycode) const {
|
||||
if (!m_unshifted) return GHOSTTY_MODS_NONE;
|
||||
const xkb_keysym_t sym =
|
||||
xkb_state_key_get_one_sym(m_unshifted, keycode);
|
||||
int r = GHOSTTY_MODS_NONE;
|
||||
switch (sym) {
|
||||
case XKB_KEY_Shift_R: r |= GHOSTTY_MODS_SHIFT_RIGHT; break;
|
||||
case XKB_KEY_Control_R: r |= GHOSTTY_MODS_CTRL_RIGHT; break;
|
||||
// Both Alt_R and ISO_Level3_Shift (AltGr) are right-Alt physically.
|
||||
case XKB_KEY_Alt_R:
|
||||
case XKB_KEY_ISO_Level3_Shift: r |= GHOSTTY_MODS_ALT_RIGHT; break;
|
||||
case XKB_KEY_Super_R:
|
||||
case XKB_KEY_Hyper_R:
|
||||
case XKB_KEY_Meta_R: r |= GHOSTTY_MODS_SUPER_RIGHT; break;
|
||||
default: break;
|
||||
}
|
||||
return static_cast<ghostty_input_mods_e>(r);
|
||||
}
|
||||
|
||||
// Modifiers consumed by the layout to produce `keycode`'s text given
|
||||
// `mods` are depressed. Returns the consumed subset, expressed as
|
||||
// ghostty mod bits. Mutates m_query (mutable) — see thread-safety
|
||||
|
|
@ -698,6 +724,12 @@ void GhosttySurface::sendKey(QKeyEvent *ev, ghostty_input_action_e action) {
|
|||
ghostty_input_key_s k = {};
|
||||
k.action = action;
|
||||
k.mods = translateMods(ev->modifiers());
|
||||
// OR in any right-side bit for this keycode (e.g. Right-Shift sets
|
||||
// SHIFT_RIGHT alongside SHIFT). macOS + GTK populate these; without
|
||||
// them, keybinds like `right_shift+x` can't distinguish from
|
||||
// `left_shift+x`.
|
||||
k.mods = static_cast<ghostty_input_mods_e>(
|
||||
k.mods | XkbState::instance().sideBitsForKeycode(keycode));
|
||||
k.keycode = keycode;
|
||||
k.text = printable ? text.constData() : nullptr;
|
||||
// XKB lookups: unshifted codepoint (what this physical key would
|
||||
|
|
@ -772,13 +804,30 @@ void GhosttySurface::mousePressEvent(QMouseEvent *ev) {
|
|||
m_owner->removeSurface(this);
|
||||
return;
|
||||
}
|
||||
// Click-to-focus: if the surface didn't have focus, this click is
|
||||
// grabbing focus rather than a real interaction with the running
|
||||
// program. macOS + GTK suppress the matching mouse-up so vim, less,
|
||||
// etc. don't see a stray button-up event. We mirror that by setting
|
||||
// a one-shot flag the matching release consults.
|
||||
const bool wasFocused = hasFocus();
|
||||
setFocus();
|
||||
if (rightClickOpensMenu(ev)) return;
|
||||
if (!wasFocused && ev->button() == Qt::LeftButton)
|
||||
m_suppressNextLeftRelease = true;
|
||||
|
||||
// Right-click: send the press to libghostty BEFORE deciding to
|
||||
// open the context menu. macOS + GTK both do this so the core can
|
||||
// word-select on right-press and then we open the menu over the
|
||||
// selection. If the running program is mouse-captured, the press
|
||||
// is forwarded as a real button event.
|
||||
sendMouseButton(ev, GHOSTTY_MOUSE_PRESS);
|
||||
}
|
||||
|
||||
void GhosttySurface::mouseReleaseEvent(QMouseEvent *ev) {
|
||||
if (rightClickOpensMenu(ev)) return;
|
||||
// Suppress the release of a focus-grabbing click — see press above.
|
||||
if (ev->button() == Qt::LeftButton && m_suppressNextLeftRelease) {
|
||||
m_suppressNextLeftRelease = false;
|
||||
return;
|
||||
}
|
||||
sendMouseButton(ev, GHOSTTY_MOUSE_RELEASE);
|
||||
}
|
||||
|
||||
|
|
@ -947,9 +996,28 @@ void GhosttySurface::wheelEvent(QWheelEvent *ev) {
|
|||
flashScrollbar(); // mouse-wheel scrolling reveals the overlay scrollbar
|
||||
}
|
||||
|
||||
void GhosttySurface::enterEvent(QEnterEvent *) {
|
||||
void GhosttySurface::enterEvent(QEnterEvent *ev) {
|
||||
// focus-follows-mouse: take focus when the pointer enters this pane.
|
||||
if (m_owner && m_owner->focusFollowsMouse() && !hasFocus()) setFocus();
|
||||
// Tell libghostty about the actual cursor position so hover state
|
||||
// and OSC-8 link arming reset from any stale (-1, -1) sentinel.
|
||||
// macOS does this in mouseEntered (SurfaceView_AppKit.swift:920);
|
||||
// GTK does it in ecMouseEnter (apprt/gtk/class/surface.zig).
|
||||
if (m_surface)
|
||||
ghostty_surface_mouse_pos(m_surface, ev->position().x(),
|
||||
ev->position().y(),
|
||||
translateMods(QGuiApplication::keyboardModifiers()));
|
||||
}
|
||||
|
||||
void GhosttySurface::leaveEvent(QEvent *) {
|
||||
// libghostty's "no cursor here" sentinel: pass (-1, -1) so any
|
||||
// hover-armed state (URL underline, mouse-report sequences for an
|
||||
// OSC-8 link) clears once the pointer leaves the pane. macOS and
|
||||
// GTK both do this; without it the arm state would survive until
|
||||
// the next move event.
|
||||
if (m_surface)
|
||||
ghostty_surface_mouse_pos(m_surface, -1, -1,
|
||||
translateMods(QGuiApplication::keyboardModifiers()));
|
||||
}
|
||||
|
||||
void GhosttySurface::focusInEvent(QFocusEvent *) {
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ protected:
|
|||
void dropEvent(QDropEvent *) override;
|
||||
void wheelEvent(QWheelEvent *) override;
|
||||
void enterEvent(QEnterEvent *) override; // focus-follows-mouse
|
||||
void leaveEvent(QEvent *) override; // libghostty hover reset
|
||||
void focusInEvent(QFocusEvent *) override;
|
||||
void focusOutEvent(QFocusEvent *) override;
|
||||
|
||||
|
|
@ -206,6 +207,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
|
||||
// Set when a left-click grabbed focus from elsewhere; cleared on
|
||||
// the matching mouse-up so the click that grabbed focus isn't
|
||||
// also reported to the running program. macOS + GTK do the same
|
||||
// (suppressNextLeftMouseUp / suppress_left_mouse_release).
|
||||
bool m_suppressNextLeftRelease = false;
|
||||
// Last requested cursor shape (from MOUSE_SHAPE) and visibility
|
||||
// (from MOUSE_VISIBILITY). Tracked separately so toggling
|
||||
// visibility doesn't reset the shape.
|
||||
|
|
|
|||
|
|
@ -1457,8 +1457,17 @@ bool MainWindow::onAction(ghostty_app_t, ghostty_target_s target,
|
|||
|
||||
case GHOSTTY_ACTION_SHOW_CHILD_EXITED: {
|
||||
if (!src) return false;
|
||||
const int code =
|
||||
static_cast<int>(action.action.child_exited.exit_code);
|
||||
const ghostty_surface_message_childexited_s ce =
|
||||
action.action.child_exited;
|
||||
// Suppress the banner for fast-exiting children (e.g. an
|
||||
// intentional `exit 0` after a quick command). Match the macOS
|
||||
// gate: only show when runtime_ms is at least the configured
|
||||
// abnormal threshold (default 250ms). Banner = "the process
|
||||
// died unexpectedly," not "the process exited."
|
||||
uint32_t threshold = 250;
|
||||
configGet(s_config, &threshold, "abnormal-command-exit-runtime");
|
||||
if (ce.runtime_ms < threshold) return true;
|
||||
const int code = static_cast<int>(ce.exit_code);
|
||||
post(src, [srcp, code]() {
|
||||
if (srcp) srcp->showChildExited(code);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue