Files
ghostty-rs/crates/ghostty-apprt-gtk/src/class/clipboard_confirmation_dialog.rs
T
ddidderr 126da77a37 style: apply nightly rustfmt import grouping across the workspace
rustfmt.toml sets group_imports, imports_granularity and imports_layout,
which are nightly-only; agents formatted with stable rustfmt, which
ignored them. This is a formatting-only pass with `cargo +nightly fmt`.

Test Plan:
- cargo clippy --workspace --all-targets -- -D warnings
- cargo test --workspace
2026-10-12 05:03:37 +02:00

262 lines
9.0 KiB
Rust

//! Port of apprt/gtk/class/clipboard_confirmation_dialog.zig
//! (`GhosttyClipboardConfirmationDialog`).
//!
//! Properties: `can-remember`, `request` (boxed [`ClipboardRequestBoxed`]),
//! `clipboard-contents` (GtkTextBuffer), `blur`. Signals `confirm(bool)` and
//! `deny(bool)` (bool = "remember choice").
use std::{
cell::{Cell, RefCell},
sync::OnceLock,
};
use adw::{prelude::*, subclass::prelude::*};
use ghostty_apprt::structs::ClipboardRequest;
use ghostty_os::i18n::tr;
use gtk::{glib, glib::subclass::Signal};
use super::dialog::{Dialog, DialogImpl};
/// GBoxed wrapper for [`ClipboardRequest`] (Zig: boxed `apprt.ClipboardRequest`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, glib::Boxed)]
#[boxed_type(name = "GhosttyClipboardRequest", nullable)]
pub struct ClipboardRequestBoxed(pub ClipboardRequest);
mod imp {
use adw::prelude::*;
use gtk::{glib, subclass::prelude::*};
use super::{
AdwAlertDialogImpl,
AdwDialogImpl,
Cell,
ClipboardRequest,
ClipboardRequestBoxed,
Dialog,
DialogImpl,
ObjectImpl,
ObjectSubclass,
OnceLock,
RefCell,
Signal,
WidgetImpl,
tr,
};
#[derive(Default, glib::Properties, gtk::CompositeTemplate)]
#[properties(wrapper_type = super::ClipboardConfirmationDialog)]
#[template(resource = "/com/mitchellh/ghostty/ui/1.4/clipboard-confirmation-dialog.ui")]
pub struct ClipboardConfirmationDialog {
/// The request that this dialog is for.
#[property(get, set, nullable)]
pub request: RefCell<Option<ClipboardRequestBoxed>>,
/// The clipboard contents being read/written.
#[property(get, set, nullable, name = "clipboard-contents")]
pub clipboard_contents: RefCell<Option<gtk::TextBuffer>>,
/// Whether the contents should be blurred.
#[property(get, set)]
pub blur: Cell<bool>,
/// Whether the user can remember the choice.
#[property(get, set, name = "can-remember")]
pub can_remember: Cell<bool>,
#[template_child]
pub text_view_scroll: TemplateChild<gtk::ScrolledWindow>,
#[template_child]
pub text_view: TemplateChild<gtk::TextView>,
#[template_child]
pub reveal_button: TemplateChild<gtk::Button>,
#[template_child]
pub hide_button: TemplateChild<gtk::Button>,
#[template_child]
pub remember_choice: TemplateChild<adw::SwitchRow>,
}
#[glib::object_subclass]
impl ObjectSubclass for ClipboardConfirmationDialog {
const NAME: &'static str = "GhosttyClipboardConfirmationDialog";
type Type = super::ClipboardConfirmationDialog;
type ParentType = Dialog;
fn class_init(klass: &mut Self::Class) {
Dialog::ensure_type();
klass.bind_template();
klass.bind_template_callbacks();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
#[gtk::template_callbacks]
impl ClipboardConfirmationDialog {
fn set_blurred(&self, blurred: bool, reveal_visible: bool, hide_visible: bool) {
self.text_view_scroll.set_sensitive(!blurred);
if blurred {
self.text_view.add_css_class("blurred");
} else {
self.text_view.remove_css_class("blurred");
}
self.reveal_button.set_visible(reveal_visible);
self.hide_button.set_visible(hide_visible);
}
#[template_callback]
fn notify_blur(&self, _pspec: &glib::ParamSpec) {
if self.blur.get() {
self.set_blurred(true, true, false);
} else {
self.set_blurred(false, false, false);
}
}
#[template_callback]
fn notify_request(&self, _pspec: &glib::ParamSpec) {
self.update_request();
}
pub fn update_request(&self) {
let Some(ClipboardRequestBoxed(req)) = *self.request.borrow() else {
return;
};
let obj = self.obj();
match req {
ClipboardRequest::Osc52Write(_) => {
obj.set_heading(Some(&tr("Authorize Clipboard Access")));
obj.set_body(&tr("An application is attempting to write to the clipboard. The current clipboard contents are shown below."));
}
ClipboardRequest::Osc52Read(_) => {
obj.set_heading(Some(&tr("Authorize Clipboard Access")));
obj.set_body(&tr("An application is attempting to read from the clipboard. The current clipboard contents are shown below."));
}
ClipboardRequest::Paste => {
obj.set_heading(Some(&tr("Warning: Potentially Unsafe Paste")));
obj.set_body(&tr("Pasting this text into the terminal may be dangerous as it looks like some commands may be executed."));
}
}
}
#[template_callback]
fn reveal_clicked(_button: &gtk::Button, this: &super::ClipboardConfirmationDialog) {
// The signal emitter is the button, so the dialog arrives as the
// trailing user-data argument (a `&self` receiver would be
// matched against the button and panic).
this.imp().set_blurred(false, false, true);
}
#[template_callback]
fn hide_clicked(_button: &gtk::Button, this: &super::ClipboardConfirmationDialog) {
this.imp().set_blurred(true, true, false);
}
}
#[glib::derived_properties]
impl ObjectImpl for ClipboardConfirmationDialog {
fn constructed(&self) {
self.parent_constructed();
// Mirrors `self.propBlur(..); self.propRequest(..)` in init.
self.notify_blur(&Self::type_pspec_dummy());
self.update_request();
}
fn signals() -> &'static [Signal] {
static SIGNALS: OnceLock<Vec<Signal>> = OnceLock::new();
SIGNALS.get_or_init(|| {
vec![
Signal::builder("deny")
.param_types([bool::static_type()])
.build(),
Signal::builder("confirm")
.param_types([bool::static_type()])
.build(),
]
})
}
fn dispose(&self) {
self.clipboard_contents.replace(None);
self.dispose_template();
}
}
impl ClipboardConfirmationDialog {
fn type_pspec_dummy() -> glib::ParamSpec {
glib::ParamSpecBoolean::builder("blur").build()
}
}
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]);
}
}
}
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;
}
impl ClipboardConfirmationDialog {
pub fn new() -> Self {
crate::register_resources();
glib::Object::new()
}
pub fn present(&self, parent: Option<&impl IsA<gtk::Widget>>) {
self.upcast_ref::<Dialog>().present(parent);
}
/// Get the clipboard request (`getRequest`).
pub fn get_request(&self) -> Option<ClipboardRequest> {
self.imp().request.borrow().map(|b| b.0)
}
/// Get the clipboard contents (`getClipboardContents`).
pub fn get_clipboard_contents(&self) -> Option<gtk::TextBuffer> {
self.imp().clipboard_contents.borrow().clone()
}
pub fn set_request_value(&self, req: ClipboardRequest) {
self.set_request(Some(ClipboardRequestBoxed(req)));
}
/// `confirm(remember)`.
pub fn connect_confirm<F: Fn(&Self, bool) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_closure(
"confirm",
false,
glib::closure_local!(move |obj: &Self, remember: bool| f(obj, remember)),
)
}
/// `deny(remember)`.
pub fn connect_deny<F: Fn(&Self, bool) + 'static>(&self, f: F) -> glib::SignalHandlerId {
self.connect_closure(
"deny",
false,
glib::closure_local!(move |obj: &Self, remember: bool| f(obj, remember)),
)
}
}
impl Default for ClipboardConfirmationDialog {
fn default() -> Self {
Self::new()
}
}