feat(core): port App/Surface and wire up the runnable ghostty binary
This is the integration step that turns the ported crates into a working
terminal: `cargo run` now opens a GTK window running the user's shell.
ghostty-core ports App.zig, Surface.zig (split into keyboard, mouse,
binding, clipboard and config submodules), surface_mouse.zig and
global.zig, and implements the runtime traits. perform_binding_action
handles every binding action.
Cross-crate integration:
- The core<->runtime traits (CoreApp, CoreSurface, RtApp, RtSurface,
Waker) move from apprt-gtk into ghostty_apprt::runtime_api so the core
stays runtime-agnostic; Scrollbar/RendererHealth stand-ins are replaced
by the real terminal/renderer types.
- termio now uses ghostty_renderer::state::{State, SharedState}; the
renderer calls kitty_update; image-storage-limit is applied at init
and on config change.
- The search thread locks the terminal through a TerminalLock trait so
it can share the render state mutex; termio's Wakeup gained a hook to
wake the renderer thread.
- apprt-gtk: GL proc addresses are resolved through libepoxy's
epoxy_glFoo pointer variables (plain glFoo symbols resolved to null),
default DPI fixed to 96, libadwaita version read without GTK init so
--version works headless.
The root binary dispatches +actions to ghostty-cli (list-fonts,
show-face and new-window are now wired) and otherwise runs the GTK app
with the real core.
Verified at runtime on Wayland and X11: shell prompt and colored output
render, child exit closes the window cleanly. Not yet verified: keyboard
and mouse input, clipboard, IME, tabs and splits (no key injection was
possible in this environment). GHOSTTY_DEBUG_SCREENSHOT=<png> dumps the
renderer's last frame for headless checks.
Test Plan:
- cargo test --workspace (2890 passed)
- cargo clippy --workspace --all-targets -- -D warnings
- GSETTINGS_BACKEND=memory GHOSTTY_RESOURCES_DIR=/usr/share/ghostty \
cargo run
This commit is contained in:
1 parent
9c3eeeba50
commit
3266108c10
42 files changed
+9301
-179
No files matched your search
@@ -85,3 +85,13 @@ new-window arg hook).
|
||||
* crash-report only lists (sentry is not ported). `ssh-cache` locking uses `File::try_lock`;
|
||||
like Zig, a lock held by another process is ignored (the boolean result is unused upstream).
|
||||
* `Config::load` and `finalize` are called twice in `validate-config` (as in Zig).
|
||||
|
||||
## Integration pass: list-fonts / show-face / new-window (wired)
|
||||
* `list_fonts.rs`, `show_face.rs` now use `ghostty-font` (fontconfig `Discover`, `SharedGridSet`, `SharedGrid::get_index`,
|
||||
`Collection::get_face`); ghostty-cli depends on ghostty-font and ghostty-apprt. Output is byte-identical to
|
||||
/usr/bin/ghostty for `+list-fonts` (plain, `--family`, `--bold`) and `+show-face` (`--cp`, `--string`, `--presentation`).
|
||||
Like Zig, show-face writes its "stderr" messages to stdout.
|
||||
* `new_window.rs`: `set_ipc_handler(fn(&ipc::Target, &ipc::Action) -> Result<bool, String>)` (OnceLock). The root binary must
|
||||
register it (map `ghostty_apprt_gtk::ipc::perform_ipc`'s `Err(IpcFailed)` to `Err(new_window::IPC_FAILED.into())`).
|
||||
Without a handler: prints "+new-window is not supported on this platform." and exits 1 (apprt none behaviour).
|
||||
* Still open: `build_info::set`.
|
||||
@@ -0,0 +1,47 @@
|
||||
# ghostty-core notes
|
||||
|
||||
## Keyboard (fork K) - crates/ghostty-core/src/surface/keyboard.rs
|
||||
Ported: Keyboard state, keyEventIsBinding, keyCallback, maybeHandleBinding (sequences/leader, key tables incl. `once`,
|
||||
catch_all ignore, chained actions, performable, consumed/global/all, last_trigger release suppression), endKeySequence,
|
||||
encodeKey (+ `encode_options_from_terminal` = `key_encode.Options.fromTerminal`), key-table helpers
|
||||
(`activate_key_table`, `deactivate_key_table`, `deactivate_all_key_tables`), `release_pressed_key_on_unfocus`,
|
||||
`SurfaceMouse.keyToMouseShape` (private copy, because it is only used here at the moment).
|
||||
Deviations: sequences/tables hold paths/names resolved against the current config (no raw pointers);
|
||||
global/all chained actions are re-implemented locally (`perform_all_chained_action`) as the surface has no `App` handle;
|
||||
no inspector; macOS option-as-alt skipped; `once` table deactivation calls `deactivate_key_table` directly.
|
||||
Needs from the mouse module: `Mouse.{mods: Mods, over_link: bool, hidden: bool}`, `mods_changed(Mods)`,
|
||||
`mouse_shift_capture(bool) -> bool`, `mouse_refresh_links(CursorPos, Coordinate, bool) -> Result<(), CoreError>`.
|
||||
Tests: 8 (Zig has none for this code): OwnedKeyEvent round trip, closing actions, sequence path resolution, table lookup,
|
||||
leader path, encode options from terminal modes, mouse shape for keys.
|
||||
|
||||
## Binding actions (fork B) - surface/binding.rs
|
||||
|
||||
- Ported: `Search` (+ search-thread event callback), `Text`/`dump_text`/`dump_text_locked` (viewport math in free fn `text_viewport`), `perform_binding_action` for every action, `closing_action`, `writeScreenFile` (free fns `screen_file_contents` / `write_screen_temp_file`; the temp dir is deliberately left on disk like Zig), `adjust_selection` (free fn on `&mut Terminal`).
|
||||
- App-scoped actions are mapped locally (`perform_app_action`) because `App::perform_action` needs an `App`, not the shared `AppState`; `new_window` from a surface targets the surface.
|
||||
- `toggle_mouse_reporting` rebuilds the `DerivedConfig` with a cloned `Config` (Zig mutated in place).
|
||||
- Inspector action only forwards `RtAction::Inspector` (inspector is a no-op).
|
||||
- Cross-crate change: `ghostty_terminal::search::thread::Options.terminal` is now `Arc<dyn TerminalLock>` (trait implemented for `Mutex<Terminal>`; core implements it for the shared render state) because the search thread must lock the terminal inside `ghostty_renderer::state::State`.
|
||||
- Requires from the mouse module: `link_url_at_cursor(&self) -> Option<String>` (returns the link text under the cursor incl. clipboard trimming and OSC8 URI; None when not over a link); also `mods_changed`, `mouse_shift_capture`, `mouse_refresh_links` used by keyboard.rs.
|
||||
- Tests: 7 (no Zig tests in this range).
|
||||
|
||||
## Mouse (fork M)
|
||||
Files: `crates/ghostty-core/src/surface/mouse.rs` (Mouse state, mouse reporting x10/utf8/sgr/urxvt/sgr-pixels, button/cursor/scroll/pressure callbacks, click-to-move, multi-click select, right-click actions, middle-click paste, links incl. OSC8, selection scrolling) and `src/surface_mouse.rs`.
|
||||
- Tests: `key_to_mouse_shape`, `surface_selection_logic`, `surface_rectangle_selection_logic` ported 1:1 (selection math is the pure `mouse_selection(&PageList, ..)`); 3 extra.
|
||||
- Deviations: mouse reports/IO messages/runtime actions are collected and run after the renderer lock is released (Zig held the lock and used `.locked` sends). `process_links` takes the lock itself (Zig called it unlocked). Left-release copy-on-select copies via `copy_on_select` directly: Zig's `setSelection(prev-equal)` early-returns on `eql`, which would never copy.
|
||||
- Extra API for binding fork: `link_url_at_cursor()`.
|
||||
|
||||
## Core overview (integration, agent CORE)
|
||||
Done: trait boundary moved to `ghostty_apprt::runtime_api` (apprt now depends on terminal+renderer and re-exports the real `Scrollbar`/`RendererHealth`);
|
||||
termio uses `ghostty_renderer::state::{State,SharedState}` (`SharedStateExt::lock_terminal`); renderer `update_kitty` calls `image::State::kitty_update`;
|
||||
termio applies `image-storage-limit` (init + config change) and deletes kitty images on clear-screen; `app.rs` (App.zig, `CoreApp` impl, app mailbox),
|
||||
`global.rs` (logging in Zig format, GHOSTTY_LOG, rlimits, resources dir, i18n), `surface/mod.rs` (init/teardown with renderer+IO threads, message handler,
|
||||
config updates, sizing, fonts, focus, preedit, selection, `CoreSurface` impl), `surface/{clipboard,keyboard,mouse,binding,config}.rs`, `surface_mouse.rs`; root `src/main.rs`.
|
||||
Wiring details: renderer->app via `Host` (scrollbar/health/redraw as app mailbox messages); termio->renderer via `RendererLink` + `Wakeup::with_hook`;
|
||||
apprt-gtk got `RtSurface::gl_proc_address` (libepoxy `epoxy_*` pointer variables) and `create_scope_fn` (cgroup scope over D-Bus from the IO thread).
|
||||
Deviations/stubs: inspector no-op; locale only via GTK's setlocale (no unsafe in core/root); crash reporting (sentry) not ported; `Rust version` row of `+version` empty;
|
||||
default DPI 96 (Zig linux).
|
||||
How to run: `GSETTINGS_BACKEND=memory GHOSTTY_RESOURCES_DIR=/usr/share/ghostty cargo run -- -e sh -c '...'` (debug builds only find resources via the env var here).
|
||||
Dev screenshot: `GHOSTTY_DEBUG_SCREENSHOT=/abs/out.png [GHOSTTY_DEBUG_SCREENSHOT_MS=2500]` dumps the renderer's last frame (compositor screenshots under niri come out black / are blocked).
|
||||
Verified manually: window opens on Wayland and X11, shell prompt and SGR colored output render (target/screenshots/frame.png, shell.png), child exit closes window, clean shutdown.
|
||||
NOT verified: keyboard input end-to-end (no way to inject keys into the window here; xdotool/niri did not deliver), mouse, clipboard, IME, splits/tabs.
|
||||
Known warnings at runtime: `unimplemented action=CellSize` (same as Zig GTK), cgroup scope creation fails with UnixProcessIdUnknown in this sandbox (non-fatal).
|
||||
Reference in new issue
Block a user