Skip to content

Handle mouse shape and config change Ghostty actions (#13)#17

Draft
mvbmir wants to merge 90 commits into
mvbmir/devfrom
mvbmir/monitor-ghostty-actions
Draft

Handle mouse shape and config change Ghostty actions (#13)#17
mvbmir wants to merge 90 commits into
mvbmir/devfrom
mvbmir/monitor-ghostty-actions

Conversation

@mvbmir
Copy link
Copy Markdown
Owner

@mvbmir mvbmir commented Apr 16, 2026

Summary

  • Add handler for GHOSTTY_ACTION_MOUSE_SHAPE (tag 35): converts Ghostty's cursor shape enum to CSS cursor names and applies them to the GLArea widget via set_cursor_from_name. Cursor now changes correctly for links in TUI apps, resize handles, etc.
  • Add handler for GHOSTTY_ACTION_CONFIG_CHANGE (tag 47): forwards the config object provided by Ghostty core to the appropriate ghostty_app_update_config / ghostty_surface_update_config function, enabling live config updates without a full reload.
  • Add FFI bindings: mouse shape enum constants (34 values), ghostty_action_config_change_s struct, and both union fields.

Note: GHOSTTY_ACTION_RELOAD_CONFIG (46) and GHOSTTY_ACTION_SHOW_CHILD_EXITED (54) were already handled. GHOSTTY_ACTION_COLOR_CHANGE (45) is tracked separately in #9.

Closes #13

am-will and others added 30 commits March 24, 2026 16:16
ci: add AUR publish workflow
mvbmir and others added 23 commits April 1, 2026 23:13
Replaces the light background app icon with a dark variant (#1E1E2E background) while preserving and slightly brightening the blue gradient chevron. All sizes regenerated from 512px source.
Co-authored-by: Codex <noreply@openai.com>
Replace pane reparenting with atomic split tree rebuild
Co-authored-by: Codex <noreply@openai.com>
Add a size check to package.sh that catches unoptimized libghostty
builds before they get shipped. A Debug build is ~89MB vs ~30MB for
ReleaseFast, and causes ~7x slower terminal IO throughput.
Instead of trusting whatever zig-out/ contains, package.sh now runs
the zig build itself with -Doptimize=ReleaseFast. This prevents
accidentally shipping a Debug build, which causes ~7x slower IO.
Keep the coalesced idle wakeup path from main, retain explicit GLArea renders, and redraw on remap so hidden terminals refresh when shown again.

Co-authored-by: Codex <noreply@openai.com>
Fail fast when Zig or the Ghostty submodule is missing, and document the new package.sh build flow in the README.\n\nCo-authored-by: Codex <noreply@openai.com>
Fix: reject Debug builds of libghostty in packaging
* Fix 10+ second freeze on pane split/close

Replaces surgical widget reparenting with Ghostty-style atomic tree
rebuild. The old code detached and reattached GLArea widgets on the
same tick, which breaks GTK4's GL context lifecycle.

New architecture:
- SplitNode data model is the source of truth for split layout
- SplitTreeContainer manages async rebuild: tear down old widget tree,
  then rebuild from the data model on the next idle tick
- The one-tick separation between unrealize and realize prevents the
  GLArea breakage that caused the freeze
- Scrollback is fully preserved across split/close operations

Removes ~215 lines of surgical reparenting code from window.rs,
replaced by data model mutations that delegate to the container.

* Wire control socket to GTK UI

Connects the existing limux-control Unix socket server to the GTK
window, enabling programmatic workspace management from external
tools (limux-cli, scripts, editor integrations).

New control_bridge module starts a socket listener in a background
thread and dispatches commands to the GTK main loop via polling.
Supported commands: workspace.create, workspace.list,
workspace.rename, workspace.activate, workspace.close,
surface.send_text.

Also adds TerminalHandle::send_text() for injecting text into
terminal surfaces, and pane::first_terminal_handle() for resolving
the active terminal in a workspace.

* Update app icon to dark mode variant

Replaces the light background app icon with a dark variant (#1E1E2E background) while preserving and slightly brightening the blue gradient chevron. All sizes regenerated from 512px source.

* Fix wrapped workspace root pane resolution

Co-authored-by: Codex <noreply@openai.com>

* host: coalesce ghostty wakeup idles

Co-authored-by: Codex <noreply@openai.com>

* Reject Debug builds of libghostty in packaging script

Add a size check to package.sh that catches unoptimized libghostty
builds before they get shipped. A Debug build is ~89MB vs ~30MB for
ReleaseFast, and causes ~7x slower terminal IO throughput.

* Build libghostty with ReleaseFast automatically in package.sh

Instead of trusting whatever zig-out/ contains, package.sh now runs
the zig build itself with -Doptimize=ReleaseFast. This prevents
accidentally shipping a Debug build, which causes ~7x slower IO.

* host: reconcile render throttling with main

Keep the coalesced idle wakeup path from main, retain explicit GLArea renders, and redraw on remap so hidden terminals refresh when shown again.

Co-authored-by: Codex <noreply@openai.com>

* Clarify packaging prerequisites

Fail fast when Zig or the Ghostty submodule is missing, and document the new package.sh build flow in the README.\n\nCo-authored-by: Codex <noreply@openai.com>

* host: wire control socket into GTK UI

Align the GTK host bridge with the current control protocol, remove the CLI fallback that would double-run workspace commands, and dispatch control commands onto the GLib main context directly.

Co-authored-by: Codex <noreply@openai.com>

* host: drop unused control helper

Remove the stale pane helper pulled in from the original PR branch so the host bridge stays warning-free under stricter checks.

Co-authored-by: Codex <noreply@openai.com>

---------

Co-authored-by: am.will <42459108+am-will@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <noreply@openai.com>
Ctrl+Tab and Ctrl+Shift+Tab are the standard tab cycling shortcuts
across browsers and most terminal emulators. The current defaults
(Ctrl+Shift+Right/Left) are less discoverable and conflict with
text selection shortcuts in many editors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Save the current font size to ~/.config/limux/settings.json whenever
the user changes it via Ctrl+/-, and restore it on new terminal creation.

Closes am-will#43
The connect_map handler in apply_split_ratio_after_layout fired on
every workspace switch, overwriting user-adjusted split positions with
the stale ratio captured at build time. Now it only fires once for the
initial layout.

Closes #2
Track font size on the Rust side by reading the default from
the ghostty config file and applying deltas on increase/decrease.
Removes the ghostty_surface_font_size C API export and reverts the
submodule pointer so builds work without a custom ghostty fork.
Both position_notify handlers (data model + session persistence) now
skip updates when the Paned widget is unmapped. GTK fires position
events with stale values during unmap/remap cycles, corrupting the
stored ratio when switching workspaces.
Zoom in/out/reset now applies to every terminal surface across all
workspaces and tabs, not just the focused one.
The previous is_mapped() guard was insufficient. GTK fires position_notify
events with stale positions during the remap transition while is_mapped()
is already true. Two changes fix this:

1. Add an applying flag that suppresses position_notify during programmatic
   set_position calls (initial layout and workspace re-map).

2. Re-apply the current data model ratio on every map event instead of only
   once, so workspace switches always restore the correct split positions.
- Change default increase font size keybinding from <Ctrl>plus to
  <Ctrl>equal so it matches Ctrl+= without requiring Shift
- Broadcast zoom changes to all terminals across all workspaces/tabs
- Fix split ratio corruption with applying flag to suppress
  position_notify during programmatic set_position calls
The is_mapped() check in position_notify handlers prevented GTK from
recording the initial position during split creation (new Paneds aren't
mapped yet when first laid out). The applying flag alone is sufficient
to prevent ratio corruption during workspace switches.
The idle callback for initial layout now captures the ratio as a plain
f64 instead of reading from the shared cell. This prevents early
position_notify events (which fire with position=0 before layout)
from corrupting the ratio before the idle callback runs.

The connect_map handler still reads from the cell so workspace switches
restore drag-adjusted ratios correctly.
Add handlers for GHOSTTY_ACTION_MOUSE_SHAPE (tag 35) and
GHOSTTY_ACTION_CONFIG_CHANGE (tag 47). Mouse shape converts the
Ghostty cursor enum to CSS cursor names and applies them to the
GLArea widget. Config change forwards the provided config to the
appropriate update function without freeing it (owned by Ghostty core).

Closes #13
@mvbmir mvbmir marked this pull request as draft April 16, 2026 18:35
mvbmir added 4 commits April 17, 2026 01:38
Top bar (always visible):
- Sidebar toggle icon, new-workspace "+" icon, workspace indicator pills
  (active/hover/unread states), minimize/maximize/close window buttons
- Custom buttons instead of gtk::WindowControls so hover shape, padding,
  and border-radius match the pane bar
- Empty header space drags the window via gtk::WindowHandle

Pane header:
- Drag filler between the last tab and the action icons (a WindowHandle
  inside tab_overlay) so empty space moves the window without conflicting
  with the tabs' DragSource
- Middle-click on a tab closes it
- Ctrl+W now closes the active tab instead of the whole pane; the pane
  (and workspace) still collapses when the last tab closes, via the
  existing on_empty callback path

Sidebar:
- Drops the "WORKSPACES" title and the big "+" button at the bottom
- Each row has a close X on the top-right and the favorite star moved
  underneath; both reveal on hover/selected
- Cleaner row styling, single-layer selection highlight (the default
  ListBox row background is suppressed so only the inner row box paints)

New-workspace UX:
- "+" clones the active workspace's folder instead of opening the folder
  picker; the picker only shows on first run / when no workspace exists

Fix: GHOSTTY_ACTION_CONFIG_CHANGE handler is now a no-op. Calling
ghostty_*_update_config from inside the action callback re-fires the
same action and recursed until the stack overflowed, preventing the
app from starting.
Follow-up to the cmux redesign (#5), adding persistent user settings
and fixing a pile of spacing/split-ratio bugs.

Settings:
- General → "Top bar" (switch). When off, removes the top bar entirely
  and relocates its buttons: dock toggle, settings cog, +, and window
  controls move into a new WindowHandle-wrapped sidebar header (with an
  hexpand spacer separating the left and right groups). When the sidebar
  is collapsed, the dock toggle parks on the active workspace's leading
  pane (before the first tab) via a new `leading_box` slot on every pane.
- General → "Window controls side" (Left/Right). Moves minimize/maximize/
  close between the two ends of whichever header is active (top bar or
  sidebar header). Applies live.
- Both persist under `interface.*` in settings.json.

Workspace row:
- Close X in the top-right (replaces the old star), star moved below
  next to the folder path. Both 20×20, halign End, hover-to-reveal.
- Double-click a row triggers inline rename.

Pane & tabs:
- Settings cog removed from per-pane action row; now lives in the top
  bar / sidebar header, freeing tab space in every pane.
- Middle-click a tab closes it (same path as the close X).
- Ctrl+W closes the active tab; the pane only collapses when the last
  tab closes (via the existing on_empty callback).
- Pane header now draggable: a WindowHandle filler fills the space
  between the last tab and the action icons, so empty header space
  moves the window without conflicting with tab DnD.

New workspace UX:
- Clicking "+" clones the active workspace's folder instead of opening
  the folder picker. The picker only appears on first run / when no
  workspace exists.

Split ratio fix (the big one):
- gtk::Paned's `position` is absolute pixels, so any width change
  (sidebar toggle, window resize) made position-notify fire with a
  wrong ratio = position / new_width, corrupting the saved ratio and
  drifting splits from 50/50 over time.
- Fixes:
  * shrink_start_child / shrink_end_child on every Paned so the ratio
    wins over larger child minimums (e.g. wide tab strips).
  * position-notify tracks `last_size`; if width changed since the last
    notify it's an auto-adjust (skip), otherwise it's a real user drag
    (update ratio).
  * Per-frame tick callback observes the paned's actual width and
    re-applies `position = ratio * new_width` on size changes.
    GtkWidget's `width` / `height` properties don't reliably emit
    notify across GTK 4.x versions, so polling is intentional; the
    check is O(1).
  * Startup uses a one-shot tick callback to apply the ratio once the
    paned first has a non-zero allocation.

Spacing / visual polish:
- Custom minimize/maximize/close buttons replace gtk::WindowControls
  (Adwaita's 24px circular defaults couldn't be overridden cleanly).
  Same .limux-top-bar-btn class as the other top bar icons →
  consistent square hover with matching padding.
- Pane action icons and tab close X aligned to the same CSS pattern
  (border-radius: 6px, margin 0 1px, valign Center).
- First workspace row gets a 4px top margin so the above-first gap
  visually matches the between-workspace gap.
- sidebar_drag_area hidden in both top-bar and no-top-bar modes since
  the respective headers already handle window drag.
- Border-bottom on the sidebar header removed.

Structural refactors:
- Extracted handle_config_change() to dedupe the appearance + top-bar
  + save/revert logic that ran in two places.
- Inlined apply_window_controls_side's single delegating wrapper.
- Dropped the unused on_config_changed field from PaneCallbacks (now
  that the settings cog lives in the top bar, no pane opens the dialog).
- toggle_top_bar (keyboard shortcut) now calls apply_top_bar_mode too,
  so buttons don't get stranded when the user toggles off via Ctrl+Shift+M.
  apply_top_bar_mode respects both `show_top_bar` (persistent) and
  `top_bar_visible` (transient).

cargo fmt / cargo clippy -D warnings / cargo test all clean.
The keyboard-shortcut side of middle-click tab close is being pulled
into its own PR that properly routes close-tab through the configurable
shortcut system (adds CloseTab shortcut, remaps CloseFocusedPane to
Ctrl+Alt+W, updates the close-pane button tooltip to reflect whichever
binding is active).

Keeps on this branch:
- Middle-click a tab still closes it (uses remove_tab directly)
- All other redesign + settings work

Reverts from this branch:
- Ctrl+W → close_active_tab_in_pane. Restored to the original
  close-pane behavior.
- pub fn close_active_tab_in_pane — helper not needed here; will be
  re-added in the follow-up PR.
Some users may prefer to navigate workspaces solely via the sidebar.
Add a dedicated "Workspace indicators on the top bar" switch in
General settings that hides the pills without hiding the top bar.

- New field: InterfaceConfig.show_workspace_indicators (default true)
- Persisted under interface.show_workspace_indicators in settings.json
- apply_top_bar_mode hides each pill (not the container box) so
  indicator_box keeps its hexpand spacer role between the left group
  and the window controls on the right
- Called from workspace creation paths (add_workspace_from_state,
  create_workspace_for_tab) so newly-added pills honor the setting
mvbmir pushed a commit that referenced this pull request Apr 17, 2026
Credit: implementation approach adapted from #17 by @threehymns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants