apprt/gtk-ng: clipboard confirmation dialog

This commit is contained in:
Mitchell Hashimoto
2025-07-22 09:51:23 -07:00
parent a91ed99054
commit 222c3c888d
4 changed files with 158 additions and 18 deletions

View File

@ -7,6 +7,7 @@ const gtk = @import("gtk");
const apprt = @import("../../../apprt.zig"); const apprt = @import("../../../apprt.zig");
const gresource = @import("../build/gresource.zig"); const gresource = @import("../build/gresource.zig");
const i18n = @import("../../../os/main.zig").i18n;
const Common = @import("../class.zig").Common; const Common = @import("../class.zig").Common;
const Dialog = @import("dialog.zig").Dialog; const Dialog = @import("dialog.zig").Dialog;
@ -25,6 +26,26 @@ pub const ClipboardConfirmationDialog = extern struct {
}); });
pub const properties = struct { pub const properties = struct {
pub const @"can-remember" = struct {
pub const name = "can-remember";
const impl = gobject.ext.defineProperty(
name,
Self,
bool,
.{
.nick = "Can Remember",
.blurb = "Allow remembering the choice.",
.default = false,
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"can_remember",
),
},
);
};
pub const request = struct { pub const request = struct {
pub const name = "request"; pub const name = "request";
const impl = gobject.ext.defineProperty( const impl = gobject.ext.defineProperty(
@ -43,12 +64,37 @@ pub const ClipboardConfirmationDialog = extern struct {
}, },
); );
}; };
pub const @"clipboard-contents" = struct {
pub const name = "clipboard-contents";
const impl = gobject.ext.defineProperty(
name,
Self,
?*gtk.TextBuffer,
.{
.nick = "Clipboard Contents",
.blurb = "The clipboard contents being read/written.",
.accessor = gobject.ext.privateFieldAccessor(
Self,
Private,
&Private.offset,
"clipboard_contents",
),
},
);
};
}; };
const Private = struct { const Private = struct {
/// The request that this dialog is for. /// The request that this dialog is for.
request: ?*apprt.ClipboardRequest = null, request: ?*apprt.ClipboardRequest = null,
/// The clipboard contents being read/written.
clipboard_contents: ?*gtk.TextBuffer = null,
/// Whether the user can remember the choice.
can_remember: bool = false,
pub var offset: c_int = 0; pub var offset: c_int = 0;
}; };
@ -58,16 +104,60 @@ pub const ClipboardConfirmationDialog = extern struct {
fn init(self: *Self, _: *Class) callconv(.C) void { fn init(self: *Self, _: *Class) callconv(.C) void {
gtk.Widget.initTemplate(self.as(gtk.Widget)); gtk.Widget.initTemplate(self.as(gtk.Widget));
// Some property signals
_ = gobject.Object.signals.notify.connect(
self,
?*anyopaque,
&propRequest,
null,
.{ .detail = "request" },
);
// Trigger initial values
self.propRequest(undefined, null);
} }
pub fn present(self: *Self, parent: ?*gtk.Widget) void { pub fn present(self: *Self, parent: ?*gtk.Widget) void {
self.as(Dialog).present(parent); self.as(Dialog).present(parent);
} }
//---------------------------------------------------------------
// Signal Handlers
fn propRequest(
self: *Self,
_: *gobject.ParamSpec,
_: ?*anyopaque,
) callconv(.c) void {
const priv = self.private();
const req = priv.request orelse return;
switch (req.*) {
.osc_52_write => {
self.as(Dialog.Parent).setHeading(i18n._("Authorize Clipboard Access"));
self.as(Dialog.Parent).setBody(i18n._("An application is attempting to write to the clipboard. The current clipboard contents are shown below."));
},
.osc_52_read => {
self.as(Dialog.Parent).setHeading(i18n._("Authorize Clipboard Access"));
self.as(Dialog.Parent).setBody(i18n._("An application is attempting to read from the clipboard. The current clipboard contents are shown below."));
},
.paste => {
self.as(Dialog.Parent).setHeading(i18n._("Warning: Potentially Unsafe Paste"));
self.as(Dialog.Parent).setBody(i18n._("Pasting this text into the terminal may be dangerous as it looks like some commands may be executed."));
},
}
}
//--------------------------------------------------------------- //---------------------------------------------------------------
// Virtual methods // Virtual methods
fn dispose(self: *Self) callconv(.C) void { fn dispose(self: *Self) callconv(.C) void {
const priv = self.private();
if (priv.clipboard_contents) |v| {
v.unref();
priv.clipboard_contents = null;
}
gtk.Widget.disposeTemplate( gtk.Widget.disposeTemplate(
self.as(gtk.Widget), self.as(gtk.Widget),
getGObjectType(), getGObjectType(),
@ -79,6 +169,19 @@ pub const ClipboardConfirmationDialog = extern struct {
); );
} }
fn finalize(self: *Self) callconv(.C) void {
const priv = self.private();
if (priv.request) |v| {
glib.ext.destroy(v);
priv.request = null;
}
gobject.Object.virtual_methods.finalize.call(
Class.parent,
self.as(Parent),
);
}
const C = Common(Self, Private); const C = Common(Self, Private);
pub const as = C.as; pub const as = C.as;
pub const ref = C.ref; pub const ref = C.ref;
@ -101,15 +204,18 @@ pub const ClipboardConfirmationDialog = extern struct {
); );
// Bindings // Bindings
//class.bindTemplateChildPrivate("label", .{}); //class.bindTemplateChildPrivate("remember_bin", .{});
// Properties // Properties
gobject.ext.registerProperties(class, &.{ gobject.ext.registerProperties(class, &.{
properties.@"can-remember".impl,
properties.@"clipboard-contents".impl,
properties.request.impl, properties.request.impl,
}); });
// Virtual methods // Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose); gobject.Object.virtual_methods.dispose.implement(class, &dispose);
gobject.Object.virtual_methods.finalize.implement(class, &finalize);
} }
pub const as = C.Class.as; pub const as = C.Class.as;

View File

@ -2001,19 +2001,21 @@ const Clipboard = struct {
return; return;
} }
// Build a text buffer for our contents
const contents_buf: *gtk.TextBuffer = .new(null);
defer contents_buf.unref();
contents_buf.insertAtCursor(val, @intCast(val.len));
// Confirm // Confirm
const diag = gobject.ext.newInstance( const diag = gobject.ext.newInstance(
ClipboardConfirmationDialog, ClipboardConfirmationDialog,
.{ .request = &apprt.ClipboardRequest{ .{
.osc_52_write = clipboard_type, .request = &apprt.ClipboardRequest{ .osc_52_write = clipboard_type },
} }, .@"can-remember" = true,
.@"clipboard-contents" = contents_buf,
},
); );
// We need to trigger the dialog
diag.present(self.as(gtk.Widget)); diag.present(self.as(gtk.Widget));
log.warn("TODO: confirmation window", .{});
} }
/// Request data from the clipboard (read the clipboard). This /// Request data from the clipboard (read the clipboard). This

View File

@ -4,6 +4,9 @@
* https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.3/styles-and-appearance.html#custom-styles * https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1.3/styles-and-appearance.html#custom-styles
*/ */
/*
* GhosttySurface URL overlay
*/
label.url-overlay { label.url-overlay {
padding: 4px 8px 4px 8px; padding: 4px 8px 4px 8px;
outline-style: solid; outline-style: solid;
@ -23,6 +26,9 @@ label.url-overlay.right {
border-radius: 6px 0px 0px 0px; border-radius: 6px 0px 0px 0px;
} }
/*
* GhosttySurface resize overlay
*/
.size-overlay label { .size-overlay label {
padding: 4px 8px 4px 8px; padding: 4px 8px 4px 8px;
border-radius: 6px 6px 6px 6px; border-radius: 6px 6px 6px 6px;
@ -30,3 +36,22 @@ label.url-overlay.right {
outline-width: 1px; outline-width: 1px;
outline-color: #555555; outline-color: #555555;
} }
/*
* GhosttyClipboardConfirmationDialog
*
* Based on boxed-list-separate:
* https://gitlab.gnome.org/GNOME/libadwaita/-/blob/ad446167acf3e6d1ee693f98ca636268be8592a1/src/stylesheet/widgets/_lists.scss#L548
*/
.clipboard-confirmation-dialog list {
background: none;
}
.clipboard-confirmation-dialog list > row {
border: none;
margin-bottom: 12px;
}
.clipboard-confirmation-dialog list > row:last-child {
margin-bottom: 0;
}

View File

@ -3,8 +3,13 @@ using Gtk 4.0;
using Adw 1; using Adw 1;
template $GhosttyClipboardConfirmationDialog: $GhosttyDialog { template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
styles [
"clipboard-confirmation-dialog",
]
heading: _("Authorize Clipboard Access"); heading: _("Authorize Clipboard Access");
body: _("An application is attempting to write to the clipboard. The current clipboard contents are shown below."); // Not localized because this is a placeholder users never see.
body: "If you see this text, there is a bug in Ghostty. Please report it.";
responses [ responses [
cancel: _("Deny") suggested, cancel: _("Deny") suggested,
@ -17,10 +22,6 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
extra-child: ListBox { extra-child: ListBox {
selection-mode: none; selection-mode: none;
styles [
"boxed-list-separate",
]
Overlay { Overlay {
styles [ styles [
"osd", "osd",
@ -32,6 +33,10 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
height-request: 200; height-request: 200;
TextView text_view { TextView text_view {
styles [
"clipboard-content-view",
]
cursor-visible: false; cursor-visible: false;
editable: false; editable: false;
monospace: true; monospace: true;
@ -39,10 +44,7 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
left-margin: 8; left-margin: 8;
bottom-margin: 8; bottom-margin: 8;
right-margin: 8; right-margin: 8;
buffer: bind template.clipboard-contents;
styles [
"clipboard-content-view",
]
} }
} }
@ -74,6 +76,11 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
} }
Adw.SwitchRow remember_choice { Adw.SwitchRow remember_choice {
styles [
"card",
]
visible: bind template.can-remember;
title: _("Remember choice for this split"); title: _("Remember choice for this split");
subtitle: _("Reload configuration to show this prompt again"); subtitle: _("Reload configuration to show this prompt again");
} }