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
@@ -18,12 +18,13 @@ use ghostty_config::types_b::{LinuxCgroup, ShellIntegration};
|
||||
use ghostty_datastruct::blocking_queue::Timeout;
|
||||
use ghostty_renderer::message::Message as RendererMessage;
|
||||
use ghostty_renderer::size::{CellSize, Padding, ScreenSize, Size};
|
||||
use ghostty_termio::SharedStateExt;
|
||||
use ghostty_termio::backend::{self, Backend};
|
||||
use ghostty_termio::exec;
|
||||
use ghostty_termio::message::Message;
|
||||
use ghostty_termio::termio::DerivedConfig;
|
||||
use ghostty_termio::{
|
||||
Mailbox, Options, RendererMailbox, RendererState, SurfaceMailbox, Termio, Thread, Wakeup,
|
||||
Mailbox, Options, RendererMailbox, SharedState, SurfaceMailbox, Termio, Thread, Wakeup,
|
||||
};
|
||||
|
||||
struct SurfaceRec(Sender<SurfaceMessage>);
|
||||
@@ -69,7 +70,7 @@ fn size(cols: u32, rows: u32) -> Size {
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
state: Arc<RendererState>,
|
||||
state: SharedState,
|
||||
mailbox: Mailbox,
|
||||
stop: Wakeup,
|
||||
thread: Option<JoinHandle<()>>,
|
||||
@@ -136,7 +137,7 @@ fn sh(script: &str) -> ConfigCommand {
|
||||
|
||||
impl Fixture {
|
||||
fn text(&self) -> String {
|
||||
self.state.lock().plain_string().unwrap()
|
||||
self.state.lock_terminal().plain_string().unwrap()
|
||||
}
|
||||
|
||||
fn send(&self, msg: Message) {
|
||||
@@ -218,7 +219,7 @@ fn printf_output_reaches_terminal_and_exit_is_reported() {
|
||||
|
||||
// The SGR was applied: the cursor pen is red (palette 1).
|
||||
{
|
||||
let t = f.state.lock();
|
||||
let t = f.state.lock_terminal();
|
||||
let style = t.screen().cursor.style;
|
||||
assert!(
|
||||
!matches!(style.fg_color, ghostty_terminal::style::Color::None),
|
||||
@@ -308,7 +309,7 @@ fn linefeed_mode_translates_cr() {
|
||||
f.wait_text("a^M");
|
||||
// cat -v got "a\r\n", so it printed a newline after the ^M.
|
||||
f.wait_for("cursor on second row", |f| {
|
||||
(f.state.lock().screen().cursor.y == 1).then_some(())
|
||||
(f.state.lock_terminal().screen().cursor.y == 1).then_some(())
|
||||
});
|
||||
f.stop();
|
||||
}
|
||||
@@ -346,7 +347,7 @@ fn missing_input_file_shows_error_and_thread_keeps_draining() {
|
||||
f.wait_text("A configured `input` path was not found");
|
||||
assert!(
|
||||
!f.state
|
||||
.lock()
|
||||
.lock_terminal()
|
||||
.modes
|
||||
.get(ghostty_vt::modes::Mode::CursorVisible)
|
||||
);
|
||||
@@ -366,7 +367,7 @@ fn resize_messages_are_coalesced_and_applied_to_the_pty() {
|
||||
f.send(Message::Resize(size(100, 40)));
|
||||
f.wait_text("40 100");
|
||||
{
|
||||
let t = f.state.lock();
|
||||
let t = f.state.lock_terminal();
|
||||
assert_eq!((t.cols, t.rows), (100, 40));
|
||||
assert_eq!(t.width_px, 1000);
|
||||
assert_eq!(t.height_px, 800);
|
||||
@@ -385,7 +386,7 @@ fn synchronized_output_is_force_reset_by_timer() {
|
||||
let mut f = start(sh("printf '\\033[?2026h'; sleep 5"), |_| {});
|
||||
f.wait_for("sync output on", |f| {
|
||||
f.state
|
||||
.lock()
|
||||
.lock_terminal()
|
||||
.modes
|
||||
.get(ghostty_vt::modes::Mode::SynchronizedOutput)
|
||||
.then_some(())
|
||||
@@ -393,7 +394,7 @@ fn synchronized_output_is_force_reset_by_timer() {
|
||||
let started = Instant::now();
|
||||
f.wait_for("sync output reset", |f| {
|
||||
(!f.state
|
||||
.lock()
|
||||
.lock_terminal()
|
||||
.modes
|
||||
.get(ghostty_vt::modes::Mode::SynchronizedOutput))
|
||||
.then_some(())
|
||||
@@ -462,7 +463,7 @@ fn focus_events_are_sent_when_enabled() {
|
||||
);
|
||||
f.wait_for("focus mode", |f| {
|
||||
f.state
|
||||
.lock()
|
||||
.lock_terminal()
|
||||
.modes
|
||||
.get(ghostty_vt::modes::Mode::FocusEvent)
|
||||
.then_some(())
|
||||
@@ -529,7 +530,7 @@ fn writes_after_exit_are_ignored_and_thread_stays_alive_until_stopped() {
|
||||
f.send(Message::Focused(true));
|
||||
f.send(Message::Resize(size(90, 25)));
|
||||
f.wait_for("resize after exit", |f| {
|
||||
(f.state.lock().cols == 90).then_some(())
|
||||
(f.state.lock_terminal().cols == 90).then_some(())
|
||||
});
|
||||
f.stop();
|
||||
}
|
||||
@@ -542,11 +543,11 @@ fn change_config_message_updates_palette_and_colors() {
|
||||
cfg.enquiry_response = "x".into();
|
||||
f.send(Message::ChangeConfig(Box::new(cfg)));
|
||||
f.wait_for("background change", |f| {
|
||||
let t = f.state.lock();
|
||||
let t = f.state.lock_terminal();
|
||||
(t.colors.background.default == Some(ghostty_vt::color::Rgb { r: 1, g: 2, b: 3 }))
|
||||
.then_some(())
|
||||
});
|
||||
assert!(f.state.lock().flags.dirty.palette);
|
||||
assert!(f.state.lock_terminal().flags.dirty.palette);
|
||||
f.stop();
|
||||
}
|
||||
|
||||
@@ -571,7 +572,11 @@ fn process_output_manual_api() {
|
||||
termio.process_output(b"manual\x1b[31m!");
|
||||
assert_eq!(
|
||||
"manual!",
|
||||
termio.renderer_state().lock().plain_string().unwrap()
|
||||
termio
|
||||
.renderer_state()
|
||||
.lock_terminal()
|
||||
.plain_string()
|
||||
.unwrap()
|
||||
);
|
||||
termio.process_output(b"\x1b[5n");
|
||||
let m = termio.mailbox().pop().unwrap();
|
||||
|
||||
Reference in new issue
Block a user