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 @@ - - - - - - Authorize Clipboard Access - An application is attempting to read from 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-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