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 d1494d0ae..77a846f79 100644 --- a/src/apprt/gtk/ClipboardConfirmationWindow.zig +++ b/src/apprt/gtk/ClipboardConfirmationWindow.zig @@ -65,14 +65,14 @@ fn init( ) !void { var builder = switch (DialogType) { adw.AlertDialog => switch (request) { - .osc_52_read => Builder.init("ccw-osc-52-write-15", .blp), - .osc_52_write => Builder.init("ccw-osc-52-write-15", .blp), - .paste => Builder.init("ccw-paste-15", .blp), + .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-write-12", .ui), - .osc_52_write => Builder.init("ccw-osc-52-write-12", .ui), - .paste => Builder.init("ccw-paste-12", .ui), + .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, }; diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 06a1576d3..dc8b11d31 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -1050,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 32995c924..44a1e00bf 100644 --- a/src/apprt/gtk/gresource.zig +++ b/src/apprt/gtk/gresource.zig @@ -53,26 +53,31 @@ const icons = [_]struct { }, }; -pub const ui_files = [_][]const u8{ - "ccw-osc-52-read-12", - "ccw-osc-52-write-12", - "ccw-paste-12", +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, .micro = 0, .name = "ccw-osc-52-read-15" }, - .{ .major = 1, .minor = 5, .micro = 0, .name = "ccw-osc-52-write-15" }, - .{ .major = 1, .minor = 5, .micro = 0, .name = "ccw-paste-15" }, + .{ .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 { @@ -126,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( \\ @@ -156,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/ccw-osc-52-read-12.blp b/src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp similarity index 100% rename from src/apprt/gtk/ui/ccw-osc-52-read-12.blp rename to src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp diff --git a/src/apprt/gtk/ui/ccw-osc-52-read-12.ui b/src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui similarity index 100% rename from src/apprt/gtk/ui/ccw-osc-52-read-12.ui rename to src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui diff --git a/src/apprt/gtk/ui/ccw-osc-52-write-12.blp b/src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp similarity index 100% rename from src/apprt/gtk/ui/ccw-osc-52-write-12.blp rename to src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp diff --git a/src/apprt/gtk/ui/ccw-osc-52-write-12.ui b/src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui similarity index 100% rename from src/apprt/gtk/ui/ccw-osc-52-write-12.ui rename to src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui diff --git a/src/apprt/gtk/ui/ccw-paste-12.blp b/src/apprt/gtk/ui/1.2/ccw-paste.blp similarity index 100% rename from src/apprt/gtk/ui/ccw-paste-12.blp rename to src/apprt/gtk/ui/1.2/ccw-paste.blp diff --git a/src/apprt/gtk/ui/ccw-paste-12.ui b/src/apprt/gtk/ui/1.2/ccw-paste.ui similarity index 100% rename from src/apprt/gtk/ui/ccw-paste-12.ui rename to src/apprt/gtk/ui/1.2/ccw-paste.ui diff --git a/src/apprt/gtk/ui/ccw-osc-52-read-15.blp b/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp similarity index 100% rename from src/apprt/gtk/ui/ccw-osc-52-read-15.blp rename to src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp diff --git a/src/apprt/gtk/ui/ccw-osc-52-write-15.blp b/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp similarity index 100% rename from src/apprt/gtk/ui/ccw-osc-52-write-15.blp rename to src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp diff --git a/src/apprt/gtk/ui/ccw-paste-15.blp b/src/apprt/gtk/ui/1.5/ccw-paste.blp similarity index 100% rename from src/apprt/gtk/ui/ccw-paste-15.blp rename to src/apprt/gtk/ui/1.5/ccw-paste.blp 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); }