Files
ghostty-rs/crates/ghostty-apprt-gtk/src/app.rs
T
ddidderr 69d823cbc7 feat(apprt-gtk): port the GTK4/libadwaita app runtime
Ports ZIG/src/apprt/gtk into ghostty-apprt-gtk: every GObject class
(application, window, tab, split tree, surface + scrolled window,
child-exited banner, search/key-state/resize overlays, command palette,
global shortcuts, all dialogs, config wrapper) as glib subclasses with
the same GType names, properties, signals and template children; GDK
key translation; X11 and Wayland window-protocol helpers (blur, CSD,
quick terminal via gtk4-layer-shell); single-instance D-Bus IPC;
gsettings; cgroup scope creation.

build.rs compiles Ghostty's blueprints with blueprint-compiler and
bundles UI, CSS and icons into a gresource, as Ghostty's build does.

The core is not ported yet, so the crate defines the boundary it needs
in src/core.rs (CoreApp, CoreSurface, RtApp, RtSurface, Waker); the
spec is in docs/rewrite/notes/ghostty-apprt-gtk.md. ghostty-core will
implement these traits (the crate no longer depends on core to avoid a
cycle). examples/demo.rs runs the app with a dummy core that clears the
GLArea; it launches on Wayland without GTK criticals.

Stubs/open items: toggle_visibility, float_window, secure_input and
close_all_windows return false; inspector is a no-op; X11/Wayland
protocol code is untested against real compositors; gtk4-rs doesn't
expose the GLArea framebuffer id, so the renderer must query it.

Also registers ghostty-apprt and ghostty-cli in workspace.dependencies.

Test Plan:
- cargo test -p ghostty-apprt-gtk (36 tests)
- DEMO_QUIT_AFTER_MS=3000 cargo run -p ghostty-apprt-gtk --example demo
2026-10-11 22:57:15 +02:00

90 lines
2.7 KiB
Rust

//! Port of `apprt/gtk/App.zig`: the apprt `App` handed to the core. It is a
//! thin wrapper around the [`Application`] GObject.
//!
//! ```ignore
//! let app = App::new(core_app); // loads config, creates GtkApplication
//! app.run()?; // main loop (ticks core_app)
//! app.terminate();
//! ```
use std::rc::Rc;
use std::sync::Arc;
use ghostty_apprt::action::{Action, Target};
use ghostty_apprt::ipc;
use crate::class::application::{Application, RunError};
use crate::core::{CoreApp, SurfaceId, Waker};
/// This is detected by the Renderer, in which case it sends a
/// `redraw_surface` message so that we can call `drawFrame` ourselves from
/// the app thread, because GTK's `GLArea` does not support drawing from a
/// different thread.
pub const MUST_DRAW_FROM_APP_THREAD: bool = true;
/// GTK application ID (Zig: `App.application_id`).
pub use crate::APPLICATION_ID as application_id;
/// GTK object path (Zig: `App.object_path`).
pub use crate::OBJECT_PATH as object_path;
/// The [`Waker`] given to core threads: wakes the GLib main context so the
/// app loop runs `CoreApp::tick`.
#[derive(Debug, Default, Clone, Copy)]
pub struct GlibWaker;
impl Waker for GlibWaker {
fn wake(&self) {
gtk::glib::MainContext::default().wakeup();
}
}
/// A new thread-safe waker (create it before the core app, hand it to core).
#[must_use]
pub fn waker() -> Arc<dyn Waker> {
Arc::new(GlibWaker)
}
/// The apprt App.
pub struct App {
/// The GObject Application instance.
pub app: Application,
}
impl App {
/// `App.init`: creates the GTK application (loads the configuration,
/// initializes GTK/libadwaita).
#[must_use]
pub fn new(core_app: Rc<dyn CoreApp>) -> Self {
Self {
app: Application::new(core_app),
}
}
/// `App.run`: run until quit.
pub fn run(&self) -> Result<(), RunError> {
self.app.run_loop()
}
/// `App.terminate`: force deinitialize the app (other things tend to
/// still hold references, so this forces the disposal now).
pub fn terminate(&self) {
self.app.deinit();
}
/// `App.wakeup`: wake up the event loop (thread safe).
pub fn wakeup(&self) {
self.app.wakeup();
}
/// `App.performAction`.
pub fn perform_action(&self, target: Target<SurfaceId>, action: Action) -> bool {
self.app.perform_action(target, action)
}
/// `App.performIpc`: send the IPC to a running Ghostty. Static: it's
/// called from a CLI process without a full-featured apprt App.
pub fn perform_ipc(target: &ipc::Target, action: &ipc::Action) -> Result<bool, ipc::Error> {
crate::ipc::perform_ipc(target, action)
}
}