linux: wire keyboard, mouse, and focus input to libghostty#240
linux: wire keyboard, mouse, and focus input to libghostty#240Jesssullivan merged 3 commits intomainfrom
Conversation
Add GTK4 event controllers (key, motion, click, scroll, focus) to the terminal surface widget and forward events to libghostty via ghostty_surface_key, ghostty_surface_mouse_button/pos/scroll, and ghostty_surface_set_focus. Includes GDK→ghostty modifier and button translation. This makes the terminal interactive — keyboard input and mouse interaction now reach the terminal process.
Greptile SummaryThis PR wires all GTK4 input (keyboard, mouse, scroll, focus) to libghostty via event controllers, and adds action handlers for close/quit/maximize/child-exit/config in the Linux port. Signal handler signatures, modifier translation, and button mapping are all correct. Two P2 items worth tracking before the Linux port stabilises:
Confidence Score: 4/5Safe to merge for the MVP milestone; two P2 issues should be tracked before the Linux port reaches broad use. All new signal wiring is structurally correct and the action handlers are sound. Two P2 findings exist: a correctness gap in keyboard protocol data (unshifted_codepoint) and a per-close memory leak that accumulates over a multiplexer session. Neither blocks basic operation but both affect reliability over time. cmux-linux/src/surface.zig — unshifted_codepoint computation and missing Surface deallocation on close. Important Files Changed
Sequence DiagramsequenceDiagram
participant GTK as GTK4 Event Controllers
participant Surface as surface.zig (Surface)
participant Ghostty as libghostty
GTK->>Surface: key-pressed (keyval, keycode, state)
Surface->>Surface: gtkModsToGhostty(state)
Surface->>Surface: gdk_keyval_to_unicode(keyval) → text
Surface->>Ghostty: ghostty_surface_key(key_event)
GTK->>Surface: GtkGestureClick pressed (n_press, x, y)
Surface->>Surface: gtk_widget_grab_focus()
Surface->>Surface: gtkButtonToGhostty(button)
Surface->>Ghostty: ghostty_surface_mouse_pos(x, y, mods)
Surface->>Ghostty: ghostty_surface_mouse_button(PRESS, button, mods)
GTK->>Surface: motion (x, y)
Surface->>Ghostty: ghostty_surface_mouse_pos(x, y, mods)
GTK->>Surface: scroll (dx, dy)
Surface->>Ghostty: ghostty_surface_mouse_scroll(dx, dy, scroll_mods)
GTK->>Surface: focus enter
Surface->>Ghostty: ghostty_surface_set_focus(true)
Ghostty->>Surface: close_surface_cb(userdata)
Surface->>Surface: ws.removePanel() [Surface struct not freed ⚠]
Reviews (2): Last reviewed commit: "linux: add close, quit, maximize, child-..." | Re-trigger Greptile |
| const key_event = c.ghostty.ghostty_input_key_s{ | ||
| .action = action, | ||
| .mods = mods, | ||
| .consumed_mods = c.ghostty.GHOSTTY_MODS_NONE, |
There was a problem hiding this comment.
consumed_mods always zero may cause double-modifier effects
Hardcoding consumed_mods = GHOSTTY_MODS_NONE means ghostty never learns that a modifier was "consumed" producing the text character. For Shift+letter ghostty would see both the shift modifier and the uppercase letter, and may emit an extra modifier keypress sequence. Ideally, when text_len > 0 and the keyval differs from its lowercase version, GHOSTTY_MODS_SHIFT should be included in consumed_mods. This matches the approach in ghostty/src/apprt/gtk/Surface.zig where consumed mods are derived from the GTK key event's consumed_modifiers.
There was a problem hiding this comment.
Ditto, I think we should take note of this
Cast action param and mods return with @intcast to match ghostty C API unsigned types. Discard gtk_widget_grab_focus return value.
Handle 10 more libghostty actions: close_window, close_all_windows, quit, toggle_maximize, set_tab_title, show_child_exited, renderer_health, color_change, reload_config, config_change. Total coverage now 23 of ~40 runtime-relevant actions.
Address Greptile P2 findings from PR #240: - Set consumed_mods to include SHIFT when text was produced from a shifted keyval, preventing double-modifier effects in ghostty. - Translate GDK_MOD2_MASK (Num Lock) to GHOSTTY_MODS_NUM so numpad-aware programs see correct modifier state.
…241) * linux: implement split pane actions (new, goto, resize, equalize) Wire 5 split management actions from libghostty: - new_split: create terminal in focused pane via split_tree.splitPane - goto_split: navigate between panes (next/prev/directional) - resize_split: adjust split ratio via findResizeSplit - equalize_splits: reset all ratios to 0.5 - toggle_split_zoom: placeholder (logs, returns true) Add split_tree helpers: collectLeaves, adjacentLeaf, equalize, applyRatios. Rebuild workspace widget tree after mutations via AdwTabView page replacement with idle ratio application. * fix: use named TraversalDirection enum for cross-file type compatibility * fix: address Greptile P1/P2 findings on split actions P1: fix use-after-free in applyRatiosIdle — carry workspace ID instead of raw root pointer, re-lookup in callback. P1: fix orphaned panel on splitPane OOM — find target node before creating panel, clean up on failure path. P2: toggle_split_zoom returns false (unhandled) until implemented. P2: log OOM in collectLeaves instead of silent swallow. * linux: fix consumed_mods for shift and add Num Lock translation Address Greptile P2 findings from PR #240: - Set consumed_mods to include SHIFT when text was produced from a shifted keyval, preventing double-modifier effects in ghostty. - Translate GDK_MOD2_MASK (Num Lock) to GHOSTTY_MODS_NUM so numpad-aware programs see correct modifier state. * fix: define GDK_MOD2_MASK locally for Num Lock translation The GDK_MOD2_MASK constant is a C macro not always visible through Zig's @cImport. Define it locally as 0x10 (1 << 4), the standard value for Num Lock on Linux. * fix: address Greptile P1 findings on widget reparenting and resize delta - Unparent leaf widgets before buildWidget so GTK4's parent assertion is satisfied (close old tab page first, then detach leaves from old GtkPaneds before building the new tree). - Use actual pane size instead of hardcoded 1000px divisor for resize_split delta-to-ratio conversion. * linux: update split tree on surface close, add move_tab action - onCloseSurface now updates the split tree when a pane is closed (shell exit, close-surface callback), promoting the sibling node and rebuilding the widget tree. - Add MOVE_TAB action handler for tab reordering via keybinds. * fix: grab GTK focus on new pane after split Greptile P1: after new_split, the data model set focused_panel_id to the new panel but GTK keyboard focus remained on the old pane. Add gtk_widget_grab_focus on the new widget after rebuild.
Summary
GtkEventControllerKey→ghostty_surface_keywith GDK keyval→text conversion and modifier translationGtkGestureClick(all buttons) →ghostty_surface_mouse_buttonwith position trackingGtkEventControllerMotion→ghostty_surface_mouse_posGtkEventControllerScroll(both axes + discrete) →ghostty_surface_mouse_scrollGtkEventControllerFocusenter/leave →ghostty_surface_set_focusgtkModsToGhostty(modifier translation),gtkButtonToGhostty(button number mapping)This is the critical missing piece that makes the terminal interactive — keyboard input and mouse events now reach the terminal process. Combined with PR #237 (close/title/pwd) and PR #238 (actions/clipboard), the Linux port now has a functional input→render→clipboard pipeline.
Test plan
lessorman🤖 Generated with Claude Code