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);
}