feat: port GTK version fallbacks, helpgen, bench modes, CLI leftovers
- apprt-gtk: Ghostty's runtime+build-time version gating (at_least) with cargo features adw_1_3/adw_1_4/adw_1_5/gtk_4_16 (default on) and the fallbacks for older libraries: MessageDialog (adw < 1.5), clipboard dialog without SwitchRow, pre-Banner debug warning and child-exited widgets, the old about dialog, GTK 4.16 CSS error source bytes. The minimum stays GTK 4.14 / libadwaita 1.5 (Ghostty's floor). The root build links with -z lazy so version-gated symbols don't keep older libraries from loading. - ghostty-helpgen (port of helpgen.zig) generates CLI action help and keybinding action docs from doc comments at build time, replacing the checked-in help/*.txt; +help and +list-actions --docs stay byte-identical to Ghostty 1.3.1. - ghostty-bench: libc wcwidth mode and separate simd/table and uucode/table code paths like Zig. - ghostty-cli: Windows branches of edit-config, list-colors, list-keybinds and boo; ssh-cache DiskCache flock locking (no-op on Windows, as in Zig) with an exclusivity test. Test Plan: - cargo test --workspace - diff <(cargo run -q -- +list-actions --docs) \ <(ghostty +list-actions --docs)
This commit is contained in:
1 parent
cd86fe92a8
commit
f2252bb165
51 files changed
+2426
-1621
No files matched your search
@@ -15,7 +15,10 @@ use ghostty_apprt::structs::ClipboardRequest;
|
||||
use ghostty_os::i18n::tr;
|
||||
use gtk::{glib, glib::subclass::Signal};
|
||||
|
||||
use super::dialog::{Dialog, DialogImpl};
|
||||
use super::dialog::{Dialog, dialog_subclass_wrapper, impl_dialog_parent};
|
||||
|
||||
/// Whether we're able to have the remember switch (Zig: `can_remember`).
|
||||
pub const CAN_REMEMBER: bool = crate::adw_version::COMPTIME_SUPPORTS_SWITCH_ROW;
|
||||
|
||||
/// GBoxed wrapper for [`ClipboardRequest`] (Zig: boxed `apprt.ClipboardRequest`).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Boxed)]
|
||||
@@ -27,13 +30,10 @@ mod imp {
|
||||
use gtk::{glib, subclass::prelude::*};
|
||||
|
||||
use super::{
|
||||
AdwAlertDialogImpl,
|
||||
AdwDialogImpl,
|
||||
Cell,
|
||||
ClipboardRequest,
|
||||
ClipboardRequestBoxed,
|
||||
Dialog,
|
||||
DialogImpl,
|
||||
ObjectImpl,
|
||||
ObjectSubclass,
|
||||
OnceLock,
|
||||
@@ -45,7 +45,14 @@ mod imp {
|
||||
|
||||
#[derive(Default, glib::Properties, gtk::CompositeTemplate)]
|
||||
#[properties(wrapper_type = super::ClipboardConfirmationDialog)]
|
||||
#[template(resource = "/com/mitchellh/ghostty/ui/1.4/clipboard-confirmation-dialog.ui")]
|
||||
#[cfg_attr(
|
||||
feature = "adw_1_4",
|
||||
template(resource = "/com/mitchellh/ghostty/ui/1.4/clipboard-confirmation-dialog.ui")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(feature = "adw_1_4"),
|
||||
template(resource = "/com/mitchellh/ghostty/ui/1.0/clipboard-confirmation-dialog.ui")
|
||||
)]
|
||||
pub struct ClipboardConfirmationDialog {
|
||||
/// The request that this dialog is for.
|
||||
#[property(get, set, nullable)]
|
||||
@@ -71,6 +78,8 @@ mod imp {
|
||||
pub reveal_button: TemplateChild<gtk::Button>,
|
||||
#[template_child]
|
||||
pub hide_button: TemplateChild<gtk::Button>,
|
||||
// Only exists in the 1.4+ template (`adw::SwitchRow`).
|
||||
#[cfg(feature = "adw_1_4")]
|
||||
#[template_child]
|
||||
pub remember_choice: TemplateChild<adw::SwitchRow>,
|
||||
}
|
||||
@@ -190,26 +199,24 @@ mod imp {
|
||||
}
|
||||
|
||||
impl WidgetImpl for ClipboardConfirmationDialog {}
|
||||
impl AdwDialogImpl for ClipboardConfirmationDialog {}
|
||||
impl AdwAlertDialogImpl for ClipboardConfirmationDialog {
|
||||
fn response(&self, response: &str) {
|
||||
let remember = self.remember_choice.is_active();
|
||||
let obj = self.obj();
|
||||
if response == "cancel" {
|
||||
obj.emit_by_name::<()>("deny", &[&remember]);
|
||||
} else if response == "ok" {
|
||||
obj.emit_by_name::<()>("confirm", &[&remember]);
|
||||
}
|
||||
super::impl_dialog_parent!(ClipboardConfirmationDialog, |this, response| {
|
||||
#[cfg(feature = "adw_1_4")]
|
||||
let remember = this.remember_choice.is_active();
|
||||
#[cfg(not(feature = "adw_1_4"))]
|
||||
let remember = false;
|
||||
let obj = this.obj();
|
||||
if response == "cancel" {
|
||||
obj.emit_by_name::<()>("deny", &[&remember]);
|
||||
} else if response == "ok" {
|
||||
obj.emit_by_name::<()>("confirm", &[&remember]);
|
||||
}
|
||||
}
|
||||
impl DialogImpl for ClipboardConfirmationDialog {}
|
||||
});
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ClipboardConfirmationDialog(ObjectSubclass<imp::ClipboardConfirmationDialog>)
|
||||
@extends Dialog, adw::AlertDialog, adw::Dialog, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::ShortcutManager;
|
||||
}
|
||||
dialog_subclass_wrapper!(
|
||||
ClipboardConfirmationDialog,
|
||||
imp::ClipboardConfirmationDialog
|
||||
);
|
||||
|
||||
impl ClipboardConfirmationDialog {
|
||||
pub fn new() -> Self {
|
||||
|
||||
@@ -8,7 +8,18 @@ use adw::{prelude::*, subclass::prelude::*};
|
||||
use ghostty_os::i18n::tr;
|
||||
use gtk::{glib, glib::subclass::Signal};
|
||||
|
||||
use super::dialog::{Dialog, DialogImpl};
|
||||
use super::dialog::{Dialog, dialog_subclass_wrapper, impl_dialog_parent};
|
||||
|
||||
/// Name of the "user confirmed the close" signal. Zig always uses
|
||||
/// `close-request`, but with the `adw::MessageDialog` parent (a `GtkWindow`)
|
||||
/// that name already exists in the class ancestry, which makes GObject
|
||||
/// refuse to register it (and would fire it on every window close). Zig's
|
||||
/// fallback hits that, so the fallback build renames the signal.
|
||||
const CLOSE_REQUEST: &str = if cfg!(feature = "adw_1_5") {
|
||||
"close-request"
|
||||
} else {
|
||||
"dialog-close-request"
|
||||
};
|
||||
|
||||
/// The target of a close dialog (`GhosttyCloseConfirmationDialogTarget`).
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, glib::Enum)]
|
||||
@@ -47,19 +58,7 @@ impl Target {
|
||||
mod imp {
|
||||
use gtk::{glib, glib::prelude::*, subclass::prelude::*};
|
||||
|
||||
use super::{
|
||||
AdwAlertDialogImpl,
|
||||
AdwDialogImpl,
|
||||
Cell,
|
||||
Dialog,
|
||||
DialogImpl,
|
||||
ObjectImpl,
|
||||
ObjectSubclass,
|
||||
OnceLock,
|
||||
Signal,
|
||||
Target,
|
||||
WidgetImpl,
|
||||
};
|
||||
use super::{Cell, Dialog, ObjectImpl, ObjectSubclass, OnceLock, Signal, Target, WidgetImpl};
|
||||
|
||||
#[derive(Default, glib::Properties, gtk::CompositeTemplate)]
|
||||
#[properties(wrapper_type = super::CloseConfirmationDialog)]
|
||||
@@ -91,7 +90,7 @@ mod imp {
|
||||
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
|
||||
SIGNALS.get_or_init(|| {
|
||||
vec![
|
||||
Signal::builder("close-request").build(),
|
||||
Signal::builder(super::CLOSE_REQUEST).build(),
|
||||
Signal::builder("cancel").build(),
|
||||
]
|
||||
})
|
||||
@@ -102,25 +101,17 @@ mod imp {
|
||||
}
|
||||
}
|
||||
impl WidgetImpl for CloseConfirmationDialog {}
|
||||
impl AdwDialogImpl for CloseConfirmationDialog {}
|
||||
impl AdwAlertDialogImpl for CloseConfirmationDialog {
|
||||
fn response(&self, response: &str) {
|
||||
let name = if response == "close" {
|
||||
"close-request"
|
||||
} else {
|
||||
"cancel"
|
||||
};
|
||||
self.obj().emit_by_name::<()>(name, &[]);
|
||||
}
|
||||
}
|
||||
impl DialogImpl for CloseConfirmationDialog {}
|
||||
super::impl_dialog_parent!(CloseConfirmationDialog, |this, response| {
|
||||
let name = if response == "close" {
|
||||
super::CLOSE_REQUEST
|
||||
} else {
|
||||
"cancel"
|
||||
};
|
||||
this.obj().emit_by_name::<()>(name, &[]);
|
||||
});
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct CloseConfirmationDialog(ObjectSubclass<imp::CloseConfirmationDialog>)
|
||||
@extends Dialog, adw::AlertDialog, adw::Dialog, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::ShortcutManager;
|
||||
}
|
||||
dialog_subclass_wrapper!(CloseConfirmationDialog, imp::CloseConfirmationDialog);
|
||||
|
||||
impl CloseConfirmationDialog {
|
||||
pub fn new(target: Target) -> Self {
|
||||
@@ -142,7 +133,7 @@ impl CloseConfirmationDialog {
|
||||
|
||||
pub fn connect_close_request<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_closure(
|
||||
"close-request",
|
||||
CLOSE_REQUEST,
|
||||
false,
|
||||
glib::closure_local!(move |obj: &Self| f(obj)),
|
||||
)
|
||||
|
||||
@@ -9,18 +9,15 @@ use gtk::{glib, glib::subclass::Signal};
|
||||
|
||||
use super::{
|
||||
config::Config,
|
||||
dialog::{Dialog, DialogImpl},
|
||||
dialog::{Dialog, dialog_subclass_wrapper, impl_dialog_parent},
|
||||
};
|
||||
|
||||
mod imp {
|
||||
use gtk::{glib, glib::prelude::*, subclass::prelude::*};
|
||||
|
||||
use super::{
|
||||
AdwAlertDialogImpl,
|
||||
AdwDialogImpl,
|
||||
Config,
|
||||
Dialog,
|
||||
DialogImpl,
|
||||
ObjectImpl,
|
||||
ObjectSubclass,
|
||||
ObjectSubclassExt,
|
||||
@@ -69,23 +66,15 @@ mod imp {
|
||||
}
|
||||
}
|
||||
impl WidgetImpl for ConfigErrorsDialog {}
|
||||
impl AdwDialogImpl for ConfigErrorsDialog {}
|
||||
impl AdwAlertDialogImpl for ConfigErrorsDialog {
|
||||
fn response(&self, response: &str) {
|
||||
if response != "reload" {
|
||||
return;
|
||||
}
|
||||
self.obj().emit_by_name::<()>("reload-config", &[]);
|
||||
super::impl_dialog_parent!(ConfigErrorsDialog, |this, response| {
|
||||
if response != "reload" {
|
||||
return;
|
||||
}
|
||||
}
|
||||
impl DialogImpl for ConfigErrorsDialog {}
|
||||
this.obj().emit_by_name::<()>("reload-config", &[]);
|
||||
});
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ConfigErrorsDialog(ObjectSubclass<imp::ConfigErrorsDialog>)
|
||||
@extends Dialog, adw::AlertDialog, adw::Dialog, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::ShortcutManager;
|
||||
}
|
||||
dialog_subclass_wrapper!(ConfigErrorsDialog, imp::ConfigErrorsDialog);
|
||||
|
||||
impl ConfigErrorsDialog {
|
||||
pub fn new(config: &Config) -> Self {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
//! Port of apprt/gtk/class/debug_warning.zig (`GhosttyDebugWarning`).
|
||||
//! Uses the `adw::Banner` variant (libadwaita >= 1.3).
|
||||
//!
|
||||
//! Debug warning banner. It is based on `adw::Banner` (a `adw::Bin` with the
|
||||
//! 1.3 template) if we're using Adwaita 1.3 or newer (the `adw_1_3` build
|
||||
//! level, Zig: `adw_version.supportsBanner()`). Otherwise it is a `gtk::Box`
|
||||
//! holding a label (1.2 template).
|
||||
|
||||
use gtk::glib;
|
||||
|
||||
@@ -7,14 +11,24 @@ mod imp {
|
||||
use gtk::{glib, subclass::prelude::*};
|
||||
|
||||
#[derive(Default, gtk::CompositeTemplate)]
|
||||
#[template(resource = "/com/mitchellh/ghostty/ui/1.3/debug-warning.ui")]
|
||||
#[cfg_attr(
|
||||
feature = "adw_1_3",
|
||||
template(resource = "/com/mitchellh/ghostty/ui/1.3/debug-warning.ui")
|
||||
)]
|
||||
#[cfg_attr(
|
||||
not(feature = "adw_1_3"),
|
||||
template(resource = "/com/mitchellh/ghostty/ui/1.2/debug-warning.ui")
|
||||
)]
|
||||
pub struct DebugWarning;
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for DebugWarning {
|
||||
const NAME: &'static str = "GhosttyDebugWarning";
|
||||
type Type = super::DebugWarning;
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
type ParentType = adw::Bin;
|
||||
#[cfg(not(feature = "adw_1_3"))]
|
||||
type ParentType = gtk::Box;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
@@ -31,15 +45,26 @@ mod imp {
|
||||
}
|
||||
}
|
||||
impl WidgetImpl for DebugWarning {}
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
impl adw::subclass::prelude::BinImpl for DebugWarning {}
|
||||
#[cfg(not(feature = "adw_1_3"))]
|
||||
impl BoxImpl for DebugWarning {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
glib::wrapper! {
|
||||
pub struct DebugWarning(ObjectSubclass<imp::DebugWarning>)
|
||||
@extends adw::Bin, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "adw_1_3"))]
|
||||
glib::wrapper! {
|
||||
pub struct DebugWarning(ObjectSubclass<imp::DebugWarning>)
|
||||
@extends gtk::Box, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||
}
|
||||
|
||||
impl DebugWarning {
|
||||
pub fn new() -> Self {
|
||||
crate::register_resources();
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
//! Port of apprt/gtk/class/dialog.zig (`GhosttyDialog`).
|
||||
//!
|
||||
//! Thin `adw::AlertDialog` subclass (we require libadwaita >= 1.5 so the
|
||||
//! `adw::MessageDialog` fallback is not needed). Subclasses implement
|
||||
//! [`DialogImpl`] and may override `response` via `AlertDialogImpl`.
|
||||
//! Thin abstraction over `adw::AlertDialog` and `adw::MessageDialog`, chosen
|
||||
//! at compile time from the libadwaita build level exactly like Zig's
|
||||
//! `Parent = if (adw_version.supportsDialogs()) adw.AlertDialog else
|
||||
//! adw.MessageDialog` (the `adw_1_5` cargo feature plays the role of the
|
||||
//! header version). Subclasses implement [`DialogImpl`] and override
|
||||
//! `response` through [`impl_dialog_parent!`].
|
||||
//!
|
||||
//! Once we drop support for Adwaita <= 1.2, this can be fully removed and we
|
||||
//! can use `adw::AlertDialog` directly.
|
||||
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::glib;
|
||||
|
||||
/// The parent class of [`Dialog`] (Zig: `Dialog.Parent`).
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
pub type DialogParent = adw::AlertDialog;
|
||||
/// The parent class of [`Dialog`] (Zig: `Dialog.Parent`).
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
pub type DialogParent = adw::MessageDialog;
|
||||
|
||||
mod imp {
|
||||
use adw::subclass::prelude::*;
|
||||
use gtk::glib;
|
||||
@@ -18,46 +31,144 @@ mod imp {
|
||||
impl ObjectSubclass for Dialog {
|
||||
const NAME: &'static str = "GhosttyDialog";
|
||||
type Type = super::Dialog;
|
||||
type ParentType = adw::AlertDialog;
|
||||
type ParentType = super::DialogParent;
|
||||
}
|
||||
|
||||
impl ObjectImpl for Dialog {}
|
||||
impl WidgetImpl for Dialog {}
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
impl AdwDialogImpl for Dialog {}
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
impl AdwAlertDialogImpl for Dialog {}
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
impl WindowImpl for Dialog {}
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
impl MessageDialogImpl for Dialog {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
glib::wrapper! {
|
||||
pub struct Dialog(ObjectSubclass<imp::Dialog>)
|
||||
@extends adw::AlertDialog, adw::Dialog, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::ShortcutManager;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
glib::wrapper! {
|
||||
pub struct Dialog(ObjectSubclass<imp::Dialog>)
|
||||
@extends adw::MessageDialog, gtk::Window, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Native,
|
||||
gtk::Root, gtk::ShortcutManager;
|
||||
}
|
||||
|
||||
/// Implemented by subclasses of [`Dialog`].
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
pub trait DialogImpl: AdwAlertDialogImpl {}
|
||||
/// Implemented by subclasses of [`Dialog`].
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
pub trait DialogImpl: MessageDialogImpl {}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
// SAFETY: `Dialog` adds no class/instance data of its own; we simply forward
|
||||
// to the parent (`adw::AlertDialog`) class/instance initialization, which is
|
||||
// valid for any `T: DialogImpl` (a subtype of `AdwAlertDialogImpl`).
|
||||
// to the parent class/instance initialization, which is valid for any
|
||||
// `T: DialogImpl` (a subtype of the parent's `*Impl` trait).
|
||||
unsafe impl<T: DialogImpl> IsSubclassable<T> for Dialog {
|
||||
fn class_init(class: &mut glib::Class<Self>) {
|
||||
<adw::AlertDialog as IsSubclassable<T>>::class_init(class.upcast_ref_mut());
|
||||
<DialogParent as IsSubclassable<T>>::class_init(class.upcast_ref_mut());
|
||||
}
|
||||
|
||||
fn instance_init(instance: &mut glib::subclass::InitializingObject<T>) {
|
||||
<adw::AlertDialog as IsSubclassable<T>>::instance_init(instance);
|
||||
<DialogParent as IsSubclassable<T>>::instance_init(instance);
|
||||
}
|
||||
}
|
||||
|
||||
impl Dialog {
|
||||
/// Present the dialog over `parent`.
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
pub fn present(&self, parent: Option<&impl IsA<gtk::Widget>>) {
|
||||
adw::prelude::AdwDialogExt::present(self, parent);
|
||||
}
|
||||
|
||||
/// Present the dialog over `parent`.
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
pub fn present(&self, parent: Option<&impl IsA<gtk::Widget>>) {
|
||||
// Reset the previous parent window
|
||||
self.set_transient_for(None::<>k::Window>);
|
||||
|
||||
// We need to get the window for the parent in order to set the
|
||||
// transient-for property on the MessageDialog.
|
||||
if let Some(window) = parent
|
||||
.and_then(gtk::prelude::WidgetExt::root)
|
||||
.and_then(|root| root.downcast::<gtk::Window>().ok())
|
||||
{
|
||||
self.set_transient_for(Some(&window));
|
||||
}
|
||||
|
||||
gtk::prelude::GtkWindowExt::present(self);
|
||||
}
|
||||
|
||||
/// Close the dialog without emitting `close-attempt`.
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
pub fn close(&self) {
|
||||
self.force_close();
|
||||
}
|
||||
|
||||
/// Close the dialog.
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
pub fn close(&self) {
|
||||
gtk::prelude::GtkWindowExt::close(self);
|
||||
}
|
||||
}
|
||||
|
||||
/// Declares the `glib::wrapper!` of a [`Dialog`] subclass with the right
|
||||
/// `@extends` chain for the libadwaita build level.
|
||||
macro_rules! dialog_subclass_wrapper {
|
||||
($name:ident, $imp:ty) => {
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
glib::wrapper! {
|
||||
pub struct $name(ObjectSubclass<$imp>)
|
||||
@extends $crate::class::dialog::Dialog, adw::AlertDialog, adw::Dialog, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget,
|
||||
gtk::ShortcutManager;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
glib::wrapper! {
|
||||
pub struct $name(ObjectSubclass<$imp>)
|
||||
@extends $crate::class::dialog::Dialog, adw::MessageDialog, gtk::Window,
|
||||
gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget,
|
||||
gtk::Native, gtk::Root, gtk::ShortcutManager;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Implements the parent-class traits of a [`Dialog`] subclass (the
|
||||
/// `Adw{Dialog,AlertDialog}Impl` or `{Window,MessageDialog}Impl` pair),
|
||||
/// overriding `response` with `|this, response| body`.
|
||||
macro_rules! impl_dialog_parent {
|
||||
($ty:ty, |$this:ident, $response:ident| $body:block) => {
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
impl adw::subclass::prelude::AdwDialogImpl for $ty {}
|
||||
#[cfg(feature = "adw_1_5")]
|
||||
impl adw::subclass::prelude::AdwAlertDialogImpl for $ty {
|
||||
fn response(&self, $response: &str) {
|
||||
let $this = self;
|
||||
$body
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
impl gtk::subclass::prelude::WindowImpl for $ty {}
|
||||
#[cfg(not(feature = "adw_1_5"))]
|
||||
impl adw::subclass::prelude::MessageDialogImpl for $ty {
|
||||
fn response(&self, $response: &str) {
|
||||
let $this = self;
|
||||
$body
|
||||
}
|
||||
}
|
||||
impl $crate::class::dialog::DialogImpl for $ty {}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use dialog_subclass_wrapper;
|
||||
pub(crate) use impl_dialog_parent;
|
||||
@@ -1371,6 +1371,13 @@ impl Surface {
|
||||
self.imp().child_exited.set(true);
|
||||
self.notify("child-exited");
|
||||
|
||||
// If we have the noop child exited overlay then we don't do anything
|
||||
// for child exited. The false return will force libghostty to show
|
||||
// the normal text-based message.
|
||||
if SurfaceChildExited::NOOP {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.imp().child_exited_overlay.set_child_exited(Some(data));
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,26 +1,41 @@
|
||||
//! Port of apprt/gtk/class/surface_child_exited.zig.
|
||||
//!
|
||||
//! Child exited overlay based on `adw.Banner` (Adwaita 1.3+; the crate
|
||||
//! requires 1.5 so the Zig `SurfaceChildExitedNoop` fallback is omitted).
|
||||
//! Emits `close-request` when the banner button is clicked.
|
||||
//! Child exited overlay based on `adw.Banner` (Adwaita 1.3+, the `adw_1_3`
|
||||
//! build level; emits `close-request` when the banner button is clicked).
|
||||
//! Without it Zig compiles `SurfaceChildExitedNoop`, an empty widget that
|
||||
//! immediately asks to be closed (`NOOP == true`); so does this port.
|
||||
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use ghostty_apprt::surface::ChildExited;
|
||||
use gtk::{glib, glib::subclass::Signal};
|
||||
use gtk::glib;
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
use gtk::glib::subclass::Signal;
|
||||
#[cfg(not(feature = "adw_1_3"))]
|
||||
use gtk::prelude::*;
|
||||
|
||||
/// Boxed wrapper so `ChildExited` can be a GObject property.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Boxed)]
|
||||
#[boxed_type(name = "GhosttyChildExited", nullable)]
|
||||
pub struct ChildExitedBoxed(pub ChildExited);
|
||||
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
glib::wrapper! {
|
||||
pub struct SurfaceChildExited(ObjectSubclass<imp::SurfaceChildExited>)
|
||||
@extends adw::Bin, gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "adw_1_3"))]
|
||||
glib::wrapper! {
|
||||
pub struct SurfaceChildExited(ObjectSubclass<imp::SurfaceChildExited>)
|
||||
@extends gtk::Widget,
|
||||
@implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
|
||||
}
|
||||
|
||||
impl Default for SurfaceChildExited {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -28,16 +43,27 @@ impl Default for SurfaceChildExited {
|
||||
}
|
||||
|
||||
impl SurfaceChildExited {
|
||||
/// Can be detected at compile time (Zig: `pub const noop = true`).
|
||||
pub const NOOP: bool = !cfg!(feature = "adw_1_3");
|
||||
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new()
|
||||
}
|
||||
|
||||
/// Set the child exited data (Zig: `setData`).
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
pub fn set_child_exited(&self, data: Option<&ChildExited>) {
|
||||
self.set_data(data.map(|d| ChildExitedBoxed(*d)));
|
||||
}
|
||||
|
||||
/// Set the child exited data (Zig: `setData`); the noop widget just asks
|
||||
/// to be closed.
|
||||
#[cfg(not(feature = "adw_1_3"))]
|
||||
pub fn set_child_exited(&self, _data: Option<&ChildExited>) {
|
||||
self.emit_by_name::<()>("close-request", &[]);
|
||||
}
|
||||
|
||||
pub fn connect_close_request<F: Fn(&Self) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||
self.connect_closure(
|
||||
"close-request",
|
||||
@@ -47,6 +73,7 @@ impl SurfaceChildExited {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "adw_1_3")]
|
||||
mod imp {
|
||||
use std::cell::RefCell;
|
||||
|
||||
@@ -141,3 +168,31 @@ mod imp {
|
||||
impl WidgetImpl for SurfaceChildExited {}
|
||||
impl BinImpl for SurfaceChildExited {}
|
||||
}
|
||||
|
||||
/// Empty widget that does nothing if we don't have a new enough Adwaita
|
||||
/// version to support the child exited banner.
|
||||
#[cfg(not(feature = "adw_1_3"))]
|
||||
mod imp {
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use gtk::{glib, glib::subclass::Signal, subclass::prelude::*};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SurfaceChildExited;
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for SurfaceChildExited {
|
||||
const NAME: &'static str = "GhosttySurfaceChildExited";
|
||||
type Type = super::SurfaceChildExited;
|
||||
type ParentType = gtk::Widget;
|
||||
}
|
||||
|
||||
impl ObjectImpl for SurfaceChildExited {
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
|
||||
SIGNALS.get_or_init(|| vec![Signal::builder("close-request").build()])
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for SurfaceChildExited {}
|
||||
}
|
||||
@@ -1657,18 +1657,29 @@ impl Window {
|
||||
let icon = "com.mitchellh.ghostty";
|
||||
let website = "https://ghostty.org";
|
||||
|
||||
// libadwaita >= 1.5 is assumed, so AdwAboutDialog is always used
|
||||
// (the Zig GtkAboutDialog fallback is unreachable here).
|
||||
let _ = adw_version::supports_dialogs();
|
||||
let dialog = adw::AboutDialog::builder()
|
||||
.application_name(name)
|
||||
.developer_name(tr("Ghostty Developers"))
|
||||
.application_icon(icon)
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.issue_url("https://github.com/ghostty-org/ghostty/issues")
|
||||
.website(website)
|
||||
.build();
|
||||
dialog.present(Some(self));
|
||||
if adw_version::supports_dialogs() {
|
||||
let dialog = adw::AboutDialog::builder()
|
||||
.application_name(name)
|
||||
.developer_name(tr("Ghostty Developers"))
|
||||
.application_icon(icon)
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.issue_url("https://github.com/ghostty-org/ghostty/issues")
|
||||
.website(website)
|
||||
.build();
|
||||
dialog.present(Some(self));
|
||||
} else {
|
||||
// libadwaita < 1.5 has no AdwAboutDialog: use the GTK one.
|
||||
gtk::show_about_dialog(
|
||||
Some(self),
|
||||
&[
|
||||
("program-name", &name),
|
||||
("logo-icon-name", &icon),
|
||||
("title", &tr("About Ghostty")),
|
||||
("version", &env!("CARGO_PKG_VERSION")),
|
||||
("website", &website),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn action_close_tab(&self, param: Option<&glib::Variant>) {
|
||||
|
||||
Reference in new issue
Block a user