14 KiB
F3: dialogs + config object (class/{dialog,config,config_errors_dialog,close_confirmation_dialog,clipboard_confirmation_dialog,title_dialog,debug_warning}.rs)
Libadwaita >= 1.5 is assumed, so Dialog is always an adw::AlertDialog subclass (no MessageDialog fallback) and DebugWarning uses the adw::Banner (1.3) template; the clipboard dialog always uses the 1.4 template (remember switch). GType names, property names, signals and template children match the Zig classes.
Public API (all &self methods on glib::wrapper! types; signals via connect_*):
config::Config(GhosttyConfig; propsdiagnostics-buffer,has-diagnostics-> generated gettersdiagnostics_buffer() -> Option<gtk::TextBuffer>,has_diagnostics() -> bool):new(&CoreConfig)(clones),from_arc(Arc<CoreConfig>),new_default(),get() -> Arc<CoreConfig>,get_mut(|&mut CoreConfig| ..)(Arc::make_mut),clone_config() -> CoreConfig. Testghostty_configported.dialog::Dialog(GhosttyDialog, parent adw::AlertDialog):present(Option<&impl IsA<Widget>>),close()(force_close); subclass viaDialogImpl: AdwAlertDialogImpl(unsafeIsSubclassableimpl, SAFETY-commented). OverrideresponsethroughAdwAlertDialogImpl.config_errors_dialog::ConfigErrorsDialog:new(&Config), propconfig,present(Option<&W>),close(),connect_reload_config(Fn(&Self)).close_confirmation_dialog::{CloseConfirmationDialog, Target{App,Tab,Window,Surface}}:new(Target), proptarget,present(Option<&W>)(sets heading/body),close(),connect_close_request(Fn(&Self)),connect_cancel(Fn(&Self)).Target::title()/body()use ghostty_os::i18n::tr.clipboard_confirmation_dialog::{ClipboardConfirmationDialog, ClipboardRequestBoxed(pub ClipboardRequest)}:new(), propsrequest(boxed, nullable),clipboard-contents(TextBuffer),blur,can-remember(generated getters/setters:set_request(Option<ClipboardRequestBoxed>),set_clipboard_contents(Option<TextBuffer>),set_blur(bool),set_can_remember(bool)), helpersset_request_value(ClipboardRequest),get_request() -> Option<ClipboardRequest>,get_clipboard_contents(),present(Option<&W>),connect_confirm(Fn(&Self, remember: bool)),connect_deny(Fn(&Self, bool)).title_dialog::{TitleDialog, Target{Surface,Tab}}(parent adw::AlertDialog directly, as in Zig):new(Target, Option<&str>), propstarget,initial-value,present(&W)(anchors to ancestor adw::ApplicationWindow/Window, asyncchoose),connect_set(Fn(&Self, &str)).debug_warning::DebugWarning:new().
Status: all type-check and are clippy-clean; runtime (template instantiation, notify callbacks) NOT yet exercised because the crate did not fully compile when F3 finished. Also added #![allow(clippy::unwrap_used)] to build.rs.
F4: overlays, child-exited banner, scrolled window, css (done)
Files: src/css.rs, src/class/{resize_overlay,key_state_overlay,search_overlay,surface_child_exited,surface_scrolled_window}.rs.
Verified in an isolated scratch crate (compiles, clippy -D warnings, instantiated on a live display with no GTK criticals).
All GType names match Zig (note GhostttySurfaceScrolledWindow keeps Zig's 3-t typo); templates from the bundled gresource.
ResizeOverlay(GhosttyResizeOverlay): propsduration(u32, 750),first-delay(250),label(Option),overlay-halign/valign. API:new(),set_label(Option<&str>),label(),schedule().KeyStateOverlay: propstables/sequenceare GStrv (Vec<String>) instead ofext.StringList; read-onlyhas-tables/has-sequence;valign-target. API:set_tables(Vec<String>),set_sequence(Vec<String>).SearchOverlay: propsactive(rw),search-total/search-selected(u64 ro),has-search-total/selected,halign-target,valign-target. API:set_search_total(Option<usize>),set_search_selected(Option<usize>),set_search_contents(&str),grab_focus_entry()(ZiggrabFocus; renamed to avoid clashing withWidget::grab_focus),set_active(bool),connect_{stop_search,search_changed(Fn(&Self, Option<String>)),next_match,previous_match}.SurfaceChildExited: propdata(boxedChildExitedBoxed), signalclose-request. API:set_child_exited(Option<&ChildExited>),connect_close_request. The Zig Noop variant (adw < 1.3) is omitted (crate requires adw 1.5).SurfaceScrolledWindow:SurfaceScrolledWindow::new(Option<&impl IsA<Widget>>), propssurface(typedGtkWidget, accepts theSurfaceclass),config(typedGObject; the scrollbar-policy closure downcasts toclass::config::Configand calls.get().scrollbar- needsConfig::get() -> impl Deref<Target = ghostty_config::config::Config>). Bindssurface.config-> ownconfig, so Surface must expose aconfigproperty holding aclass::config::Config.css::CssProviders:new(&Display)(installs runtime provider at APPLICATION+3),load_all(&Config),load_runtime,load_custom(gtk-custom-css, 5MB limit, USER priority),set_color_scheme(dark)(setsprefers-color-schemeon providers, GTK >= 4.20, dynamic property).css::runtime_css(&Config, gtk_416) -> Stringis pure and tested. Static style*.css come from the gresource: the Application must setresource_base_path=/com/mitchellh/ghosttyso AdwApplication loads them automatically.- Template signal handlers receive the emitter (not the template) in gtk-rs, so shared handlers locate the overlay with
ancestor(). - build.rs fix: blueprint
.uifiles are bundled withalias="<ver>/<name>.ui"so resource paths are/com/mitchellh/ghostty/ui/<ver>/<name>.ui(previously doubledui/ui/).
F1: utilities, key, ipc (files: key, gtk_version, adw_version, weak_ref, ext/, gsettings, cgroup, flatpak, pre_exec, post_fork, ipc/)
Ported (Zig -> Rust): key.zig -> key.rs (accel_from_trigger, xdg_shortcut_from_trigger, label_from_trigger return Option<String>; translate_mods, keyval_unicode_unshifted(&Widget, &gdk::KeyEvent, keycode) -> u32,
event_mods(&gdk::Event, Key, ModifierType, Action, winproto_event_mods: impl FnOnce(Option<&gdk::Device>, ModifierType)->Mods) -> Mods, key_from_keyval(u32), keyval_from_key(Key); 132-entry KEYMAP).
gtk_version/adw_version -> get_runtime_version() -> (u32,u32,u32), at_least/runtime_at_least/runtime_until, log_version, adw supports_*. "Comptime" version is the cargo feature level (4.14 / 1.5).
weak_ref.zig -> thin WeakRef<T> over glib::WeakRef. ext -> ext::actions::{Action, add, add_to_map, add_as_group}, ext::StringList (glib Boxed GhosttyStringList), get_ancestor, g_value_holds (boxedCopy/boxedFree/listFind not needed).
gsettings -> get_bool/get_int/get_string(Key). cgroup -> fmt_scope(pid), create_scope(&gio::DBusConnection, pid, Options). flatpak -> resources_dir().
pre_exec/post_fork -> PreExecInfo::new(&Config)/pre_exec(&info) -> Option<u8>; PostForkInfo::new(&Config)/post_fork(&info, Option<pid>) -> Result<(), PostForkError> (uses gio::Application::default() dbus connection; ghostty-termio's Command also has its own copy of the cgroup wait loop).
ipc -> ipc::perform_ipc(&Target, &Action) -> Result<bool, ipc::Error> (org.gtk.Actions.Activate, new-window / new-window-command), ipc::dbus::DBus.
Tests: Zig ported 11 (key 3, gtk_version 2, adw_version 1, weak_ref 1, gsettings 4 (the comptime ones became 2), ext/actions 2, ext/slice 3); extra 8. The ext/actions GTK-display test and adw test are skipped at runtime when GTK cannot init (not main thread / no display).
Deviations: only unsafe is FromGlib for gdk::Key in key.rs (annotated). many keycode media keys remain a TODO as upstream.
winproto (F2) — src/winproto/{mod,noop,x11,wayland}.rs
Ported winproto.zig, noop.zig, x11.zig, wayland.zig. Deps added: gdk4-x11 (x11, xlib; Xlib via its re-exported dlopen x11 = x11-dl), gdk4-wayland (wayland_crate), wayland-client/-backend(client_system)/-protocols(client,staging)/-protocols-plasma(client).
Public API (consumed by class/window.rs, class/application.rs)
pub trait WindowHost { // implemented by window.rs using a *weak* ref to the Window gobject
fn gtk_window(&self) -> Option<gtk::Window>; // as(gtk.Window)/Native
fn is_quick_terminal(&self) -> bool;
fn window_decoration(&self) -> ghostty_config::types_b::WindowDecoration;
fn config(&self) -> Option<Arc<Config>>; // getConfig()?.get()
}
pub enum App { None(noop::App), Wayland(wayland::App), X11(x11::App) }
App::init(&gdk::Display, app_id:&str, &Config) -> Result<App> // wayland, then x11, else None
App::protocol() -> Protocol; App::event_mods(Option<&gdk::Device>, gdk::ModifierType) -> Mods // falls back to crate::key::translate_mods
App::supports_quick_terminal() -> bool
App::init_quick_terminal(&mut self, >k::Window) -> Result<()> // call BEFORE the window is realized (layer-shell init)
pub enum Window { None, Wayland, X11 }
Window::init(&App, Rc<dyn WindowHost>) -> Result<Window> // window must be realized (needs gdk::Surface)
Window::{resize_event, sync_appearance, set_urgent(bool)}(&mut self) -> Result<()>
Window::client_side_decoration_enabled(&self) -> bool
Window::add_subprocess_env(&mut self, &mut Vec<(String,String)>) -> Result<()> // X11: WINDOWID
Zig deinit = Drop (Wayland releases blur/decoration/slide/token objects, disconnects enter-monitor).
noop::Window::update_config_event (unused in Zig union) was not ported.
Behaviour notes / deviations
- Wayland uses gdk4-wayland's shared
wayland_client::Connection(no unsafe). We keep a private event queue; blocking roundtrips at init; while an xdg-activation token is outstanding a 50 ms glib timeout dispatches the queue. KDE blur / server-decoration / slide / xdg-activation implemented; xdg_wm_dialog_v1 presence + layer-shell < 1.0.4 check ported. Registry changes after init (global_remove) are not tracked. - Quick terminal via
gtk4-layer-shellcrate (LayerShelltrait). Upstream requires libgtk4-layer-shell to be loaded before libwayland-client; Rust link order is not controllable, so the final binary should link it first or run withLD_PRELOAD=libgtk4-layer-shell.so(elseis_supported()is false and the quick terminal is disabled). - X11: Xlib (dlopen'd via x11-dl) for XGet/Change/DeleteWindowProperty, XEventsQueued/XPeekEvent;
XkbQueryExtension/XkbSelectEventDetailsdeclared in a smallextern "C"block (#[link(name="X11")]).unsafeis confined to x11.rs (#![allow(unsafe_code)], SAFETY comments). - Tests: Zig has none for winproto; 2 added (noop defaults, X11 property struct layouts). Not runtime-tested on a live X11/Wayland compositor in this fork.
- Depends on
crate::key::translate_mods(gdk::ModifierType) -> Mods(F1).
Application / App / GlobalShortcuts / demo (F8)
Files: src/app.rs (App.zig), src/class/application.rs (application.zig),
src/class/global_shortcuts.rs (global_shortcuts.zig), src/gl.rs (GL proc loader),
examples/demo.rs.
Public API
app::App { pub app: Application }:App::new(Rc<dyn CoreApp>),run()(->Result<(), RunError>),terminate(),wakeup(),perform_action(Target<SurfaceId>, Action) -> bool,App::perform_ipc(&ipc::Target, &ipc::Action)(static).app::waker() -> Arc<dyn Waker>(thread-safe,MainContext::wakeup) - create it before the core app and give it to core.app::MUST_DRAW_FROM_APP_THREAD = true.class::application::Application(GTypeGhosttyApplication, extendsAdwApplication, propertyconfig(read-onlyConfigobject, notify on change)):Application::default(),new(core_app),core() -> Rc<dyn CoreApp>,rt() -> Rc<dyn RtApp>,get_config() -> class::config::Config(object),core_config() -> Arc<ghostty_config::Config>,winproto() -> Ref<winproto::App>/winproto_mut(),saved_language(),surfaces() -> Vec<Surface>,perform_action(...),new_window(parent: Option<&Surface>, WindowOverrides),quit(),wakeup(),run_loop(),deinit().AppRtis theRtAppimpl (forwards to the default app; main thread only).- The giant action switch is
Application::perform_action. Surface targets are resolved withSurface::from_id(id)(F5); actions use the Zig mechanisms (activate_action("tab.close"/"win.close"/ "split-tree.*"),ancestor::<SplitTree/Window/Tab>).inspectoractions go toSurface::control_inspector(inspector deferred; no-op). Unimplemented actions (secure_input,close_all_windows, ...) log + return false. - Startup mirrors Zig: log writer funnel (
glib::log_set_writer_func->log), version logging,Config::load(errors become a diagnostic), LANG/language, i18n,GDK_DEBUG/GDK_DISABLEenv (set_gtk_env),adw::init, single-instance flag, app-id (class), winproto init (noop fallback), CSS viacss::CssProviders,setlocale(LC_ALL,"")(init_locale, closes the INTEGRATION_TODO item for locale), style-manager <-> core color scheme sync (color_scheme_event+ eachCoreSurface::color_scheme_callback),app.*GActions (new-window,new-window-command(as:-e,--command=,--working-directory=,--title=),open-config,present-surface(u64 surface id),quit,reload-config), accelerator sync, config errors dialog, quit timer,run_loop(ownMainContextiteration +CoreApp::tick+ quit conditions),quit()with close confirmation dialog. SIGUSR2reload: glib-rs has nog_unix_signal_add; a helper thread (signal-hook) hops to the main context.- Global shortcuts: portal
CreateSession/BindShortcutsover gio D-Bus (deprecatedsignal_subscribeallowed);GlobalShortcutssignaltrigger(String)carries the action in keybind syntax;connect_triggerparses it. gl::proc_address(name): dlsym on libepoxy (works for EGL and GLX); use as the loader for glow /ghostty-renderer::prepare_context.
Changes to core.rs (trait spec)
Added AppMessage::NewWindow, CoreSurface::color_scheme_callback, CoreSurface::update_config;
CoreApp::perform_all_action takes a ghostty_input::binding::Action (as in Zig).
Demo
cargo run -p ghostty-apprt-gtk --example demo (env: DEMO_QUIT_AFTER_MS, DEMO_CONFIRM=1). Dummy core:
keybindings from the user config drive window/tab/split/palette actions; draw_frame clears with background.
Known gaps
toggle_visibility/float_window/secure_input/close_all_windowsetc.: unimplemented like upstream.- Open URL uses
xdg-open(OpenURI portal is a TODO upstream too). async-backend(libxev) is meaningless in Rust and ignored.