apprt/gtk-ng: bind template callbacks so we can connect signals in blp (#8035)

This creates a helper so that we can call
[`gtk_widget_class_bind_template_callback_full`](https://docs.gtk.org/gtk4/class_method.Widget.bind_template_callback_full.html)
and register signal handlers directly in our Blueprint file. This gets
rid of a LOT of boilerplate!

A draft, since there are TODOs:

- [x] Add comptime verification of the `func` param
- [x] Convert more blueprint files
This commit is contained in:
Mitchell Hashimoto
2025-07-23 07:30:25 -07:00
committed by GitHub
8 changed files with 170 additions and 356 deletions

View File

@ -114,6 +114,34 @@ pub fn Common(
); );
} }
}).bindTemplateChildPrivate else {}; }).bindTemplateChildPrivate else {};
/// Bind a function pointer to a template callback symbol.
pub fn bindTemplateCallback(
class: *Self.Class,
comptime name: [:0]const u8,
comptime func: anytype,
) void {
{
const ptr_ti = @typeInfo(@TypeOf(func));
if (ptr_ti != .pointer) {
@compileError("bound function must be a pointer type");
}
if (ptr_ti.pointer.size != .one) {
@compileError("bound function must be a pointer to a function");
}
const func_ti = @typeInfo(ptr_ti.pointer.child);
if (func_ti != .@"fn") {
@compileError("bound function must be a function pointer");
}
}
gtk.Widget.Class.bindTemplateCallbackFull(
class.as(gtk.Widget.Class),
name,
@ptrCast(func),
);
}
}; };
}; };
} }

View File

@ -163,40 +163,6 @@ 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));
const priv = self.private();
// Signals
_ = gtk.Button.signals.clicked.connect(
priv.reveal_button,
*Self,
revealButtonClicked,
self,
.{},
);
_ = gtk.Button.signals.clicked.connect(
priv.hide_button,
*Self,
hideButtonClicked,
self,
.{},
);
// Some property signals
_ = gobject.Object.signals.notify.connect(
self,
?*anyopaque,
&propBlur,
null,
.{ .detail = "blur" },
);
_ = gobject.Object.signals.notify.connect(
self,
?*anyopaque,
&propRequest,
null,
.{ .detail = "request" },
);
// Trigger initial values // Trigger initial values
self.propBlur(undefined, null); self.propBlur(undefined, null);
self.propRequest(undefined, null); self.propRequest(undefined, null);
@ -374,6 +340,12 @@ pub const ClipboardConfirmationDialog = extern struct {
class.bindTemplateChildPrivate("remember_choice", .{}); class.bindTemplateChildPrivate("remember_choice", .{});
} }
// Template Callbacks
class.bindTemplateCallback("reveal_clicked", &revealButtonClicked);
class.bindTemplateCallback("hide_clicked", &hideButtonClicked);
class.bindTemplateCallback("notify_blur", &propBlur);
class.bindTemplateCallback("notify_request", &propRequest);
// Properties // Properties
gobject.ext.registerProperties(class, &.{ gobject.ext.registerProperties(class, &.{
properties.blur.impl, properties.blur.impl,
@ -394,5 +366,6 @@ pub const ClipboardConfirmationDialog = extern struct {
pub const as = C.Class.as; pub const as = C.Class.as;
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate; pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
}; };
}; };

View File

@ -245,10 +245,6 @@ pub const Surface = extern struct {
/// focus events. /// focus events.
focused: bool = true, focused: bool = true,
/// The overlay we use for things such as the URL hover label
/// or resize box. Bound from the template.
overlay: *gtk.Overlay,
/// The GLAarea that renders the actual surface. This is a binding /// The GLAarea that renders the actual surface. This is a binding
/// to the template so it doesn't have to be unrefed manually. /// to the template so it doesn't have to be unrefed manually.
gl_area: *gtk.GLArea, gl_area: *gtk.GLArea,
@ -256,18 +252,10 @@ pub const Surface = extern struct {
/// The labels for the left/right sides of the URL hover tooltip. /// The labels for the left/right sides of the URL hover tooltip.
url_left: *gtk.Label, url_left: *gtk.Label,
url_right: *gtk.Label, url_right: *gtk.Label,
url_ec_motion: *gtk.EventControllerMotion,
/// The resize overlay /// The resize overlay
resize_overlay: *ResizeOverlay, resize_overlay: *ResizeOverlay,
// Event controllers
ec_focus: *gtk.EventControllerFocus,
ec_key: *gtk.EventControllerKey,
ec_motion: *gtk.EventControllerMotion,
ec_scroll: *gtk.EventControllerScroll,
gesture_click: *gtk.GestureClick,
/// The apprt Surface. /// The apprt Surface.
rt_surface: ApprtSurface = undefined, rt_surface: ApprtSurface = undefined,
@ -288,7 +276,7 @@ pub const Surface = extern struct {
/// Various input method state. All related to key input. /// Various input method state. All related to key input.
in_keyevent: IMKeyEvent = .false, in_keyevent: IMKeyEvent = .false,
im_context: ?*gtk.IMMulticontext = null, im_context: *gtk.IMMulticontext,
im_composing: bool = false, im_composing: bool = false,
im_buf: [128]u8 = undefined, im_buf: [128]u8 = undefined,
im_len: u7 = 0, im_len: u7 = 0,
@ -368,13 +356,13 @@ pub const Surface = extern struct {
// The block below is all related to input method handling. See the function // The block below is all related to input method handling. See the function
// comment for some high level details and then the comments within // comment for some high level details and then the comments within
// the block for more specifics. // the block for more specifics.
if (priv.im_context) |im_context| { {
// This can trigger an input method so we need to notify the im context // This can trigger an input method so we need to notify the im context
// where the cursor is so it can render the dropdowns in the correct // where the cursor is so it can render the dropdowns in the correct
// place. // place.
if (priv.core_surface) |surface| { if (priv.core_surface) |surface| {
const ime_point = surface.imePoint(); const ime_point = surface.imePoint();
im_context.as(gtk.IMContext).setCursorLocation(&.{ priv.im_context.as(gtk.IMContext).setCursorLocation(&.{
.f_x = @intFromFloat(ime_point.x), .f_x = @intFromFloat(ime_point.x),
.f_y = @intFromFloat(ime_point.y), .f_y = @intFromFloat(ime_point.y),
.f_width = 1, .f_width = 1,
@ -415,7 +403,7 @@ pub const Surface = extern struct {
// triggered despite being technically consumed. At the time of // triggered despite being technically consumed. At the time of
// writing, both Kitty and Alacritty have the same behavior. I // writing, both Kitty and Alacritty have the same behavior. I
// know of no way to fix this. // know of no way to fix this.
const im_handled = im_context.as(gtk.IMContext).filterKeypress(event) != 0; const im_handled = priv.im_context.as(gtk.IMContext).filterKeypress(event) != 0;
// log.warn("GTKIM: im_handled={} im_len={} im_composing={}", .{ // log.warn("GTKIM: im_handled={} im_len={} im_composing={}", .{
// im_handled, // im_handled,
// self.im_len, // self.im_len,
@ -546,10 +534,7 @@ pub const Surface = extern struct {
// because there is other IME state that we want to preserve, // because there is other IME state that we want to preserve,
// such as quotation mark ordering for Chinese input. // such as quotation mark ordering for Chinese input.
if (priv.im_composing) { if (priv.im_composing) {
if (priv.im_context) |im_context| { priv.im_context.as(gtk.IMContext).reset();
im_context.as(gtk.IMContext).reset();
}
surface.preeditCallback(null) catch {}; surface.preeditCallback(null) catch {};
} }
@ -803,238 +788,30 @@ pub const Surface = extern struct {
priv.config = app.getConfig(); priv.config = app.getConfig();
} }
// Setup our event controllers to get input events
_ = gtk.EventControllerKey.signals.key_pressed.connect(
priv.ec_key,
*Self,
ecKeyPressed,
self,
.{},
);
_ = gtk.EventControllerKey.signals.key_released.connect(
priv.ec_key,
*Self,
ecKeyReleased,
self,
.{},
);
// Focus controller will tell us about focus enter/exit events
_ = gtk.EventControllerFocus.signals.enter.connect(
priv.ec_focus,
*Self,
ecFocusEnter,
self,
.{},
);
_ = gtk.EventControllerFocus.signals.leave.connect(
priv.ec_focus,
*Self,
ecFocusLeave,
self,
.{},
);
// Clicks
_ = gtk.GestureClick.signals.pressed.connect(
priv.gesture_click,
*Self,
gcMouseDown,
self,
.{},
);
_ = gtk.GestureClick.signals.released.connect(
priv.gesture_click,
*Self,
gcMouseUp,
self,
.{},
);
// Mouse movement
_ = gtk.EventControllerMotion.signals.motion.connect(
priv.ec_motion,
*Self,
ecMouseMotion,
self,
.{},
);
_ = gtk.EventControllerMotion.signals.leave.connect(
priv.ec_motion,
*Self,
ecMouseLeave,
self,
.{},
);
// Scroll
_ = gtk.EventControllerScroll.signals.scroll.connect(
priv.ec_scroll,
*Self,
ecMouseScroll,
self,
.{},
);
_ = gtk.EventControllerScroll.signals.scroll_begin.connect(
priv.ec_scroll,
*Self,
ecMouseScrollPrecisionBegin,
self,
.{},
);
_ = gtk.EventControllerScroll.signals.scroll_end.connect(
priv.ec_scroll,
*Self,
ecMouseScrollPrecisionEnd,
self,
.{},
);
// Setup our input method state // Setup our input method state
const im_context = gtk.IMMulticontext.new();
priv.im_context = im_context;
priv.in_keyevent = .false; priv.in_keyevent = .false;
priv.im_composing = false; priv.im_composing = false;
priv.im_len = 0; priv.im_len = 0;
_ = gtk.IMContext.signals.preedit_start.connect(
im_context,
*Self,
imPreeditStart,
self,
.{},
);
_ = gtk.IMContext.signals.preedit_changed.connect(
im_context,
*Self,
imPreeditChanged,
self,
.{},
);
_ = gtk.IMContext.signals.preedit_end.connect(
im_context,
*Self,
imPreeditEnd,
self,
.{},
);
_ = gtk.IMContext.signals.commit.connect(
im_context,
*Self,
imCommit,
self,
.{},
);
// Initialize our GLArea. We could do a lot of this in // Initialize our GLArea. We only set the values we can't set
// the Blueprint file but I think its cleaner to separate // in our blueprint file.
// the "UI" part of the blueprint file from the internal logic/config
// part.
const gl_area = priv.gl_area; const gl_area = priv.gl_area;
gl_area.setRequiredVersion( gl_area.setRequiredVersion(
renderer.OpenGL.MIN_VERSION_MAJOR, renderer.OpenGL.MIN_VERSION_MAJOR,
renderer.OpenGL.MIN_VERSION_MINOR, renderer.OpenGL.MIN_VERSION_MINOR,
); );
gl_area.setHasStencilBuffer(0);
gl_area.setHasDepthBuffer(0);
gl_area.setUseEs(0);
gl_area.as(gtk.Widget).setCursorFromName("text"); gl_area.as(gtk.Widget).setCursorFromName("text");
_ = gtk.Widget.signals.realize.connect(
gl_area,
*Self,
glareaRealize,
self,
.{},
);
_ = gtk.Widget.signals.unrealize.connect(
gl_area,
*Self,
glareaUnrealize,
self,
.{},
);
_ = gtk.GLArea.signals.render.connect(
gl_area,
*Self,
glareaRender,
self,
.{},
);
_ = gtk.GLArea.signals.resize.connect(
gl_area,
*Self,
glareaResize,
self,
.{},
);
// Some property signals
_ = gobject.Object.signals.notify.connect(
self,
?*anyopaque,
&propConfig,
null,
.{ .detail = "config" },
);
_ = gobject.Object.signals.notify.connect(
self,
?*anyopaque,
&propMouseHoverUrl,
null,
.{ .detail = "mouse-hover-url" },
);
_ = gobject.Object.signals.notify.connect(
self,
?*anyopaque,
&propMouseHidden,
null,
.{ .detail = "mouse-hidden" },
);
_ = gobject.Object.signals.notify.connect(
self,
?*anyopaque,
&propMouseShape,
null,
.{ .detail = "mouse-shape" },
);
// Some other initialization steps
self.initUrlOverlay();
// Initialize our config // Initialize our config
self.propConfig(undefined, null); self.propConfig(undefined, null);
} }
fn initUrlOverlay(self: *Self) void {
const priv = self.private();
// Setup a motion controller to handle moving the label
// to avoid the mouse.
_ = gtk.EventControllerMotion.signals.enter.connect(
priv.url_ec_motion,
*Self,
ecUrlMouseEnter,
self,
.{},
);
_ = gtk.EventControllerMotion.signals.leave.connect(
priv.url_ec_motion,
*Self,
ecUrlMouseLeave,
self,
.{},
);
}
fn dispose(self: *Self) callconv(.C) void { fn dispose(self: *Self) callconv(.C) void {
const priv = self.private(); const priv = self.private();
if (priv.config) |v| { if (priv.config) |v| {
v.unref(); v.unref();
priv.config = null; priv.config = null;
} }
if (priv.im_context) |v| {
v.unref();
priv.im_context = null;
}
gtk.Widget.disposeTemplate( gtk.Widget.disposeTemplate(
self.as(gtk.Widget), self.as(gtk.Widget),
@ -1260,22 +1037,14 @@ pub const Surface = extern struct {
fn ecFocusEnter(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void { fn ecFocusEnter(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
const priv = self.private(); const priv = self.private();
priv.focused = true; priv.focused = true;
priv.im_context.as(gtk.IMContext).focusIn();
if (priv.im_context) |im_context| {
im_context.as(gtk.IMContext).focusIn();
}
_ = glib.idleAddOnce(idleFocus, self.ref()); _ = glib.idleAddOnce(idleFocus, self.ref());
} }
fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void { fn ecFocusLeave(_: *gtk.EventControllerFocus, self: *Self) callconv(.c) void {
const priv = self.private(); const priv = self.private();
priv.focused = false; priv.focused = false;
priv.im_context.as(gtk.IMContext).focusOut();
if (priv.im_context) |im_context| {
im_context.as(gtk.IMContext).focusOut();
}
_ = glib.idleAddOnce(idleFocus, self.ref()); _ = glib.idleAddOnce(idleFocus, self.ref());
} }
@ -1647,9 +1416,8 @@ pub const Surface = extern struct {
// Setup our input method. We do this here because this will // Setup our input method. We do this here because this will
// create a strong reference back to ourself and we want to be // create a strong reference back to ourself and we want to be
// able to release that in unrealize. // able to release that in unrealize.
if (self.private().im_context) |im_context| { const priv = self.private();
im_context.as(gtk.IMContext).setClientWidget(self.as(gtk.Widget)); priv.im_context.as(gtk.IMContext).setClientWidget(self.as(gtk.Widget));
}
} }
fn glareaUnrealize( fn glareaUnrealize(
@ -1683,9 +1451,7 @@ pub const Surface = extern struct {
} }
// Unset our input method // Unset our input method
if (priv.im_context) |im_context| { priv.im_context.as(gtk.IMContext).setClientWidget(null);
im_context.as(gtk.IMContext).setClientWidget(null);
}
} }
fn glareaRender( fn glareaRender(
@ -1899,29 +1665,38 @@ pub const Surface = extern struct {
); );
// Bindings // Bindings
class.bindTemplateChildPrivate("overlay", .{});
class.bindTemplateChildPrivate("gl_area", .{}); class.bindTemplateChildPrivate("gl_area", .{});
class.bindTemplateChildPrivate("url_left", .{}); class.bindTemplateChildPrivate("url_left", .{});
class.bindTemplateChildPrivate("url_right", .{}); class.bindTemplateChildPrivate("url_right", .{});
class.bindTemplateChildPrivate("resize_overlay", .{}); class.bindTemplateChildPrivate("resize_overlay", .{});
class.bindTemplateChildPrivate("im_context", .{});
// EventControllers don't work with our helper. // Template Callbacks
// https://github.com/ianprime0509/zig-gobject/issues/111 class.bindTemplateCallback("focus_enter", &ecFocusEnter);
inline for (&.{ class.bindTemplateCallback("focus_leave", &ecFocusLeave);
"ec_focus", class.bindTemplateCallback("key_pressed", &ecKeyPressed);
"ec_key", class.bindTemplateCallback("key_released", &ecKeyReleased);
"ec_motion", class.bindTemplateCallback("mouse_down", &gcMouseDown);
"ec_scroll", class.bindTemplateCallback("mouse_up", &gcMouseUp);
"gesture_click", class.bindTemplateCallback("mouse_motion", &ecMouseMotion);
"url_ec_motion", class.bindTemplateCallback("mouse_leave", &ecMouseLeave);
}) |name| { class.bindTemplateCallback("scroll", &ecMouseScroll);
gtk.Widget.Class.bindTemplateChildFull( class.bindTemplateCallback("scroll_begin", &ecMouseScrollPrecisionBegin);
gobject.ext.as(gtk.Widget.Class, class), class.bindTemplateCallback("scroll_end", &ecMouseScrollPrecisionEnd);
name, class.bindTemplateCallback("gl_realize", &glareaRealize);
@intFromBool(false), class.bindTemplateCallback("gl_unrealize", &glareaUnrealize);
Private.offset + @offsetOf(Private, name), class.bindTemplateCallback("gl_render", &glareaRender);
); class.bindTemplateCallback("gl_resize", &glareaResize);
} class.bindTemplateCallback("im_preedit_start", &imPreeditStart);
class.bindTemplateCallback("im_preedit_changed", &imPreeditChanged);
class.bindTemplateCallback("im_preedit_end", &imPreeditEnd);
class.bindTemplateCallback("im_commit", &imCommit);
class.bindTemplateCallback("url_mouse_enter", &ecUrlMouseEnter);
class.bindTemplateCallback("url_mouse_leave", &ecUrlMouseLeave);
class.bindTemplateCallback("notify_config", &propConfig);
class.bindTemplateCallback("notify_mouse_hover_url", &propMouseHoverUrl);
class.bindTemplateCallback("notify_mouse_hidden", &propMouseHidden);
class.bindTemplateCallback("notify_mouse_shape", &propMouseShape);
// Properties // Properties
gobject.ext.registerProperties(class, &.{ gobject.ext.registerProperties(class, &.{
@ -1946,6 +1721,7 @@ pub const Surface = extern struct {
pub const as = C.Class.as; pub const as = C.Class.as;
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate; pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
}; };
}; };

View File

@ -36,15 +36,6 @@ pub const Window = 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));
const surface = self.private().surface;
_ = Surface.signals.@"close-request".connect(
surface,
*Self,
surfaceCloseRequest,
self,
.{},
);
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
@ -102,11 +93,15 @@ pub const Window = extern struct {
// Bindings // Bindings
class.bindTemplateChildPrivate("surface", .{}); class.bindTemplateChildPrivate("surface", .{});
// Template Callbacks
class.bindTemplateCallback("surface_close_request", &surfaceCloseRequest);
// Virtual methods // Virtual methods
gobject.Object.virtual_methods.dispose.implement(class, &dispose); gobject.Object.virtual_methods.dispose.implement(class, &dispose);
} }
pub const as = C.Class.as; pub const as = C.Class.as;
pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate; pub const bindTemplateChildPrivate = C.Class.bindTemplateChildPrivate;
pub const bindTemplateCallback = C.Class.bindTemplateCallback;
}; };
}; };

View File

@ -7,6 +7,8 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
"clipboard-confirmation-dialog", "clipboard-confirmation-dialog",
] ]
notify::blur => $notify_blur();
notify::request => $notify_request();
heading: _("Authorize Clipboard Access"); heading: _("Authorize Clipboard Access");
// Not localized because this is a placeholder users never see. // 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."; body: "If you see this text, there is a bug in Ghostty. Please report it.";
@ -50,6 +52,7 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
[overlay] [overlay]
Button reveal_button { Button reveal_button {
clicked => $reveal_clicked();
visible: false; visible: false;
halign: end; halign: end;
valign: start; valign: start;
@ -63,6 +66,7 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
[overlay] [overlay]
Button hide_button { Button hide_button {
clicked => $hide_clicked();
visible: false; visible: false;
halign: end; halign: end;
valign: start; valign: start;

View File

@ -2,19 +2,27 @@ using Gtk 4.0;
using Adw 1; using Adw 1;
template $GhosttySurface: Adw.Bin { template $GhosttySurface: Adw.Bin {
// We need to wrap our Overlay one more time because if you bind a notify::config => $notify_config();
// direct child of your widget to a property, it will double free: notify::mouse-hover-url => $notify_mouse_hover_url();
// https://gitlab.gnome.org/GNOME/gtk/-/blob/847571a1e314aba79260e4ef282e2ed9ba91a0d9/gtk/gtkwidget.c#L11423-11425 notify::mouse-hidden => $notify_mouse_hidden();
Adw.Bin { notify::mouse-shape => $notify_mouse_shape();
Overlay overlay {
Overlay {
focusable: false; focusable: false;
focus-on-click: false; focus-on-click: false;
GLArea gl_area { GLArea gl_area {
realize => $gl_realize();
unrealize => $gl_unrealize();
render => $gl_render();
resize => $gl_resize();
hexpand: true; hexpand: true;
vexpand: true; vexpand: true;
focusable: true; focusable: true;
focus-on-click: true; focus-on-click: true;
has-stencil-buffer: false;
has-depth-buffer: false;
use-es: false;
} }
[overlay] [overlay]
@ -35,7 +43,10 @@ template $GhosttySurface: Adw.Bin {
valign: end; valign: end;
label: bind template.mouse-hover-url; label: bind template.mouse-hover-url;
EventControllerMotion url_ec_motion {} EventControllerMotion url_ec_motion {
enter => $url_mouse_enter();
leave => $url_mouse_leave();
}
} }
[overlay] [overlay]
@ -50,18 +61,39 @@ template $GhosttySurface: Adw.Bin {
label: bind template.mouse-hover-url; label: bind template.mouse-hover-url;
} }
} }
}
// Event controllers for interactivity // Event controllers for interactivity
EventControllerFocus ec_focus {} EventControllerFocus {
enter => $focus_enter();
leave => $focus_leave();
}
EventControllerKey ec_key {} EventControllerKey {
key-pressed => $key_pressed();
key-released => $key_released();
}
EventControllerMotion ec_motion {} EventControllerMotion {
motion => $mouse_motion();
leave => $mouse_leave();
}
EventControllerScroll ec_scroll {} EventControllerScroll {
scroll => $scroll();
scroll-begin => $scroll_begin();
scroll-end => $scroll_end();
}
GestureClick gesture_click { GestureClick {
pressed => $mouse_down();
released => $mouse_up();
button: 0; button: 0;
} }
} }
IMMulticontext im_context {
preedit-start => $im_preedit_start();
preedit-changed => $im_preedit_changed();
preedit-end => $im_preedit_end();
commit => $im_commit();
}

View File

@ -7,6 +7,8 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
"clipboard-confirmation-dialog", "clipboard-confirmation-dialog",
] ]
notify::blur => $notify_blur();
notify::request => $notify_request();
heading: _("Authorize Clipboard Access"); heading: _("Authorize Clipboard Access");
// Not localized because this is a placeholder users never see. // 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."; body: "If you see this text, there is a bug in Ghostty. Please report it.";
@ -50,6 +52,7 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
[overlay] [overlay]
Button reveal_button { Button reveal_button {
clicked => $reveal_clicked();
visible: false; visible: false;
halign: end; halign: end;
valign: start; valign: start;
@ -63,6 +66,7 @@ template $GhosttyClipboardConfirmationDialog: $GhosttyDialog {
[overlay] [overlay]
Button hide_button { Button hide_button {
clicked => $hide_clicked();
visible: false; visible: false;
halign: end; halign: end;
valign: start; valign: start;

View File

@ -5,5 +5,7 @@ template $GhosttyWindow: Adw.ApplicationWindow {
default-width: 800; default-width: 800;
default-height: 600; default-height: 600;
content: $GhosttySurface surface {}; content: $GhosttySurface surface {
close-request => $surface_close_request();
};
} }