mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
gtk: use versioned directories to store blueprint files
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,
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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").?;
|
||||
|
@ -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,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(
|
||||
" <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>
|
||||
@ -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;
|
||||
|
@ -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").?;
|
||||
|
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