## 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; props `diagnostics-buffer`, `has-diagnostics` -> generated getters `diagnostics_buffer() -> Option`, `has_diagnostics() -> bool`): `new(&CoreConfig)` (clones), `from_arc(Arc)`, `new_default()`, `get() -> Arc`, `get_mut(|&mut CoreConfig| ..)` (Arc::make_mut), `clone_config() -> CoreConfig`. Test `ghostty_config` ported. - `dialog::Dialog` (GhosttyDialog, parent adw::AlertDialog): `present(Option<&impl IsA>)`, `close()` (force_close); subclass via `DialogImpl: AdwAlertDialogImpl` (unsafe `IsSubclassable` impl, SAFETY-commented). Override `response` through `AdwAlertDialogImpl`. - `config_errors_dialog::ConfigErrorsDialog`: `new(&Config)`, prop `config`, `present(Option<&W>)`, `close()`, `connect_reload_config(Fn(&Self))`. - `close_confirmation_dialog::{CloseConfirmationDialog, Target{App,Tab,Window,Surface}}`: `new(Target)`, prop `target`, `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()`, props `request` (boxed, nullable), `clipboard-contents` (TextBuffer), `blur`, `can-remember` (generated getters/setters: `set_request(Option)`, `set_clipboard_contents(Option)`, `set_blur(bool)`, `set_can_remember(bool)`), helpers `set_request_value(ClipboardRequest)`, `get_request() -> Option`, `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>)`, props `target`, `initial-value`, `present(&W)` (anchors to ancestor adw::ApplicationWindow/Window, async `choose`), `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`): props `duration`(u32, 750), `first-delay`(250), `label`(Option), `overlay-halign/valign`. API: `new()`, `set_label(Option<&str>)`, `label()`, `schedule()`. - `KeyStateOverlay`: props `tables`/`sequence` are **GStrv (`Vec`)** instead of `ext.StringList`; read-only `has-tables`/`has-sequence`; `valign-target`. API: `set_tables(Vec)`, `set_sequence(Vec)`. - `SearchOverlay`: props `active` (rw), `search-total`/`search-selected` (u64 ro), `has-search-total/selected`, `halign-target`, `valign-target`. API: `set_search_total(Option)`, `set_search_selected(Option)`, `set_search_contents(&str)`, `grab_focus_entry()` (Zig `grabFocus`; renamed to avoid clashing with `Widget::grab_focus`), `set_active(bool)`, `connect_{stop_search,search_changed(Fn(&Self, Option)),next_match,previous_match}`. - `SurfaceChildExited`: prop `data` (boxed `ChildExitedBoxed`), signal `close-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>)`, props `surface` (typed `GtkWidget`, accepts the `Surface` class), `config` (typed `GObject`; the scrollbar-policy closure downcasts to `class::config::Config` and calls `.get().scrollbar` - needs `Config::get() -> impl Deref`). Binds `surface.config` -> own `config`, so Surface must expose a `config` property holding a `class::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)` (sets `prefers-color-scheme` on providers, GTK >= 4.20, dynamic property). `css::runtime_css(&Config, gtk_416) -> String` is pure and tested. Static style*.css come from the gresource: the Application must set `resource_base_path` = `/com/mitchellh/ghostty` so 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 `.ui` files are bundled with `alias="/.ui"` so resource paths are `/com/mitchellh/ghostty/ui//.ui` (previously doubled `ui/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`; `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` 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`; `PostForkInfo::new(&Config)`/`post_fork(&info, Option) -> 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` (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) ```rust pub trait WindowHost { // implemented by window.rs using a *weak* ref to the Window gobject fn gtk_window(&self) -> Option; // as(gtk.Window)/Native fn is_quick_terminal(&self) -> bool; fn window_decoration(&self) -> ghostty_config::types_b::WindowDecoration; fn config(&self) -> Option>; // getConfig()?.get() } pub enum App { None(noop::App), Wayland(wayland::App), X11(x11::App) } App::init(&gdk::Display, app_id:&str, &Config) -> Result // 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) -> Result // 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-shell` crate (`LayerShell` trait). 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 with `LD_PRELOAD=libgtk4-layer-shell.so` (else `is_supported()` is false and the quick terminal is disabled). * X11: Xlib (dlopen'd via x11-dl) for XGet/Change/DeleteWindowProperty, XEventsQueued/XPeekEvent; `XkbQueryExtension`/`XkbSelectEventDetails` declared in a small `extern "C"` block (`#[link(name="X11")]`). `unsafe` is 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)`, `run()` (-> `Result<(), RunError>`), `terminate()`, `wakeup()`, `perform_action(Target, Action) -> bool`, `App::perform_ipc(&ipc::Target, &ipc::Action)` (static). `app::waker() -> Arc` (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` (GType `GhosttyApplication`, extends `AdwApplication`, property `config` (read-only `Config` object, notify on change)): `Application::default()`, `new(core_app)`, `core() -> Rc`, `rt() -> Rc`, `get_config() -> class::config::Config` (object), `core_config() -> Arc`, `winproto() -> Ref` / `winproto_mut()`, `saved_language()`, `surfaces() -> Vec`, `perform_action(...)`, `new_window(parent: Option<&Surface>, WindowOverrides)`, `quit()`, `wakeup()`, `run_loop()`, `deinit()`. `AppRt` is the `RtApp` impl (forwards to the default app; main thread only). * The giant action switch is `Application::perform_action`. Surface targets are resolved with `Surface::from_id(id)` (F5); actions use the Zig mechanisms (`activate_action("tab.close"/"win.close"/ "split-tree.*")`, `ancestor::`). `inspector` actions go to `Surface::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_DISABLE` env (`set_gtk_env`), `adw::init`, single-instance flag, app-id (`class`), winproto init (noop fallback), CSS via `css::CssProviders`, `setlocale(LC_ALL,"")` (`init_locale`, closes the INTEGRATION_TODO item for locale), style-manager <-> core color scheme sync (`color_scheme_event` + each `CoreSurface::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` (own `MainContext` iteration + `CoreApp::tick` + quit conditions), `quit()` with close confirmation dialog. * `SIGUSR2` reload: glib-rs has no `g_unix_signal_add`; a helper thread (`signal-hook`) hops to the main context. * Global shortcuts: portal `CreateSession`/`BindShortcuts` over gio D-Bus (deprecated `signal_subscribe` allowed); `GlobalShortcuts` signal `trigger(String)` carries the action in keybind syntax; `connect_trigger` parses 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_windows` etc.: unimplemented like upstream. * Open URL uses `xdg-open` (OpenURI portal is a TODO upstream too). * `async-backend` (libxev) is meaningless in Rust and ignored. ## F7: window + command palette (class/{window,command_palette}.rs) `window::Window` (GhosttyWindow, `adw::ApplicationWindow` subclass; template `ui/1.5/window.ui`). Props: `active-surface` (ro), `config` (rw), `debug` (ro), `titlebar-style` (ro, enum `window::TitlebarStyle` = GEnum `GhosttyWindowTitlebarStyle`), `headerbar-visible`, `tabs-autohide`, `tabs-wide`, `tabs-visible`, `toolbar-style` (`adw::ToolbarStyle`), `quick-terminal` (rw). Template callbacks follow GtkBuilder's `[sender, args.., template_instance]` calling convention (instance is the LAST parameter); the two blueprint closures use `#[template_callback(function)]`. Actions registered on the window (`win.*`, same names/param types as Zig): about, close, close-tab (s), new-tab, new-window, prompt-surface-title, prompt-tab-title, prompt-context-tab-title, ring-bell, split-{right,left,up,down}, copy, paste, reset, clear, toggle-command-palette, toggle-inspector (no-op, inspector deferred). Public API: `Window::new(&Application, Option<&str> title)`, `Window::new_quick_terminal(&Application)`, `winproto() -> RefMut`, `is_quick_terminal()`, `get_active_surface() -> Option`, `get_config() -> Option`, `tab_view()/get_tab_view()`, `get_window_decoration()`, `toggle_window_decorations()`, `get_needs_confirm_quit()`, `new_tab(Option<&Surface>)`, `new_tab_for_window(Option<&Surface>, TabOverrides{command,working_directory,title})`, `select_tab(SelectTab::{Previous,Next,Last,N(usize)}) -> bool`, `move_tab(&Surface, isize) -> bool`, `toggle_tab_overview()`, `toggle_visibility()`. Everything else (sync_appearance, sync_actions, tab/surface signal wiring: present-request, clipboard-write (toast for standard clipboard only), menu, toggle-fullscreen, toggle-maximize, init (default/min size once), tab close-request, split-tree `changed`) is private and ported 1:1, incl. tab-overview focus timer (500ms), close confirmations for window/tab, new-window-from-dragged-tab, quick-terminal autohide, urgency reset, winproto resize/scale hooks. Deviations: parent surfaces are GTK `Surface` objects (not core surfaces); `adw::AboutDialog` always (adw >= 1.5 assumed); `GBindingGroup` replaced by a single `glib::Binding` (tab title -> window title, no sync_create, like Zig); signal handler "disconnect by data" is a registry of `SignalHandlerId`s; command palette `clipboard-write` enum compared by value 0 / nick `standard`. TODO: toggle-inspector is a stub (log only). `command_palette::CommandPalette` (GhosttyCommandPalette, `adw::Bin`, template `ui/1.5/command-palette.ui`, root object is the `AdwDialog`): prop `config`; signal `trigger(BoxedAction)` with `connect_trigger(|palette, &binding::Action|)`; `new()` (keeps a self-reference until the dialog's `closed`, like Zig's refSink), `toggle(&Window)`. Private `GhosttyCommand` list item (props `config`, `action-key`, `action`, `title`, `description`; regular + jump commands, comparator `compare_commands` with colon normalisation) sourced from `config.command_palette_entry` filtered by `is_action_supported_on_gtk` plus one jump command per `Application::surfaces()`. Tests: 3 unit tests (title ordering, unsupported actions, titlebar conversion); GObject runtime paths need a display (exercised by the demo). Shared edit: added `"gnome_44"` to the gtk4 features in Cargo.toml (glib >= 2.74/2.76 APIs such as SignalGroup/BindingGroup).