mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
gtk: switch clipboard confirmation to zig-gobject and blueprints (#5968)
This commit is contained in:
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
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" },
|
||||
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));
|
||||
};
|
||||
|
||||
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,
|
||||
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,
|
||||
.{},
|
||||
);
|
||||
_ = 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 };
|
||||
dialog.as(gtk.Widget).show();
|
||||
}
|
||||
|
||||
fn gtkCancelClick(_: *c.GtkWidget, ud: ?*anyopaque) callconv(.C) void {
|
||||
const self: *ClipboardConfirmation = @ptrCast(@alignCast(ud));
|
||||
c.gtk_window_destroy(@ptrCast(self.window));
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -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").?;
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{0s}.ui\">src/apprt/gtk/ui/{0s}.ui</file>\n",
|
||||
.{ui_file},
|
||||
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{0d}.{1d}/{2s}.ui\">src/apprt/gtk/ui/{0d}.{1d}/{2s}.ui</file>\n",
|
||||
.{ ui_file.major, ui_file.minor, ui_file.name },
|
||||
);
|
||||
}
|
||||
for (extra_ui_files.items) |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(
|
||||
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{s}\">{s}</file>\n",
|
||||
.{ std.fs.path.basename(ui_file), ui_file },
|
||||
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{d}.{d}/{s}.ui\">{s}</file>\n",
|
||||
.{ file.major, file.minor, file.name, ui_file },
|
||||
);
|
||||
break;
|
||||
} else return error.BlueprintNotFound;
|
||||
}
|
||||
try writer.writeAll(
|
||||
\\ </gresource>
|
||||
@ -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;
|
||||
|
@ -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").?;
|
||||
|
31
src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp
Normal file
31
src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp
Normal file
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
36
src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui
Normal file
36
src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
DO NOT EDIT!
|
||||
This file was @generated by blueprint-compiler. Instead, edit the
|
||||
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||
-->
|
||||
<interface domain="com.mitchellh.ghostty">
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="AdwMessageDialog" id="clipboard_confirmation_window">
|
||||
<property name="heading" translatable="yes">Authorize Clipboard Access</property>
|
||||
<property name="body" translatable="yes">An application is attempting to read from the clipboard. The current clipboard contents are shown below.</property>
|
||||
<responses>
|
||||
<response id="cancel" translatable="yes" appearance="suggested">Deny</response>
|
||||
<response id="ok" translatable="yes" appearance="destructive">Allow</response>
|
||||
</responses>
|
||||
<property name="default-response">cancel</property>
|
||||
<property name="close-response">cancel</property>
|
||||
<property name="extra-child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="width-request">500</property>
|
||||
<property name="height-request">250</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="cursor-visible">false</property>
|
||||
<property name="editable">false</property>
|
||||
<property name="monospace">true</property>
|
||||
<property name="top-margin">8</property>
|
||||
<property name="left-margin">8</property>
|
||||
<property name="bottom-margin">8</property>
|
||||
<property name="right-margin">8</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
31
src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp
Normal file
31
src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp
Normal file
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
36
src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui
Normal file
36
src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
DO NOT EDIT!
|
||||
This file was @generated by blueprint-compiler. Instead, edit the
|
||||
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||
-->
|
||||
<interface domain="com.mitchellh.ghostty">
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="AdwMessageDialog" id="clipboard_confirmation_window">
|
||||
<property name="heading" translatable="yes">Authorize Clipboard Access</property>
|
||||
<property name="body" translatable="yes">An application is attempting to write to the clipboard. The current clipboard contents are shown below.</property>
|
||||
<responses>
|
||||
<response id="cancel" translatable="yes" appearance="suggested">Deny</response>
|
||||
<response id="ok" translatable="yes" appearance="destructive">Allow</response>
|
||||
</responses>
|
||||
<property name="default-response">cancel</property>
|
||||
<property name="close-response">cancel</property>
|
||||
<property name="extra-child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="width-request">500</property>
|
||||
<property name="height-request">250</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="cursor-visible">false</property>
|
||||
<property name="editable">false</property>
|
||||
<property name="monospace">true</property>
|
||||
<property name="top-margin">8</property>
|
||||
<property name="left-margin">8</property>
|
||||
<property name="bottom-margin">8</property>
|
||||
<property name="right-margin">8</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
31
src/apprt/gtk/ui/1.2/ccw-paste.blp
Normal file
31
src/apprt/gtk/ui/1.2/ccw-paste.blp
Normal file
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
36
src/apprt/gtk/ui/1.2/ccw-paste.ui
Normal file
36
src/apprt/gtk/ui/1.2/ccw-paste.ui
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
DO NOT EDIT!
|
||||
This file was @generated by blueprint-compiler. Instead, edit the
|
||||
corresponding .blp file and regenerate this file with blueprint-compiler.
|
||||
-->
|
||||
<interface domain="com.mitchellh.ghostty">
|
||||
<requires lib="gtk" version="4.0"/>
|
||||
<object class="AdwMessageDialog" id="clipboard_confirmation_window">
|
||||
<property name="heading" translatable="yes">Warning: Potentially Unsafe Paste</property>
|
||||
<property name="body" translatable="yes">Pasting this text into the terminal may be dangerous as it looks like some commands may be executed.</property>
|
||||
<responses>
|
||||
<response id="cancel" translatable="yes" appearance="suggested">Cancel</response>
|
||||
<response id="ok" translatable="yes" appearance="destructive">Paste</response>
|
||||
</responses>
|
||||
<property name="default-response">cancel</property>
|
||||
<property name="close-response">cancel</property>
|
||||
<property name="extra-child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="width-request">500</property>
|
||||
<property name="height-request">250</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="text_view">
|
||||
<property name="cursor-visible">false</property>
|
||||
<property name="editable">false</property>
|
||||
<property name="monospace">true</property>
|
||||
<property name="top-margin">8</property>
|
||||
<property name="left-margin">8</property>
|
||||
<property name="bottom-margin">8</property>
|
||||
<property name="right-margin">8</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
31
src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp
Normal file
31
src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp
Normal file
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
31
src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp
Normal file
31
src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp
Normal file
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
31
src/apprt/gtk/ui/1.5/ccw-paste.blp
Normal file
31
src/apprt/gtk/ui/1.5/ccw-paste.blp
Normal file
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
21
src/apprt/gtk/ui/README.md
Normal file
21
src/apprt/gtk/ui/README.md
Normal file
@ -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.
|
@ -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);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user