diff --git a/src/apprt/gtk/Builder.zig b/src/apprt/gtk/Builder.zig index 473abc0f7..028629200 100644 --- a/src/apprt/gtk/Builder.zig +++ b/src/apprt/gtk/Builder.zig @@ -9,45 +9,101 @@ const gobject = @import("gobject"); resource_name: [:0]const u8, builder: ?*gtk.Builder, -pub fn init(comptime name: []const u8, comptime kind: enum { blp, ui }) Builder { - comptime { +pub fn init( + /// The "name" of the resource. + comptime name: []const u8, + /// The major version of the minimum Adwaita version that is required to use + /// this resource. + comptime major: u16, + /// The minor version of the minimum Adwaita version that is required to use + /// this resource. + comptime minor: u16, + /// `blp` signifies that the resource is a Blueprint that has been compiled + /// to GTK Builder XML at compile time. `ui` signifies that the resource is + /// a GTK Builder XML file that is included in the Ghostty source (perhaps + /// because the Blueprint compiler on some target platforms cannot compile a + /// Blueprint that generates the necessary resources). + comptime kind: enum { blp, ui }, +) Builder { + const resource_path = comptime resource_path: { + const gresource = @import("gresource.zig"); switch (kind) { .blp => { - // Use @embedFile to make sure that the file exists at compile - // time. Zig _should_ discard the data so that it doesn't end - // up in the final executable. At runtime we will load the data - // from a GResource. - _ = @embedFile("ui/" ++ name ++ ".blp"); - // Check to make sure that our file is listed as a // `blueprint_file` in `gresource.zig`. If it isn't Ghostty // could crash at runtime when we try and load a nonexistent // GResource. - const gresource = @import("gresource.zig"); - for (gresource.blueprint_files) |blueprint_file| { - if (std.mem.eql(u8, blueprint_file.name, name)) break; + for (gresource.blueprint_files) |file| { + if (major != file.major or minor != file.minor or !std.mem.eql(u8, file.name, name)) continue; + // Use @embedFile to make sure that the `.blp` file exists + // at compile time. Zig _should_ discard the data so that + // it doesn't end up in the final executable. At runtime we + // will load the data from a GResource. + const blp_filename = std.fmt.comptimePrint( + "ui/{d}.{d}/{s}.blp", + .{ + file.major, + file.minor, + file.name, + }, + ); + _ = @embedFile(blp_filename); + break :resource_path std.fmt.comptimePrint( + "/com/mitchellh/ghostty/ui/{d}.{d}/{s}.ui", + .{ + file.major, + file.minor, + file.name, + }, + ); } else @compileError("missing blueprint file '" ++ name ++ "' in gresource.zig"); }, .ui => { - // Use @embedFile to make sure that the file exists at compile - // time. Zig _should_ discard the data so that it doesn't end - // up in the final executable. At runtime we will load the data - // from a GResource. - _ = @embedFile("ui/" ++ name ++ ".ui"); - // Check to make sure that our file is listed as a `ui_file` in // `gresource.zig`. If it isn't Ghostty could crash at runtime // when we try and load a nonexistent GResource. - const gresource = @import("gresource.zig"); - for (gresource.ui_files) |ui_file| { - if (std.mem.eql(u8, ui_file, name)) break; + for (gresource.ui_files) |file| { + if (major != file.major or minor != file.minor or !std.mem.eql(u8, file.name, name)) continue; + // Use @embedFile to make sure that the `.ui` file exists + // at compile time. Zig _should_ discard the data so that + // it doesn't end up in the final executable. At runtime we + // will load the data from a GResource. + const ui_filename = std.fmt.comptimePrint( + "ui/{d}.{d}/{s}.ui", + .{ + file.major, + file.minor, + file.name, + }, + ); + _ = @embedFile(ui_filename); + // Also use @embedFile to make sure that a matching `.blp` + // file exists at compile time. Zig _should_ discard the + // data so that it doesn't end up in the final executable. + const blp_filename = std.fmt.comptimePrint( + "ui/{d}.{d}/{s}.blp", + .{ + file.major, + file.minor, + file.name, + }, + ); + _ = @embedFile(blp_filename); + break :resource_path std.fmt.comptimePrint( + "/com/mitchellh/ghostty/ui/{d}.{d}/{s}.ui", + .{ + file.major, + file.minor, + file.name, + }, + ); } else @compileError("missing ui file '" ++ name ++ "' in gresource.zig"); }, } - } + }; return .{ - .resource_name = "/com/mitchellh/ghostty/ui/" ++ name ++ ".ui", + .resource_name = resource_path, .builder = null, }; } diff --git a/src/apprt/gtk/ClipboardConfirmationWindow.zig b/src/apprt/gtk/ClipboardConfirmationWindow.zig index cf417b668..632f64fa3 100644 --- a/src/apprt/gtk/ClipboardConfirmationWindow.zig +++ b/src/apprt/gtk/ClipboardConfirmationWindow.zig @@ -4,18 +4,24 @@ const ClipboardConfirmation = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; +const gtk = @import("gtk"); +const adw = @import("adw"); +const gobject = @import("gobject"); +const gio = @import("gio"); + const apprt = @import("../../apprt.zig"); const CoreSurface = @import("../../Surface.zig"); const App = @import("App.zig"); const View = @import("View.zig"); -const c = @import("c.zig").c; +const Builder = @import("Builder.zig"); +const adwaita = @import("adwaita.zig"); const log = std.log.scoped(.gtk); -app: *App, -window: *c.GtkWindow, -view: PrimaryView, +const DialogType = if (adwaita.versionAtLeast(1, 5, 0)) adw.AlertDialog else adw.MessageDialog; +app: *App, +dialog: *DialogType, data: [:0]u8, core_surface: *CoreSurface, pending_req: apprt.ClipboardRequest, @@ -57,201 +63,92 @@ fn init( core_surface: *CoreSurface, request: apprt.ClipboardRequest, ) !void { - // Create the window - const window = c.gtk_window_new(); - const gtk_window: *c.GtkWindow = @ptrCast(window); - errdefer c.gtk_window_destroy(gtk_window); - c.gtk_window_set_title(gtk_window, titleText(request)); - c.gtk_window_set_default_size(gtk_window, 550, 275); - c.gtk_window_set_resizable(gtk_window, 0); - c.gtk_widget_add_css_class(@ptrCast(@alignCast(gtk_window)), "window"); - c.gtk_widget_add_css_class(@ptrCast(@alignCast(gtk_window)), "clipboard-confirmation-window"); - _ = c.g_signal_connect_data( - window, - "destroy", - c.G_CALLBACK(>kDestroy), - self, - null, - c.G_CONNECT_DEFAULT, - ); + var builder = switch (DialogType) { + adw.AlertDialog => switch (request) { + .osc_52_read => Builder.init("ccw-osc-52-read", 1, 5, .blp), + .osc_52_write => Builder.init("ccw-osc-52-write", 1, 5, .blp), + .paste => Builder.init("ccw-paste", 1, 5, .blp), + }, + adw.MessageDialog => switch (request) { + .osc_52_read => Builder.init("ccw-osc-52-read", 1, 2, .ui), + .osc_52_write => Builder.init("ccw-osc-52-write", 1, 2, .ui), + .paste => Builder.init("ccw-paste", 1, 2, .ui), + }, + else => unreachable, + }; + defer builder.deinit(); - // Set some state + const dialog = builder.getObject(DialogType, "clipboard_confirmation_window").?; + + const copy = try app.core_app.alloc.dupeZ(u8, data); + errdefer app.core_app.alloc.free(copy); self.* = .{ .app = app, - .window = gtk_window, - .view = undefined, - .data = try app.core_app.alloc.dupeZ(u8, data), + .dialog = dialog, + .data = copy, .core_surface = core_surface, .pending_req = request, }; - // Show the window - const view = try PrimaryView.init(self, data); - self.view = view; - c.gtk_window_set_child(@ptrCast(window), view.root); - _ = c.gtk_widget_grab_focus(view.buttons.cancel_button); + const text_view = builder.getObject(gtk.TextView, "text_view").?; - c.gtk_widget_show(window); + const buffer = gtk.TextBuffer.new(null); + errdefer buffer.unref(); + buffer.insertAtCursor(copy.ptr, @intCast(copy.len)); + text_view.setBuffer(buffer); - // Block the main window from input. - // This will auto-revert when the window is closed. - c.gtk_window_set_modal(gtk_window, 1); + switch (DialogType) { + adw.AlertDialog => { + const parent: ?*gtk.Widget = widget: { + const window = core_surface.rt_surface.container.window() orelse break :widget null; + break :widget @ptrCast(@alignCast(window.window)); + }; + + dialog.choose(parent, null, gtkChoose, self); + }, + adw.MessageDialog => { + if (adwaita.versionAtLeast(1, 3, 0)) { + dialog.choose(null, gtkChoose, self); + } else { + _ = adw.MessageDialog.signals.response.connect( + dialog, + *ClipboardConfirmation, + gtkResponse, + self, + .{}, + ); + dialog.as(gtk.Widget).show(); + } + }, + else => unreachable, + } } -fn gtkDestroy(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void { - const self: *ClipboardConfirmation = @ptrCast(@alignCast(ud orelse return)); - self.destroy(); -} - -const PrimaryView = struct { - root: *c.GtkWidget, - text: *c.GtkTextView, - buttons: ButtonsView, - - pub fn init(root: *ClipboardConfirmation, data: []const u8) !PrimaryView { - // All our widgets - const label = c.gtk_label_new(promptText(root.pending_req)); - const buf = unsafeBuffer(data); - defer c.g_object_unref(buf); - const buttons = try ButtonsView.init(root); - const text_scroll = c.gtk_scrolled_window_new(); - errdefer c.g_object_unref(text_scroll); - const text = c.gtk_text_view_new_with_buffer(buf); - errdefer c.g_object_unref(text); - c.gtk_scrolled_window_set_child(@ptrCast(text_scroll), text); - - // Create our view - const view = try View.init(&.{ - .{ .name = "label", .widget = label }, - .{ .name = "text", .widget = text_scroll }, - .{ .name = "buttons", .widget = buttons.root }, - }, &vfl); - errdefer view.deinit(); - - // We can do additional settings once the layout is setup - c.gtk_label_set_wrap(@ptrCast(label), 1); - c.gtk_text_view_set_editable(@ptrCast(text), 0); - c.gtk_text_view_set_cursor_visible(@ptrCast(text), 0); - c.gtk_text_view_set_top_margin(@ptrCast(text), 8); - c.gtk_text_view_set_bottom_margin(@ptrCast(text), 8); - c.gtk_text_view_set_left_margin(@ptrCast(text), 8); - c.gtk_text_view_set_right_margin(@ptrCast(text), 8); - c.gtk_text_view_set_monospace(@ptrCast(text), 1); - - return .{ .root = view.root, .text = @ptrCast(text), .buttons = buttons }; - } - - /// Returns the GtkTextBuffer for the data that was unsafe. - fn unsafeBuffer(data: []const u8) *c.GtkTextBuffer { - const buf = c.gtk_text_buffer_new(null); - errdefer c.g_object_unref(buf); - - c.gtk_text_buffer_insert_at_cursor(buf, data.ptr, @intCast(data.len)); - - return buf; - } - - const vfl = [_][*:0]const u8{ - "H:|-8-[label]-8-|", - "H:|[text]|", - "H:|[buttons]|", - "V:|[label(<=80)][text(>=100)]-[buttons]-|", - }; -}; - -const ButtonsView = struct { - root: *c.GtkWidget, - confirm_button: *c.GtkWidget, - cancel_button: *c.GtkWidget, - - pub fn init(root: *ClipboardConfirmation) !ButtonsView { - const cancel_text, const confirm_text = switch (root.pending_req) { - .paste => .{ "Cancel", "Paste" }, - .osc_52_read, .osc_52_write => .{ "Deny", "Allow" }, - }; - - const cancel_button = c.gtk_button_new_with_label(cancel_text); - errdefer c.g_object_unref(cancel_button); - - const confirm_button = c.gtk_button_new_with_label(confirm_text); - errdefer c.g_object_unref(confirm_button); - - c.gtk_widget_add_css_class(confirm_button, "destructive-action"); - c.gtk_widget_add_css_class(cancel_button, "suggested-action"); - - // Create our view - const view = try View.init(&.{ - .{ .name = "cancel", .widget = cancel_button }, - .{ .name = "confirm", .widget = confirm_button }, - }, &vfl); - - // Signals - _ = c.g_signal_connect_data( - cancel_button, - "clicked", - c.G_CALLBACK(>kCancelClick), - root, - null, - c.G_CONNECT_DEFAULT, - ); - _ = c.g_signal_connect_data( - confirm_button, - "clicked", - c.G_CALLBACK(>kConfirmClick), - root, - null, - c.G_CONNECT_DEFAULT, - ); - - return .{ .root = view.root, .confirm_button = confirm_button, .cancel_button = cancel_button }; - } - - fn gtkCancelClick(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void { - const self: *ClipboardConfirmation = @ptrCast(@alignCast(ud)); - c.gtk_window_destroy(@ptrCast(self.window)); - } - - fn gtkConfirmClick(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void { - // Requeue the paste with force. - const self: *ClipboardConfirmation = @ptrCast(@alignCast(ud)); +fn gtkChoose(dialog_: ?*gobject.Object, result: *gio.AsyncResult, ud: ?*anyopaque) callconv(.C) void { + const dialog = gobject.ext.cast(DialogType, dialog_.?).?; + const self: *ClipboardConfirmation = @ptrCast(@alignCast(ud.?)); + const response = dialog.chooseFinish(result); + if (std.mem.orderZ(u8, response, "ok") == .eq) { self.core_surface.completeClipboardRequest( self.pending_req, self.data, true, ) catch |err| { - std.log.err("Failed to requeue clipboard request: {}", .{err}); + log.err("Failed to requeue clipboard request: {}", .{err}); }; - - c.gtk_window_destroy(@ptrCast(self.window)); } - - const vfl = [_][*:0]const u8{ - "H:[cancel]-8-[confirm]-8-|", - }; -}; - -/// The title of the window, based on the reason the prompt is being shown. -fn titleText(req: apprt.ClipboardRequest) [:0]const u8 { - return switch (req) { - .paste => "Warning: Potentially Unsafe Paste", - .osc_52_read, .osc_52_write => "Authorize Clipboard Access", - }; + self.destroy(); } -/// The text to display in the prompt window, based on the reason the prompt -/// is being shown. -fn promptText(req: apprt.ClipboardRequest) [:0]const u8 { - return switch (req) { - .paste => - \\Pasting this text into the terminal may be dangerous as it looks like some commands may be executed. - , - .osc_52_read => - \\An application is attempting to read from the clipboard. - \\The current clipboard contents are shown below. - , - .osc_52_write => - \\An application is attempting to write to the clipboard. - \\The content to write is shown below. - , - }; +fn gtkResponse(_: *DialogType, response: [*:0]u8, self: *ClipboardConfirmation) callconv(.C) void { + if (std.mem.orderZ(u8, response, "ok") == .eq) { + self.core_surface.completeClipboardRequest( + self.pending_req, + self.data, + true, + ) catch |err| { + log.err("Failed to requeue clipboard request: {}", .{err}); + }; + } + self.destroy(); } diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 09d1c5a90..dc8b11d31 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -8,6 +8,7 @@ const adw = @import("adw"); const gtk = @import("gtk"); const gio = @import("gio"); const gobject = @import("gobject"); + const Allocator = std.mem.Allocator; const build_config = @import("../../build_config.zig"); const build_options = @import("build_options"); @@ -1049,7 +1050,7 @@ pub fn promptTitle(self: *Surface) !void { if (!adwaita.versionAtLeast(1, 5, 0)) return; const window = self.container.window() orelse return; - var builder = Builder.init("prompt-title-dialog", .blp); + var builder = Builder.init("prompt-title-dialog", 1, 5, .blp); defer builder.deinit(); const entry = builder.getObject(gtk.Entry, "title_entry").?; diff --git a/src/apprt/gtk/blueprint_compiler.zig b/src/apprt/gtk/blueprint_compiler.zig index 15dd574e5..7a0442e92 100644 --- a/src/apprt/gtk/blueprint_compiler.zig +++ b/src/apprt/gtk/blueprint_compiler.zig @@ -15,14 +15,10 @@ pub fn main() !void { const major = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMajorVersion, 10); const minor = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMinorVersion, 10); - const micro = try std.fmt.parseUnsigned(u8, it.next() orelse return error.NoMicroVersion, 10); const output = it.next() orelse return error.NoOutput; const input = it.next() orelse return error.NoInput; - if (c.ADW_MAJOR_VERSION < major or - (c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION < minor) or - (c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION == minor and c.ADW_MICRO_VERSION < micro)) - { + if (c.ADW_MAJOR_VERSION < major or (c.ADW_MAJOR_VERSION == major and c.ADW_MINOR_VERSION < minor)) { // If the Adwaita version is too old, generate an "empty" file. const file = try std.fs.createFileAbsolute(output, .{ .truncate = true, diff --git a/src/apprt/gtk/gresource.zig b/src/apprt/gtk/gresource.zig index 4bd08ed0a..44a1e00bf 100644 --- a/src/apprt/gtk/gresource.zig +++ b/src/apprt/gtk/gresource.zig @@ -53,19 +53,31 @@ const icons = [_]struct { }, }; -pub const ui_files = [_][]const u8{}; +pub const VersionedBuilderXML = struct { + major: u16, + minor: u16, + name: []const u8, +}; + +pub const ui_files = [_]VersionedBuilderXML{ + .{ .major = 1, .minor = 2, .name = "ccw-osc-52-read" }, + .{ .major = 1, .minor = 2, .name = "ccw-osc-52-write" }, + .{ .major = 1, .minor = 2, .name = "ccw-paste" }, +}; pub const VersionedBlueprint = struct { major: u16, minor: u16, - micro: u16, name: []const u8, }; pub const blueprint_files = [_]VersionedBlueprint{ - .{ .major = 1, .minor = 5, .micro = 0, .name = "prompt-title-dialog" }, - .{ .major = 1, .minor = 0, .micro = 0, .name = "menu-surface-context_menu" }, - .{ .major = 1, .minor = 0, .micro = 0, .name = "menu-window-titlebar_menu" }, + .{ .major = 1, .minor = 5, .name = "prompt-title-dialog" }, + .{ .major = 1, .minor = 0, .name = "menu-surface-context_menu" }, + .{ .major = 1, .minor = 0, .name = "menu-window-titlebar_menu" }, + .{ .major = 1, .minor = 5, .name = "ccw-osc-52-read" }, + .{ .major = 1, .minor = 5, .name = "ccw-osc-52-write" }, + .{ .major = 1, .minor = 5, .name = "ccw-paste" }, }; pub fn main() !void { @@ -119,15 +131,20 @@ pub fn main() !void { ); for (ui_files) |ui_file| { try writer.print( - " src/apprt/gtk/ui/{0s}.ui\n", - .{ui_file}, + " src/apprt/gtk/ui/{0d}.{1d}/{2s}.ui\n", + .{ ui_file.major, ui_file.minor, ui_file.name }, ); } for (extra_ui_files.items) |ui_file| { - try writer.print( - " {s}\n", - .{ std.fs.path.basename(ui_file), ui_file }, - ); + const stem = std.fs.path.stem(ui_file); + for (blueprint_files) |file| { + if (!std.mem.eql(u8, file.name, stem)) continue; + try writer.print( + " {s}\n", + .{ file.major, file.minor, file.name, ui_file }, + ); + break; + } else return error.BlueprintNotFound; } try writer.writeAll( \\ @@ -149,11 +166,19 @@ pub const dependencies = deps: { index += 1; } for (ui_files) |ui_file| { - deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{s}.ui", .{ui_file}); + deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{d}.{d}/{s}.ui", .{ + ui_file.major, + ui_file.minor, + ui_file.name, + }); index += 1; } for (blueprint_files) |blueprint_file| { - deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{s}.blp", .{blueprint_file.name}); + deps[index] = std.fmt.comptimePrint("src/apprt/gtk/ui/{d}.{d}/{s}.blp", .{ + blueprint_file.major, + blueprint_file.minor, + blueprint_file.name, + }); index += 1; } break :deps deps; diff --git a/src/apprt/gtk/menu.zig b/src/apprt/gtk/menu.zig index ef70df1b7..4700321bc 100644 --- a/src/apprt/gtk/menu.zig +++ b/src/apprt/gtk/menu.zig @@ -41,7 +41,7 @@ pub fn Menu( else => unreachable, }; - var builder = Builder.init("menu-" ++ object_type ++ "-" ++ menu_name, .blp); + var builder = Builder.init("menu-" ++ object_type ++ "-" ++ menu_name, 1, 0, .blp); defer builder.deinit(); const menu_model = builder.getObject(gio.MenuModel, "menu").?; diff --git a/src/apprt/gtk/ui/menu-surface-context_menu.blp b/src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp similarity index 100% rename from src/apprt/gtk/ui/menu-surface-context_menu.blp rename to src/apprt/gtk/ui/1.0/menu-surface-context_menu.blp diff --git a/src/apprt/gtk/ui/menu-window-titlebar_menu.blp b/src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp similarity index 100% rename from src/apprt/gtk/ui/menu-window-titlebar_menu.blp rename to src/apprt/gtk/ui/1.0/menu-window-titlebar_menu.blp diff --git a/src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp b/src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp new file mode 100644 index 000000000..eafa92e1b --- /dev/null +++ b/src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp @@ -0,0 +1,31 @@ +using Gtk 4.0; +using Adw 1; +translation-domain "com.mitchellh.ghostty"; + +Adw.MessageDialog clipboard_confirmation_window { + heading: _("Authorize Clipboard Access"); + body: _("An application is attempting to read from the clipboard. The current clipboard contents are shown below."); + + responses [ + cancel: _("Deny") suggested, + ok: _("Allow") destructive + ] + + default-response: "cancel"; + close-response: "cancel"; + + extra-child: ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + }; +} diff --git a/src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui b/src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui new file mode 100644 index 000000000..9c89835a0 --- /dev/null +++ b/src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui @@ -0,0 +1,36 @@ + + + + + + Authorize Clipboard Access + An application is attempting to read from the clipboard. The current clipboard contents are shown below. + + Deny + Allow + + cancel + cancel + + + 500 + 250 + + + false + false + true + 8 + 8 + 8 + 8 + + + + + + \ No newline at end of file diff --git a/src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp b/src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp new file mode 100644 index 000000000..ecd58929b --- /dev/null +++ b/src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp @@ -0,0 +1,31 @@ +using Gtk 4.0; +using Adw 1; +translation-domain "com.mitchellh.ghostty"; + +Adw.MessageDialog clipboard_confirmation_window { + heading: _("Authorize Clipboard Access"); + body: _("An application is attempting to write to the clipboard. The current clipboard contents are shown below."); + + responses [ + cancel: _("Deny") suggested, + ok: _("Allow") destructive + ] + + default-response: "cancel"; + close-response: "cancel"; + + extra-child: ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + }; +} diff --git a/src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui b/src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui new file mode 100644 index 000000000..a8904de0d --- /dev/null +++ b/src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui @@ -0,0 +1,36 @@ + + + + + + Authorize Clipboard Access + An application is attempting to write to the clipboard. The current clipboard contents are shown below. + + Deny + Allow + + cancel + cancel + + + 500 + 250 + + + false + false + true + 8 + 8 + 8 + 8 + + + + + + \ No newline at end of file diff --git a/src/apprt/gtk/ui/1.2/ccw-paste.blp b/src/apprt/gtk/ui/1.2/ccw-paste.blp new file mode 100644 index 000000000..916860368 --- /dev/null +++ b/src/apprt/gtk/ui/1.2/ccw-paste.blp @@ -0,0 +1,31 @@ +using Gtk 4.0; +using Adw 1; +translation-domain "com.mitchellh.ghostty"; + +Adw.MessageDialog clipboard_confirmation_window { + heading: _("Warning: Potentially Unsafe Paste"); + body: _("Pasting this text into the terminal may be dangerous as it looks like some commands may be executed."); + + responses [ + cancel: _("Cancel") suggested, + ok: _("Paste") destructive + ] + + default-response: "cancel"; + close-response: "cancel"; + + extra-child: ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + }; +} diff --git a/src/apprt/gtk/ui/1.2/ccw-paste.ui b/src/apprt/gtk/ui/1.2/ccw-paste.ui new file mode 100644 index 000000000..33e75b3e8 --- /dev/null +++ b/src/apprt/gtk/ui/1.2/ccw-paste.ui @@ -0,0 +1,36 @@ + + + + + + Warning: Potentially Unsafe Paste + Pasting this text into the terminal may be dangerous as it looks like some commands may be executed. + + Cancel + Paste + + cancel + cancel + + + 500 + 250 + + + false + false + true + 8 + 8 + 8 + 8 + + + + + + \ No newline at end of file diff --git a/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp b/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp new file mode 100644 index 000000000..3319bc597 --- /dev/null +++ b/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp @@ -0,0 +1,31 @@ +using Gtk 4.0; +using Adw 1; +translation-domain "com.mitchellh.ghostty"; + +Adw.AlertDialog clipboard_confirmation_window { + heading: _("Authorize Clipboard Access"); + body: _("An application is attempting to read from the clipboard. The current clipboard contents are shown below."); + + responses [ + cancel: _("Deny") suggested, + ok: _("Allow") destructive + ] + + default-response: "cancel"; + close-response: "cancel"; + + extra-child: ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + }; +} diff --git a/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp b/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp new file mode 100644 index 000000000..8372f0ccb --- /dev/null +++ b/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp @@ -0,0 +1,31 @@ +using Gtk 4.0; +using Adw 1; +translation-domain "com.mitchellh.ghostty"; + +Adw.AlertDialog clipboard_confirmation_window { + heading: _("Authorize Clipboard Access"); + body: _("An application is attempting to write to the clipboard. The current clipboard contents are shown below."); + + responses [ + cancel: _("Deny") suggested, + ok: _("Allow") destructive + ] + + default-response: "cancel"; + close-response: "cancel"; + + extra-child: ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + }; +} diff --git a/src/apprt/gtk/ui/1.5/ccw-paste.blp b/src/apprt/gtk/ui/1.5/ccw-paste.blp new file mode 100644 index 000000000..57aaabfd1 --- /dev/null +++ b/src/apprt/gtk/ui/1.5/ccw-paste.blp @@ -0,0 +1,31 @@ +using Gtk 4.0; +using Adw 1; +translation-domain "com.mitchellh.ghostty"; + +Adw.AlertDialog clipboard_confirmation_window { + heading: _("Warning: Potentially Unsafe Paste"); + body: _("Pasting this text into the terminal may be dangerous as it looks like some commands may be executed."); + + responses [ + cancel: _("Cancel") suggested, + ok: _("Paste") destructive + ] + + default-response: "cancel"; + close-response: "cancel"; + + extra-child: ScrolledWindow { + width-request: 500; + height-request: 250; + + TextView text_view { + cursor-visible: false; + editable: false; + monospace: true; + top-margin: 8; + left-margin: 8; + bottom-margin: 8; + right-margin: 8; + } + }; +} diff --git a/src/apprt/gtk/ui/prompt-title-dialog.blp b/src/apprt/gtk/ui/1.5/prompt-title-dialog.blp similarity index 100% rename from src/apprt/gtk/ui/prompt-title-dialog.blp rename to src/apprt/gtk/ui/1.5/prompt-title-dialog.blp diff --git a/src/apprt/gtk/ui/README.md b/src/apprt/gtk/ui/README.md new file mode 100644 index 000000000..08f3f367c --- /dev/null +++ b/src/apprt/gtk/ui/README.md @@ -0,0 +1,21 @@ +# GTK UI files + +This directory is for storing GTK resource definitions. With one exception, the +files should be be in the Blueprint markup language. + +Resource files should be stored in directories that represent the minimum +Adwaita version needed to use that resource. Resource files should also be +formatted using `blueprint-compiler format` as well to ensure consistency. + +The one exception to files being in Blueprint markup language is when Adwaita +features are used that the `blueprint-compiler` on a supported platform does not +compile. For example, Debian 12 includes Adwaita 1.2 and `blueprint-compiler` +0.6.0. Adwaita 1.2 includes support for `MessageDialog` but `blueprint-compiler` +0.6.0 does not. In cases like that the Blueprint markup should be compiled on a +platform that provides a new enough `blueprint-compiler` and the resulting `.ui` +file should be committed to the Ghostty source code. Care should be taken that +the `.blp` file and the `.ui` file remain in sync. + +In all other cases only the `.blp` should be committed to the Ghostty source +code. The build process will use `blueprint-compiler` to generate the `.ui` +files necessary at runtime. diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index 65b2b47da..38d4787d6 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -514,10 +514,23 @@ pub fn add( blueprint_compiler.addArgs(&.{ b.fmt("{d}", .{blueprint_file.major}), b.fmt("{d}", .{blueprint_file.minor}), - b.fmt("{d}", .{blueprint_file.micro}), }); - const ui_file = blueprint_compiler.addOutputFileArg(b.fmt("{s}.ui", .{blueprint_file.name})); - blueprint_compiler.addFileArg(b.path(b.fmt("src/apprt/gtk/ui/{s}.blp", .{blueprint_file.name}))); + const ui_file = blueprint_compiler.addOutputFileArg(b.fmt( + "{d}.{d}/{s}.ui", + .{ + blueprint_file.major, + blueprint_file.minor, + blueprint_file.name, + }, + )); + blueprint_compiler.addFileArg(b.path(b.fmt( + "src/apprt/gtk/ui/{d}.{d}/{s}.blp", + .{ + blueprint_file.major, + blueprint_file.minor, + blueprint_file.name, + }, + ))); generate.addFileArg(ui_file); }