mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
262 lines
7.9 KiB
Zig
262 lines
7.9 KiB
Zig
//! This file contains a binary helper that builds our gresource XML
|
|
//! file that we can then use with `glib-compile-resources`.
|
|
//!
|
|
//! This binary is expected to be run from the Ghostty source root.
|
|
//! Litmus test: `src/apprt/gtk` should exist relative to the pwd.
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
/// Prefix/appid for the gresource file.
|
|
pub const prefix = "/com/mitchellh/ghostty";
|
|
pub const app_id = "com.mitchellh.ghostty";
|
|
|
|
/// The path to the Blueprint files. The folder structure is expected to be
|
|
/// `{version}/{name}.blp` where `version` is the major and minor
|
|
/// minimum adwaita version.
|
|
pub const ui_path = "src/apprt/gtk-ng/ui";
|
|
|
|
/// The path to the CSS files.
|
|
pub const css_path = "src/apprt/gtk-ng/css";
|
|
|
|
/// The possible icon sizes we'll embed into the gresource file.
|
|
/// If any size doesn't exist then it will be an error. We could
|
|
/// infer this completely from available files but we wouldn't be
|
|
/// able to error when they don't exist that way.
|
|
pub const icon_sizes: []const comptime_int = &.{ 16, 32, 128, 256, 512, 1024 };
|
|
|
|
/// The blueprint files that we will embed into the gresource file.
|
|
/// We can't look these up at runtime [easily] because we require the
|
|
/// compiled UI files as input. We can refactor this lator to maybe do
|
|
/// all of this automatically and ensure we have the right dependencies
|
|
/// setup in the build system.
|
|
///
|
|
/// These will be asserted to exist at runtime.
|
|
pub const blueprints: []const Blueprint = &.{
|
|
.{ .major = 1, .minor = 0, .name = "clipboard-confirmation-dialog" },
|
|
.{ .major = 1, .minor = 4, .name = "clipboard-confirmation-dialog" },
|
|
.{ .major = 1, .minor = 2, .name = "close-confirmation-dialog" },
|
|
.{ .major = 1, .minor = 2, .name = "config-errors-dialog" },
|
|
.{ .major = 1, .minor = 2, .name = "resize-overlay" },
|
|
.{ .major = 1, .minor = 2, .name = "surface" },
|
|
.{ .major = 1, .minor = 5, .name = "window" },
|
|
};
|
|
|
|
/// CSS files in css_path
|
|
pub const css = [_][]const u8{
|
|
"style.css",
|
|
// "style-dark.css",
|
|
// "style-hc.css",
|
|
// "style-hc-dark.css",
|
|
};
|
|
|
|
pub const Blueprint = struct {
|
|
major: u16,
|
|
minor: u16,
|
|
name: []const u8,
|
|
};
|
|
|
|
/// The list of filepaths that we depend on. Used for the build
|
|
/// system to have proper caching.
|
|
pub const file_inputs = deps: {
|
|
const total = (icon_sizes.len * 2) + blueprints.len + css.len;
|
|
var deps: [total][]const u8 = undefined;
|
|
var index: usize = 0;
|
|
for (icon_sizes) |size| {
|
|
deps[index] = std.fmt.comptimePrint("images/icons/icon_{d}.png", .{size});
|
|
deps[index + 1] = std.fmt.comptimePrint("images/icons/icon_{d}@2x.png", .{size});
|
|
index += 2;
|
|
}
|
|
for (blueprints) |bp| {
|
|
deps[index] = std.fmt.comptimePrint("{s}/{d}.{d}/{s}.blp", .{
|
|
ui_path,
|
|
bp.major,
|
|
bp.minor,
|
|
bp.name,
|
|
});
|
|
index += 1;
|
|
}
|
|
for (css) |name| {
|
|
deps[index] = std.fmt.comptimePrint("{s}/{s}", .{ css_path, name });
|
|
index += 1;
|
|
}
|
|
break :deps deps;
|
|
};
|
|
|
|
/// Returns the matching blueprint resource path for the given blueprint
|
|
/// definition. This will fail at compile time if the blueprint is not
|
|
/// found.
|
|
///
|
|
/// Must be called at comptime.
|
|
pub fn blueprint(comptime bp: Blueprint) [:0]const u8 {
|
|
// The comptime block around this whole thing forces an error if
|
|
// the caller attempts to call this function at runtime.
|
|
comptime {
|
|
for (blueprints) |candidate| {
|
|
if (candidate.major == bp.major and
|
|
candidate.minor == bp.minor and
|
|
std.mem.eql(u8, candidate.name, bp.name))
|
|
{
|
|
return std.fmt.comptimePrint("{s}/ui/{d}.{d}/{s}.ui", .{
|
|
prefix,
|
|
candidate.major,
|
|
candidate.minor,
|
|
candidate.name,
|
|
});
|
|
}
|
|
}
|
|
|
|
@compileError("invalid blueprint");
|
|
}
|
|
}
|
|
|
|
pub fn main() !void {
|
|
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
|
defer _ = debug_allocator.deinit();
|
|
const alloc = debug_allocator.allocator();
|
|
|
|
// Collect the UI files that are passed in as arguments.
|
|
var ui_files: std.ArrayListUnmanaged([]const u8) = .empty;
|
|
defer {
|
|
for (ui_files.items) |item| alloc.free(item);
|
|
ui_files.deinit(alloc);
|
|
}
|
|
var it = try std.process.argsWithAllocator(alloc);
|
|
defer it.deinit();
|
|
while (it.next()) |arg| {
|
|
if (!std.mem.endsWith(u8, arg, ".ui")) continue;
|
|
try ui_files.append(
|
|
alloc,
|
|
try alloc.dupe(u8, arg),
|
|
);
|
|
}
|
|
|
|
const writer = std.io.getStdOut().writer();
|
|
try writer.writeAll(
|
|
\\<?xml version="1.0" encoding="UTF-8"?>
|
|
\\<gresources>
|
|
\\
|
|
);
|
|
|
|
try genRoot(writer);
|
|
try genIcons(writer);
|
|
try genUi(alloc, writer, &ui_files);
|
|
|
|
try writer.writeAll(
|
|
\\</gresources>
|
|
\\
|
|
);
|
|
}
|
|
|
|
/// Generate the icon resources. This works by looking up all the icons
|
|
/// specified by `icon_sizes` in `images/icons/`. They are asserted to exist
|
|
/// by trying to access the file.
|
|
fn genIcons(writer: anytype) !void {
|
|
try writer.print(
|
|
\\ <gresource prefix="{s}/icons">
|
|
\\
|
|
, .{prefix});
|
|
|
|
const cwd = std.fs.cwd();
|
|
inline for (icon_sizes) |size| {
|
|
// 1x
|
|
{
|
|
const alias = std.fmt.comptimePrint("{d}x{d}", .{ size, size });
|
|
const source = std.fmt.comptimePrint("images/icons/icon_{d}.png", .{size});
|
|
try cwd.access(source, .{});
|
|
try writer.print(
|
|
\\ <file alias="{s}/apps/{s}.png">{s}</file>
|
|
\\
|
|
,
|
|
.{ alias, app_id, source },
|
|
);
|
|
}
|
|
|
|
// 2x
|
|
{
|
|
const alias = std.fmt.comptimePrint("{d}x{d}@2", .{ size, size });
|
|
const source = std.fmt.comptimePrint("images/icons/icon_{d}@2x.png", .{size});
|
|
try cwd.access(source, .{});
|
|
try writer.print(
|
|
\\ <file alias="{s}/apps/{s}.png">{s}</file>
|
|
\\
|
|
,
|
|
.{ alias, app_id, source },
|
|
);
|
|
}
|
|
}
|
|
|
|
try writer.writeAll(
|
|
\\ </gresource>
|
|
\\
|
|
);
|
|
}
|
|
|
|
/// Generate the resources at the root prefix.
|
|
fn genRoot(writer: anytype) !void {
|
|
try writer.print(
|
|
\\ <gresource prefix="{s}">
|
|
\\
|
|
, .{prefix});
|
|
|
|
const cwd = std.fs.cwd();
|
|
inline for (css) |name| {
|
|
const source = std.fmt.comptimePrint(
|
|
"{s}/{s}",
|
|
.{ css_path, name },
|
|
);
|
|
try cwd.access(source, .{});
|
|
try writer.print(
|
|
\\ <file compressed="true" alias="{s}">{s}</file>
|
|
\\
|
|
,
|
|
.{ name, source },
|
|
);
|
|
}
|
|
|
|
try writer.writeAll(
|
|
\\ </gresource>
|
|
\\
|
|
);
|
|
}
|
|
|
|
/// Generate all the UI resources. This works by looking up all the
|
|
/// blueprint files in `${ui_path}/{major}.{minor}/{name}.blp` and
|
|
/// assuming these will be
|
|
fn genUi(
|
|
alloc: Allocator,
|
|
writer: anytype,
|
|
files: *const std.ArrayListUnmanaged([]const u8),
|
|
) !void {
|
|
try writer.print(
|
|
\\ <gresource prefix="{s}/ui">
|
|
\\
|
|
, .{prefix});
|
|
|
|
for (files.items) |ui_file| {
|
|
for (blueprints) |bp| {
|
|
const expected = try std.fmt.allocPrint(
|
|
alloc,
|
|
"/{d}.{d}/{s}.ui",
|
|
.{ bp.major, bp.minor, bp.name },
|
|
);
|
|
defer alloc.free(expected);
|
|
if (!std.mem.endsWith(u8, ui_file, expected)) continue;
|
|
try writer.print(
|
|
" <file compressed=\"true\" preprocess=\"xml-stripblanks\" alias=\"{d}.{d}/{s}.ui\">{s}</file>\n",
|
|
.{ bp.major, bp.minor, bp.name, ui_file },
|
|
);
|
|
break;
|
|
} else {
|
|
// The for loop never broke which means it didn't find
|
|
// a matching blueprint for this input.
|
|
return error.BlueprintNotFound;
|
|
}
|
|
}
|
|
|
|
try writer.writeAll(
|
|
\\ </gresource>
|
|
\\
|
|
);
|
|
}
|