diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml
index 1789576a8..52f069f4e 100644
--- a/.github/workflows/release-tag.yml
+++ b/.github/workflows/release-tag.yml
@@ -89,10 +89,13 @@ jobs:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
+ - name: compile blueprints
+ run: nix run .#compile-blueprints
+
- name: Create Tarball
run: |
- git archive --format=tgz --prefix="ghostty-${GHOSTTY_VERSION}/" -o "ghostty-${GHOSTTY_VERSION}.tar.gz" HEAD
- git archive --format=tgz --prefix=ghostty-source/ -o ghostty-source.tar.gz HEAD
+ git archive --format=tgz $(find src -name \*.ui -print0 | xargs --null --replace=UI echo --add-virtual-file=ghostty-${GHOSTTY_VERSION}/UI:UI) --prefix="ghostty-${GHOSTTY_VERSION}/" -o "ghostty-${GHOSTTY_VERSION}.tar.gz" HEAD
+ git archive --format=tgz $(find src -name \*.ui -print0 | xargs --null --replace=UI echo --add-virtual-file=ghostty-source/UI:UI --prefix=ghostty-source/) -o ghostty-source.tar.gz HEAD
- name: Sign Tarball
run: |
diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml
index a031f3ad1..96a1e849b 100644
--- a/.github/workflows/release-tip.yml
+++ b/.github/workflows/release-tip.yml
@@ -110,8 +110,10 @@ jobs:
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
+ - name: compile blueprints
+ run: nix run .#compile-blueprints
- name: Create Tarball
- run: git archive --format=tgz --prefix=ghostty-source/ -o ghostty-source.tar.gz HEAD
+ run: git archive --format=tgz $(find src -name \*.ui -print0 | xargs --null --replace=UI echo --add-virtual-file=ghostty-source/UI:UI --prefix=ghostty-source/) -o ghostty-source.tar.gz HEAD
- name: Sign Tarball
run: |
echo -n "${{ secrets.MINISIGN_KEY }}" > minisign.key
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b2f449784..406e6e324 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,6 +27,7 @@ jobs:
- prettier
- alejandra
- typos
+ - blueprint-compiler
- translations
- test-pkg-linux
- test-debian-12
@@ -609,6 +610,35 @@ jobs:
- name: prettier check
run: nix develop -c prettier --check .
+ blueprint-compiler:
+ if: github.repository == 'ghostty-org/ghostty'
+ runs-on: namespace-profile-ghostty-sm
+ timeout-minutes: 60
+ env:
+ ZIG_LOCAL_CACHE_DIR: /zig/local-cache
+ ZIG_GLOBAL_CACHE_DIR: /zig/global-cache
+ steps:
+ - uses: actions/checkout@v4 # Check out repo so we can lint it
+ - name: Setup Cache
+ uses: namespacelabs/nscloud-cache-action@v1.2.0
+ with:
+ path: |
+ /nix
+ /zig
+ - uses: cachix/install-nix-action@v30
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+ - uses: cachix/cachix-action@v15
+ with:
+ name: ghostty
+ authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
+ skipPush: true
+ useDaemon: false # sometimes fails on short jobs
+ - name: compile blueprints
+ run: nix run .#compile-blueprints
+ - name: check unchanged
+ run: git diff --exit-code
+
alejandra:
if: github.repository == 'ghostty-org/ghostty'
runs-on: namespace-profile-ghostty-sm
@@ -742,6 +772,26 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
+ - name: Setup Cache
+ uses: namespacelabs/nscloud-cache-action@v1.2.0
+ with:
+ path: |
+ /nix
+ /zig
+
+ # Install Nix and use that to run our tests so our environment matches exactly.
+ - uses: cachix/install-nix-action@v30
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+ - uses: cachix/cachix-action@v15
+ with:
+ name: ghostty
+ authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
+
+ # use Nix to compile blueprints since Debian 12 doesn't have a new enough version
+ - name: compile blueprints
+ run: nix run .#compile-blueprints
+
- name: Install and configure Namespace CLI
uses: namespacelabs/nscloud-setup@v0
diff --git a/.gitignore b/.gitignore
index f39b0c780..988311157 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ zig-out/
example/*.wasm
test/ghostty
test/cases/**/*.actual.png
+src/apprt/gtk/ui/**/*.ui
glad.zip
/Box_test.ppm
diff --git a/flake.nix b/flake.nix
index fc8daf201..35ba5aa0a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -52,6 +52,8 @@
zig = zig.packages.${system}."0.14.0";
wraptest = pkgs-stable.callPackage ./nix/wraptest.nix {};
zon2nix = zon2nix;
+ # revert once blueprint-compiler 0.16.0 is in nixpkgs-stable
+ blueprint-compiler = pkgs-unstable.blueprint-compiler;
};
packages.${system} = let
@@ -99,6 +101,10 @@
x11-gnome = runVM ./nix/vm/x11-gnome.nix;
x11-plasma6 = runVM ./nix/vm/x11-plasma6.nix;
x11-xfce = runVM ./nix/vm/x11-xfce.nix;
+ compile-blueprints = import ./nix/build-support/compile-blueprints.nix {
+ # change once Zig 0.14 and blueprints-compiler 0.16 are in a stable release of nixpkgs
+ pkgs = pkgs-unstable;
+ };
};
}
# Our supported systems are the same supported systems as the Zig binaries.
diff --git a/ghostty-source.tar.gz b/ghostty-source.tar.gz
new file mode 100644
index 000000000..f5e008577
Binary files /dev/null and b/ghostty-source.tar.gz differ
diff --git a/nix/build-support/compile-blueprints.nix b/nix/build-support/compile-blueprints.nix
new file mode 100644
index 000000000..3436bbe0b
--- /dev/null
+++ b/nix/build-support/compile-blueprints.nix
@@ -0,0 +1,23 @@
+{pkgs}: let
+ # this needs to be kept in sync with deps from devShell.nix and package.nix
+ gi_typelib_path = pkgs.lib.makeSearchPath "lib/girepository-1.0" (map (pkg: pkgs.lib.getOutput "lib" pkg) [
+ pkgs.cairo
+ pkgs.gdk-pixbuf
+ pkgs.glib
+ pkgs.gobject-introspection
+ pkgs.graphene
+ pkgs.gtk4
+ pkgs.gtk4-layer-shell
+ pkgs.harfbuzz
+ pkgs.libadwaita
+ pkgs.pango
+ ]);
+ program = pkgs.writeShellScript "compile-blueprints" ''
+ set -e
+ ${pkgs.findutils}/bin/find . -name \*.blp -print0 | ${pkgs.findutils}/bin/xargs --null --replace=BLP -- ${pkgs.lib.getExe pkgs.blueprint-compiler} format --fix BLP
+ ${pkgs.findutils}/bin/find . -name \*.blp -print0 | ${pkgs.findutils}/bin/xargs --null --replace=BLP -- sh -c "export B=BLP; ${pkgs.lib.getExe pkgs.blueprint-compiler} compile --typelib-path=${gi_typelib_path} --output \''${B%.*}.ui \$B"
+ '';
+in {
+ type = "app";
+ program = "${program}";
+}
diff --git a/nix/devShell.nix b/nix/devShell.nix
index 4acadad97..11e54355e 100644
--- a/nix/devShell.nix
+++ b/nix/devShell.nix
@@ -63,7 +63,23 @@
wayland-protocols,
zon2nix,
system,
+ cairo,
+ gdk-pixbuf,
+ graphene,
+ pango,
}: let
+ gi_typelib_path = [
+ cairo
+ gdk-pixbuf
+ glib
+ gobject-introspection
+ graphene
+ gtk4
+ gtk4-layer-shell
+ harfbuzz
+ libadwaita
+ pango
+ ];
# See package.nix. Keep in sync.
rpathLibs =
[
@@ -187,6 +203,7 @@ in
# This should be set onto the rpath of the ghostty binary if you want
# it to be "portable" across the system.
LD_LIBRARY_PATH = lib.makeLibraryPath rpathLibs;
+ GI_TYPELIB_PATH = lib.makeSearchPath "lib/girepository-1.0" (map (pkg: lib.getOutput "lib" pkg) gi_typelib_path);
shellHook =
(lib.optionalString stdenv.hostPlatform.isLinux ''
diff --git a/src/apprt/gtk/Builder.zig b/src/apprt/gtk/Builder.zig
index 028629200..dbd765ba3 100644
--- a/src/apprt/gtk/Builder.zig
+++ b/src/apprt/gtk/Builder.zig
@@ -18,88 +18,37 @@ pub fn init(
/// 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 => {
- // 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.
- 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 => {
- // 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.
- 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");
- },
- }
+ // 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.
+ 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");
};
return .{
diff --git a/src/apprt/gtk/ClipboardConfirmationWindow.zig b/src/apprt/gtk/ClipboardConfirmationWindow.zig
index 9260d1c7b..2ef3370fb 100644
--- a/src/apprt/gtk/ClipboardConfirmationWindow.zig
+++ b/src/apprt/gtk/ClipboardConfirmationWindow.zig
@@ -72,14 +72,14 @@ fn init(
) !void {
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),
+ .osc_52_read => Builder.init("ccw-osc-52-read", 1, 5),
+ .osc_52_write => Builder.init("ccw-osc-52-write", 1, 5),
+ .paste => Builder.init("ccw-paste", 1, 5),
},
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),
+ .osc_52_read => Builder.init("ccw-osc-52-read", 1, 2),
+ .osc_52_write => Builder.init("ccw-osc-52-write", 1, 2),
+ .paste => Builder.init("ccw-paste", 1, 2),
},
else => unreachable,
};
diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig
index e96b25c16..9e16560bf 100644
--- a/src/apprt/gtk/Surface.zig
+++ b/src/apprt/gtk/Surface.zig
@@ -1062,7 +1062,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", 1, 5, .blp);
+ var builder = Builder.init("prompt-title-dialog", 1, 5);
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 7a0442e92..6ad8e8513 100644
--- a/src/apprt/gtk/blueprint_compiler.zig
+++ b/src/apprt/gtk/blueprint_compiler.zig
@@ -4,9 +4,12 @@ pub const c = @cImport({
@cInclude("adwaita.h");
});
+const required_version = "0.16.0";
+
pub fn main() !void {
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
- const alloc = gpa.allocator();
+ var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
+ defer _ = debug_allocator.deinit();
+ const alloc = debug_allocator.allocator();
var it = try std.process.argsWithAllocator(alloc);
defer it.deinit();
@@ -19,47 +22,125 @@ pub fn main() !void {
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)) {
- // If the Adwaita version is too old, generate an "empty" file.
- const file = try std.fs.createFileAbsolute(output, .{
- .truncate = true,
- });
- try file.writeAll(
- \\
- \\
- );
- defer file.close();
-
- return;
+ std.log.err(
+ \\`libadwaita` is too old.
+ \\
+ \\Ghostty requires a version {d}.{d} or newer of `libadwaita` to
+ \\compile this blueprint. Please install it, ensure that it is
+ \\available on your PATH, and then retry building Ghostty.
+ , .{ major, minor });
+ std.posix.exit(1);
}
- var compiler = std.process.Child.init(
- &.{
- "blueprint-compiler",
- "compile",
- "--output",
- output,
- input,
- },
- alloc,
- );
+ {
+ var stdout: std.ArrayListUnmanaged(u8) = .empty;
+ defer stdout.deinit(alloc);
+ var stderr: std.ArrayListUnmanaged(u8) = .empty;
+ defer stderr.deinit(alloc);
- const term = compiler.spawnAndWait() catch |err| switch (err) {
- error.FileNotFound => {
+ var blueprint_compiler = std.process.Child.init(
+ &.{
+ "blueprint-compiler",
+ "--version",
+ },
+ alloc,
+ );
+ blueprint_compiler.stdout_behavior = .Pipe;
+ blueprint_compiler.stderr_behavior = .Pipe;
+ try blueprint_compiler.spawn();
+ try blueprint_compiler.collectOutput(
+ alloc,
+ &stdout,
+ &stderr,
+ std.math.maxInt(u16),
+ );
+ const term = blueprint_compiler.wait() catch |err| switch (err) {
+ error.FileNotFound => {
+ std.log.err(
+ \\`blueprint-compiler` not found.
+ \\
+ \\Ghostty requires `blueprint-compiler` version {s} as
+ \\a build-time dependency starting from version 1.2. Please
+ \\install it, ensure that it is available on your PATH, and
+ \\then retry building Ghostty.
+ \\
+ , .{required_version});
+ std.posix.exit(1);
+ },
+ else => return err,
+ };
+ switch (term) {
+ .Exited => |rc| {
+ if (rc != 0) std.process.exit(1);
+ },
+ else => std.process.exit(1),
+ }
+ const version = std.mem.trim(u8, stdout.items, &std.ascii.whitespace);
+ if (!std.mem.eql(u8, version, "0.16.0")) {
std.log.err(
- \\`blueprint-compiler` not found.
+ \\`blueprint-compiler` is the wrong version.
\\
- \\Ghostty requires `blueprint-compiler` as a build-time dependency starting from version 1.2.
- \\Please install it, ensure that it is available on your PATH, and then retry building Ghostty.
- , .{});
+ \\Ghostty requires `blueprint-compiler` version {s} as
+ \\a build-time dependency starting from version 1.2. Please
+ \\install it, ensure that it is available on your PATH, and
+ \\then retry building Ghostty.
+ \\
+ , .{required_version});
std.posix.exit(1);
- },
- else => return err,
- };
+ }
+ }
- switch (term) {
- .Exited => |rc| {
- if (rc != 0) std.process.exit(1);
- },
- else => std.process.exit(1),
+ {
+ var stdout: std.ArrayListUnmanaged(u8) = .empty;
+ defer stdout.deinit(alloc);
+ var stderr: std.ArrayListUnmanaged(u8) = .empty;
+ defer stderr.deinit(alloc);
+
+ var blueprint_compiler = std.process.Child.init(
+ &.{
+ "blueprint-compiler",
+ "compile",
+ "--output",
+ output,
+ input,
+ },
+ alloc,
+ );
+ blueprint_compiler.stdout_behavior = .Pipe;
+ blueprint_compiler.stderr_behavior = .Pipe;
+ try blueprint_compiler.spawn();
+ try blueprint_compiler.collectOutput(
+ alloc,
+ &stdout,
+ &stderr,
+ std.math.maxInt(u16),
+ );
+ const term = blueprint_compiler.wait() catch |err| switch (err) {
+ error.FileNotFound => {
+ std.log.err(
+ \\`blueprint-compiler` not found.
+ \\
+ \\Ghostty requires `blueprint-compiler` version {s} as
+ \\a build-time dependency starting from version 1.2. Please
+ \\install it, ensure that it is available on your PATH, and
+ \\then retry building Ghostty.
+ \\
+ , .{required_version});
+ std.posix.exit(1);
+ },
+ else => return err,
+ };
+ switch (term) {
+ .Exited => |rc| {
+ if (rc != 0) {
+ std.debug.print("{s}", .{stderr.items});
+ std.process.exit(1);
+ }
+ },
+ else => {
+ std.debug.print("{s}", .{stderr.items});
+ std.process.exit(1);
+ },
+ }
}
}
diff --git a/src/apprt/gtk/builder_check.zig b/src/apprt/gtk/builder_check.zig
deleted file mode 100644
index 015c6310d..000000000
--- a/src/apprt/gtk/builder_check.zig
+++ /dev/null
@@ -1,32 +0,0 @@
-const std = @import("std");
-const build_options = @import("build_options");
-
-const gtk = @import("gtk");
-const adw = @import("adw");
-
-pub fn main() !void {
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
- const alloc = gpa.allocator();
-
- const filename = filename: {
- var it = try std.process.argsWithAllocator(alloc);
- defer it.deinit();
-
- _ = it.next() orelse return error.NoFilename;
- break :filename try alloc.dupeZ(u8, it.next() orelse return error.NoFilename);
- };
- defer alloc.free(filename);
-
- const data = try std.fs.cwd().readFileAllocOptions(alloc, filename, std.math.maxInt(u16), null, 1, 0);
- defer alloc.free(data);
-
- if (gtk.initCheck() == 0) {
- std.debug.print("{s}: skipping builder check because we can't connect to display!\n", .{filename});
- return;
- }
-
- adw.init();
-
- const builder = gtk.Builder.newFromString(data.ptr, @intCast(data.len));
- defer builder.unref();
-}
diff --git a/src/apprt/gtk/gresource.zig b/src/apprt/gtk/gresource.zig
index 44a1e00bf..ebbc41309 100644
--- a/src/apprt/gtk/gresource.zig
+++ b/src/apprt/gtk/gresource.zig
@@ -53,18 +53,6 @@ const icons = [_]struct {
},
};
-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,
@@ -75,27 +63,33 @@ pub const blueprint_files = [_]VersionedBlueprint{
.{ .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 = 2, .name = "ccw-osc-52-read" },
+ .{ .major = 1, .minor = 2, .name = "ccw-osc-52-write" },
+ .{ .major = 1, .minor = 2, .name = "ccw-paste" },
.{ .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 {
- var gpa = std.heap.GeneralPurposeAllocator(.{}){};
- const alloc = gpa.allocator();
+ var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
+ defer _ = debug_allocator.deinit();
+ const alloc = debug_allocator.allocator();
- var extra_ui_files = std.ArrayList([]const u8).init(alloc);
+ var extra_ui_files: std.ArrayListUnmanaged([]const u8) = .empty;
defer {
for (extra_ui_files.items) |item| alloc.free(item);
- extra_ui_files.deinit();
+ extra_ui_files.deinit(alloc);
}
var it = try std.process.argsWithAllocator(alloc);
defer it.deinit();
+ _ = it.next();
+
while (it.next()) |argument| {
if (std.mem.eql(u8, std.fs.path.extension(argument), ".ui")) {
- try extra_ui_files.append(try alloc.dupe(u8, argument));
+ try extra_ui_files.append(alloc, try alloc.dupe(u8, argument));
}
}
@@ -129,16 +123,11 @@ pub fn main() !void {
\\
\\
);
- for (ui_files) |ui_file| {
- try writer.print(
- " 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| {
- const stem = std.fs.path.stem(ui_file);
for (blueprint_files) |file| {
- if (!std.mem.eql(u8, file.name, stem)) continue;
+ const expected = try std.fmt.allocPrint(alloc, "/{d}.{d}/{s}.ui", .{ file.major, file.minor, file.name });
+ defer alloc.free(expected);
+ if (!std.mem.endsWith(u8, ui_file, expected)) continue;
try writer.print(
" {s}\n",
.{ file.major, file.minor, file.name, ui_file },
@@ -154,7 +143,7 @@ pub fn main() !void {
}
pub const dependencies = deps: {
- const total = css_files.len + icons.len + ui_files.len + blueprint_files.len;
+ const total = css_files.len + icons.len;
var deps: [total][]const u8 = undefined;
var index: usize = 0;
for (css_files) |css_file| {
@@ -165,21 +154,5 @@ pub const dependencies = deps: {
deps[index] = std.fmt.comptimePrint("images/icons/icon_{s}.png", .{icon.source});
index += 1;
}
- for (ui_files) |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/{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 4118097c0..09adebfc3 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, 1, 0, .blp);
+ var builder = Builder.init("menu-" ++ object_type ++ "-" ++ menu_name, 1, 0);
defer builder.deinit();
const menu_model = builder.getObject(gio.MenuModel, "menu").?;
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
index c76b69884..b250073d2 100644
--- a/src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp
+++ b/src/apprt/gtk/ui/1.2/ccw-osc-52-read.blp
@@ -8,7 +8,7 @@ Adw.MessageDialog clipboard_confirmation_window {
responses [
cancel: _("Deny") suggested,
- ok: _("Allow") destructive
+ ok: _("Allow") destructive,
]
default-response: "cancel";
@@ -16,7 +16,7 @@ Adw.MessageDialog clipboard_confirmation_window {
extra-child: Overlay {
styles [
- "osd"
+ "osd",
]
ScrolledWindow text_view_scroll {
@@ -33,7 +33,7 @@ Adw.MessageDialog clipboard_confirmation_window {
right-margin: 8;
styles [
- "clipboard-content-view"
+ "clipboard-content-view",
]
}
}
@@ -60,7 +60,7 @@ Adw.MessageDialog clipboard_confirmation_window {
margin-top: 12;
styles [
- "opaque"
+ "opaque",
]
Image {
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
deleted file mode 100644
index 82512e3a2..000000000
--- a/src/apprt/gtk/ui/1.2/ccw-osc-52-read.ui
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
\ 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
index 529a2fc52..d880df5f2 100644
--- a/src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp
+++ b/src/apprt/gtk/ui/1.2/ccw-osc-52-write.blp
@@ -8,7 +8,7 @@ Adw.MessageDialog clipboard_confirmation_window {
responses [
cancel: _("Deny") suggested,
- ok: _("Allow") destructive
+ ok: _("Allow") destructive,
]
default-response: "cancel";
@@ -16,7 +16,7 @@ Adw.MessageDialog clipboard_confirmation_window {
extra-child: Overlay {
styles [
- "osd"
+ "osd",
]
ScrolledWindow text_view_scroll {
@@ -33,7 +33,7 @@ Adw.MessageDialog clipboard_confirmation_window {
right-margin: 8;
styles [
- "clipboard-content-view"
+ "clipboard-content-view",
]
}
}
@@ -60,7 +60,7 @@ Adw.MessageDialog clipboard_confirmation_window {
margin-top: 12;
styles [
- "opaque"
+ "opaque",
]
Image {
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
deleted file mode 100644
index 195fb1de1..000000000
--- a/src/apprt/gtk/ui/1.2/ccw-osc-52-write.ui
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
- 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
-
-
-
-
-
-
-
- false
- 2
- 1
- 12
- 12
-
-
- view-reveal-symbolic
-
-
-
-
-
-
- false
- 2
- 1
- 12
- 12
-
-
-
- view-conceal-symbolic
-
-
-
-
-
-
-
-
\ 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
index 3ca12e966..f26921803 100644
--- a/src/apprt/gtk/ui/1.2/ccw-paste.blp
+++ b/src/apprt/gtk/ui/1.2/ccw-paste.blp
@@ -8,7 +8,7 @@ Adw.MessageDialog clipboard_confirmation_window {
responses [
cancel: _("Cancel") suggested,
- ok: _("Paste") destructive
+ ok: _("Paste") destructive,
]
default-response: "cancel";
@@ -16,7 +16,7 @@ Adw.MessageDialog clipboard_confirmation_window {
extra-child: Overlay {
styles [
- "osd"
+ "osd",
]
ScrolledWindow text_view_scroll {
@@ -33,7 +33,7 @@ Adw.MessageDialog clipboard_confirmation_window {
right-margin: 8;
styles [
- "clipboard-content-view"
+ "clipboard-content-view",
]
}
}
@@ -60,7 +60,7 @@ Adw.MessageDialog clipboard_confirmation_window {
margin-top: 12;
styles [
- "opaque"
+ "opaque",
]
Image {
diff --git a/src/apprt/gtk/ui/1.2/ccw-paste.ui b/src/apprt/gtk/ui/1.2/ccw-paste.ui
deleted file mode 100644
index 342c767e6..000000000
--- a/src/apprt/gtk/ui/1.2/ccw-paste.ui
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
- 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
-
-
-
-
-
-
-
- false
- 2
- 1
- 12
- 12
-
-
- view-reveal-symbolic
-
-
-
-
-
-
- false
- 2
- 1
- 12
- 12
-
-
-
- view-conceal-symbolic
-
-
-
-
-
-
-
-
\ 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
index 60fca5b00..640556535 100644
--- a/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp
+++ b/src/apprt/gtk/ui/1.5/ccw-osc-52-read.blp
@@ -8,7 +8,7 @@ Adw.AlertDialog clipboard_confirmation_window {
responses [
cancel: _("Deny") suggested,
- ok: _("Allow") destructive
+ ok: _("Allow") destructive,
]
default-response: "cancel";
@@ -16,7 +16,7 @@ Adw.AlertDialog clipboard_confirmation_window {
extra-child: Overlay {
styles [
- "osd"
+ "osd",
]
ScrolledWindow text_view_scroll {
@@ -33,7 +33,7 @@ Adw.AlertDialog clipboard_confirmation_window {
right-margin: 8;
styles [
- "clipboard-content-view"
+ "clipboard-content-view",
]
}
}
@@ -60,7 +60,7 @@ Adw.AlertDialog clipboard_confirmation_window {
margin-top: 12;
styles [
- "opaque"
+ "opaque",
]
Image {
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
index 7ef17def0..2e28359ff 100644
--- a/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp
+++ b/src/apprt/gtk/ui/1.5/ccw-osc-52-write.blp
@@ -8,7 +8,7 @@ Adw.AlertDialog clipboard_confirmation_window {
responses [
cancel: _("Deny") suggested,
- ok: _("Allow") destructive
+ ok: _("Allow") destructive,
]
default-response: "cancel";
@@ -16,7 +16,7 @@ Adw.AlertDialog clipboard_confirmation_window {
extra-child: Overlay {
styles [
- "osd"
+ "osd",
]
ScrolledWindow text_view_scroll {
@@ -33,7 +33,7 @@ Adw.AlertDialog clipboard_confirmation_window {
right-margin: 8;
styles [
- "clipboard-content-view"
+ "clipboard-content-view",
]
}
}
@@ -60,7 +60,7 @@ Adw.AlertDialog clipboard_confirmation_window {
margin-top: 12;
styles [
- "opaque"
+ "opaque",
]
Image {
diff --git a/src/apprt/gtk/ui/1.5/ccw-paste.blp b/src/apprt/gtk/ui/1.5/ccw-paste.blp
index 57539fb44..a5f909526 100644
--- a/src/apprt/gtk/ui/1.5/ccw-paste.blp
+++ b/src/apprt/gtk/ui/1.5/ccw-paste.blp
@@ -8,7 +8,7 @@ Adw.AlertDialog clipboard_confirmation_window {
responses [
cancel: _("Cancel") suggested,
- ok: _("Paste") destructive
+ ok: _("Paste") destructive,
]
default-response: "cancel";
@@ -16,7 +16,7 @@ Adw.AlertDialog clipboard_confirmation_window {
extra-child: Overlay {
styles [
- "osd"
+ "osd",
]
ScrolledWindow text_view_scroll {
@@ -33,7 +33,7 @@ Adw.AlertDialog clipboard_confirmation_window {
right-margin: 8;
styles [
- "clipboard-content-view"
+ "clipboard-content-view",
]
}
}
@@ -60,7 +60,7 @@ Adw.AlertDialog clipboard_confirmation_window {
margin-top: 12;
styles [
- "opaque"
+ "opaque",
]
Image {
diff --git a/src/apprt/gtk/ui/1.5/prompt-title-dialog.blp b/src/apprt/gtk/ui/1.5/prompt-title-dialog.blp
index ffe38c980..d23594ba4 100644
--- a/src/apprt/gtk/ui/1.5/prompt-title-dialog.blp
+++ b/src/apprt/gtk/ui/1.5/prompt-title-dialog.blp
@@ -7,7 +7,7 @@ Adw.AlertDialog prompt_title_dialog {
responses [
cancel: _("Cancel") suggested,
- ok: _("OK") destructive
+ ok: _("OK") destructive,
]
focus-widget: title_entry;
diff --git a/src/apprt/gtk/ui/README.md b/src/apprt/gtk/ui/README.md
index 08f3f367c..971474b8d 100644
--- a/src/apprt/gtk/ui/README.md
+++ b/src/apprt/gtk/ui/README.md
@@ -1,21 +1,20 @@
# GTK UI files
-This directory is for storing GTK resource definitions. With one exception, the
-files should be be in the Blueprint markup language.
+This directory is for storing GTK resource definitions. All resource definitions
+_must_ start as GTK blueprint `.blp` files. GTK resource definitions are then
+generated by `blueprint-compiler` at compile time. A CI job ensures that `.blp`
+files are valid and properly formatted.
-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.
+Blueprint files should be stored in directories that represent the minimum
+Adwaita version needed to use that resource. Blueprint files should also be
+formatted using `blueprint-compiler format` as well to ensure consistency
+(formatting will be checked in CI as well).
-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.
+Blueprints can be formatted and compiled into resource definitions by running
+this command from the root of the source tree:
-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.
+```
+nix run .#compile-blueprints
+```
+
+Currently `blueprint-compiler` 0.16.0 is required to compile Blueprint files.
diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig
index 88d64ca3c..23f367f3c 100644
--- a/src/build/SharedDeps.zig
+++ b/src/build/SharedDeps.zig
@@ -668,71 +668,46 @@ fn addGTK(
});
const generate = b.addRunArtifact(generate_gresource_xml);
-
- const gtk_blueprint_compiler = b.addExecutable(.{
+ const blueprint_compiler = b.addExecutable(.{
.name = "gtk_blueprint_compiler",
.root_source_file = b.path("src/apprt/gtk/blueprint_compiler.zig"),
.target = b.graph.host,
});
- gtk_blueprint_compiler.linkSystemLibrary2("gtk4", dynamic_link_opts);
- gtk_blueprint_compiler.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
- gtk_blueprint_compiler.linkLibC();
+ blueprint_compiler.linkSystemLibrary2("gtk4", dynamic_link_opts);
+ blueprint_compiler.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
+ blueprint_compiler.linkLibC();
for (gresource.blueprint_files) |blueprint_file| {
- const blueprint_compiler = b.addRunArtifact(gtk_blueprint_compiler);
- blueprint_compiler.addArgs(&.{
- b.fmt("{d}", .{blueprint_file.major}),
- b.fmt("{d}", .{blueprint_file.minor}),
- });
- 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);
+ if (b.systemIntegrationOption("blueprint-compiler", .{ .default = true })) {
+ const blueprint_compile = b.addRunArtifact(blueprint_compiler);
+ blueprint_compile.addArgs(&.{
+ b.fmt("{d}", .{blueprint_file.major}),
+ b.fmt("{d}", .{blueprint_file.minor}),
+ });
+ const blueprint_output = blueprint_compile.addOutputFileArg(b.fmt(
+ "{[major]d}.{[minor]d}/{[name]s}.ui",
+ blueprint_file,
+ ));
+ blueprint_compile.addFileArg(b.path(b.fmt(
+ "src/apprt/gtk/ui/{[major]d}.{[minor]d}/{[name]s}.blp",
+ blueprint_file,
+ )));
+ generate.addFileArg(blueprint_output);
+ } else {
+ generate.addFileInput(b.path(b.fmt(
+ "src/apprt/gtk/ui/{[major]d}.{[minor]d}/{[name]s}.ui",
+ blueprint_file,
+ )));
+ }
+ }
+ for (gresource.dependencies) |pathname| {
+ if (std.mem.eql(u8, std.fs.path.extension(pathname), ".blp")) continue;
+ generate.addFileInput(b.path(pathname));
}
break :gresource_xml generate.captureStdOut();
};
- {
- const gtk_builder_check = b.addExecutable(.{
- .name = "gtk_builder_check",
- .root_source_file = b.path("src/apprt/gtk/builder_check.zig"),
- .target = b.graph.host,
- });
- gtk_builder_check.root_module.addOptions("build_options", self.options);
- if (gobject_) |gobject| {
- gtk_builder_check.root_module.addImport(
- "gtk",
- gobject.module("gtk4"),
- );
- gtk_builder_check.root_module.addImport(
- "adw",
- gobject.module("adw1"),
- );
- }
-
- for (gresource.dependencies) |pathname| {
- const extension = std.fs.path.extension(pathname);
- if (!std.mem.eql(u8, extension, ".ui")) continue;
- const check = b.addRunArtifact(gtk_builder_check);
- check.addFileArg(b.path(pathname));
- step.step.dependOn(&check.step);
- }
- }
-
const generate_resources_c = b.addSystemCommand(&.{
"glib-compile-resources",
"--c-name",
diff --git a/src/build/docker/debian/Dockerfile b/src/build/docker/debian/Dockerfile
index 9d9cbbf96..dd3e6c775 100644
--- a/src/build/docker/debian/Dockerfile
+++ b/src/build/docker/debian/Dockerfile
@@ -5,7 +5,6 @@ FROM docker.io/library/debian:${DISTRO_VERSION}
RUN DEBIAN_FRONTEND="noninteractive" apt-get -qq update && \
apt-get -qq -y --no-install-recommends install \
# Build Tools
- blueprint-compiler \
build-essential \
libbz2-dev \
libonig-dev \
@@ -52,5 +51,5 @@ RUN ZIG_GLOBAL_CACHE_DIR=/zig/global-cache ./nix/build-support/fetch-zig-cache.s
COPY ./src /src/src
# Debian 12 doesn't have gtk4-layer-shell, so we have to manually compile it ourselves
-RUN zig build -Doptimize=Debug -Dcpu=baseline -Dapp-runtime=gtk -fno-sys=gtk4-layer-shell --system /zig/global-cache/p
+RUN zig build -Doptimize=Debug -Dcpu=baseline -Dapp-runtime=gtk -fno-sys=gtk4-layer-shell -fno-sys=blueprint-compiler --system /zig/global-cache/p