Update to Zig 0.16.0

This commit represents the majority of the work necessary to upgrade
Ghostty to use Zig 0.16.0.

At this point, the project builds, tests, and runs under Linux. More
work may be necessary for other platforms, and possibly to fix any
(currently unknown) upgrade regressions.

Co-authored-by: Leah Amelia Chen <hi@pluie.me>
pull/12726/head
Chris Marchesi 2026-05-07 09:11:14 -07:00
parent 063ac3ecc5
commit ba38b493d4
No known key found for this signature in database
GPG Key ID: 45C2F118B474FB1B
290 changed files with 5142 additions and 2866 deletions

2
.gitignore vendored
View File

@ -10,6 +10,7 @@
zig-cache/
.zig-cache/
zig-out/
zig-pkg/
build-cmake/
CMakeCache.txt
CMakeFiles/
@ -29,3 +30,4 @@ glad.zip
vgcore.*
/sprite_face_test*

View File

@ -25,9 +25,10 @@ pub fn build(b: *std.Build) !void {
// use that as the version source of truth. Otherwise we fall back
// to what is in the build.zig.zon.
const file_version: ?[]const u8 = if (b.build_root.handle.readFileAlloc(
b.allocator,
b.graph.io,
"VERSION",
128,
b.allocator,
.limited(128),
)) |content| std.mem.trim(
u8,
content,
@ -298,7 +299,7 @@ pub fn build(b: *std.Build) !void {
// We need to rebuild Ghostty with a baseline CPU target.
const valgrind_exe = exe: {
var valgrind_config = config;
valgrind_config.target = valgrind_config.baselineTarget();
valgrind_config.target = valgrind_config.baselineTarget(b.graph.io);
break :exe try buildpkg.GhosttyExe.init(
b,
&valgrind_config,
@ -343,7 +344,7 @@ pub fn build(b: *std.Build) !void {
.filters = test_filters,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = config.baselineTarget(),
.target = config.baselineTarget(b.graph.io),
.optimize = .Debug,
.strip = false,
.omit_frame_pointer = false,
@ -358,7 +359,7 @@ pub fn build(b: *std.Build) !void {
// Verify our internal libghostty header.
const ghostty_h = b.addTranslateC(.{
.root_source_file = b.path("include/ghostty.h"),
.target = config.baselineTarget(),
.target = config.baselineTarget(b.graph.io),
.optimize = .Debug,
});
test_exe.root_module.addImport("ghostty.h", ghostty_h.createModule());

View File

@ -3,62 +3,73 @@
.version = "1.3.2-dev",
.paths = .{""},
.fingerprint = 0x64407a2a0b4147e5,
.minimum_zig_version = "0.15.2",
.minimum_zig_version = "0.16.0",
.dependencies = .{
// Zig libs
// External translate-c
// NOTE: We might not need to keep this around forever, but we need it
// for at least the 0.16.0 release cycle since we need fixes in
// Aro/translate-c itself.
//
// Since this is a key part of the build toolchain, it's *not* an
// optional (read: not lazy) dependency.
.translate_c = .{
.url = "https://codeberg.org/vancluever/translate-c/archive/c401682d0cbc6bc1f883d84886b8b1346922268d.tar.gz",
.hash = "translate_c-1.0.0-Q_BUWjnzBgDZX5ADyqP5K0kVqd-otnnj6Sld8Kzouvsa",
},
// Zig libs
.libxev = .{
// mitchellh/libxev
.url = "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz",
.hash = "libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs",
.url = "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
.hash = "libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F",
.lazy = true,
},
.vaxis = .{
// rockorager/libvaxis
.url = "https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz",
.hash = "vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS",
.url = "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
.hash = "vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka",
.lazy = true,
},
.z2d = .{
// vancluever/z2d
.url = "https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz",
.hash = "z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ",
.url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz",
.hash = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR",
.lazy = true,
},
.zig_objc = .{
// mitchellh/zig-objc
.url = "https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz",
.hash = "zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK",
.url = "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
.hash = "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi",
.lazy = true,
},
.zig_js = .{
// mitchellh/zig-js
.url = "https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz",
.hash = "zig_js-0.0.0-rjCAV-6GAADxFug7rDmPH-uM_XcnJ5NmuAMJCAscMjhi",
.url = "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
.hash = "zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj",
.lazy = true,
},
.uucode = .{
// jacobsandlund/uucode
.url = "https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz",
.hash = "uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9",
.url = "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
.hash = "uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA",
},
.zig_wayland = .{
// codeberg ifreund/zig-wayland
.url = "https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz",
.hash = "wayland-0.5.0-dev-lQa1khrMAQDJDwYFKpdH3HizherB7sHo5dKMECfvxQHe",
.url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
.hash = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX",
.lazy = true,
},
.zf = .{
// natecraddock/zf
.url = "https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz",
.hash = "zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh",
.url = "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
.hash = "zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW",
.lazy = true,
},
.gobject = .{
// https://github.com/ghostty-org/zig-gobject based on zig_gobject
// Temporary until we generate them at build time automatically.
.url = "https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst",
.hash = "gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-",
.url = "https://github.com/ghostty-org/zig-gobject/releases/download/0.8.0-2026-04-23-26-1/ghostty-gobject-0.8.0-2026-04-23-26-1.tar.zst",
.hash = "gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp",
.lazy = true,
},

View File

@ -3,11 +3,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
@ -23,11 +23,11 @@
]
},
"locked": {
"lastModified": 1770586272,
"narHash": "sha256-Ucci8mu8QfxwzyfER2DQDbvW9t1BnTUJhBmY7ybralo=",
"lastModified": 1778144356,
"narHash": "sha256-dGM+QCstz/DyLB68+JK5GWyMx4QSqmOJEVgZmy63d/g=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "b1f916ba052341edc1f80d4b2399f1092a4873ca",
"rev": "e4419d3123b780d5f4c0bceeace450424387638c",
"type": "github"
},
"original": {
@ -38,11 +38,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1770537093,
"narHash": "sha256-XV30uo8tXuxdzuV8l3sojmlPRLd/8tpMsOp4lNzLGUo=",
"rev": "fef9403a3e4d31b0a23f0bacebbec52c248fbb51",
"lastModified": 1778124196,
"narHash": "sha256-Z5mLDoR8p0d7psIY4LnyaHHRykXngMcWXTZ9JWvtvPc=",
"rev": "68a8af93ff4297686cb68880845e61e5e2e41d92",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre942631.fef9403a3e4d/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre993588.68a8af93ff42/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
@ -88,11 +88,11 @@
]
},
"locked": {
"lastModified": 1776789209,
"narHash": "sha256-G6B7Q4TXn7MZ1mB+f9rymjsYF5PLWoSvmbxijb/99bw=",
"lastModified": 1778158259,
"narHash": "sha256-PwPyM4vM1uzxYqT5txYzauxx9Tfgji/iTPO9KwbTcUc=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "14fe971844e841297ddd2ce9783d6892b467af39",
"rev": "cd6faec62cb29fda88b2195b91edb2dfe6dce9c2",
"type": "github"
},
"original": {
@ -101,40 +101,18 @@
"type": "github"
}
},
"zig_2": {
"inputs": {
"nixpkgs": [
"zon2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1777234348,
"narHash": "sha256-fKw44a4qbUuI5eTG8k0gPbqMV5TOrjYF35PBzsYgd2U=",
"ref": "refs/heads/main",
"rev": "2c781c0609ecda600ab98f98cca417bbd981bd53",
"revCount": 1677,
"type": "git",
"url": "https://codeberg.org/jcollie/zig-overlay.git"
},
"original": {
"type": "git",
"url": "https://codeberg.org/jcollie/zig-overlay.git"
}
},
"zon2nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"zig": "zig_2"
]
},
"locked": {
"lastModified": 1777314365,
"narHash": "sha256-eLxQaD0wc96Neqkln8wHS0rNq/chPODifFkhwrwilEU=",
"lastModified": 1778078000,
"narHash": "sha256-0djKIL0E8Y1W495bzIkmbCub571ubghZx3Fg7xQPNrk=",
"owner": "jcollie",
"repo": "zon2nix",
"rev": "a5a1d412ad1ab6305511997bbc92b3a9dd6cb784",
"rev": "a9f46c6fe9177135a3001e26ec76715bb998c6cf",
"type": "github"
},
"original": {

View File

@ -74,11 +74,24 @@
};
in {
devShells = forAllPlatforms (pkgs: {
default = pkgs.callPackage ./nix/devShell.nix {
zig =
if pkgs.stdenv.hostPlatform.isDarwin
then zig.packages.${pkgs.stdenv.hostPlatform.system}.brew."0.15.2"
else zig.packages.${pkgs.stdenv.hostPlatform.system}."0.15.2";
default = pkgs.callPackage ./nix/devShell.nix
(let
libfyaml = if pkgs.stdenv.hostPlatform.isDarwin then
pkgs.libfyaml.overrideAttrs (prev: {
# Manually fix libfyaml.pc until NixOS/nixpkgs#515614 is available
postInstall = (prev.postInstall or "") + ''
substituteInPlace "$dev/lib/pkgconfig/libfyaml.pc" \
--replace-fail " none required" ""
'';
})
else pkgs.libfyaml;
appstream = pkgs.appstream.override { libfyaml = libfyaml; };
libadwaita = pkgs.libadwaita.override { appstream = appstream; };
blueprint-compiler = pkgs.blueprint-compiler.override { libadwaita = libadwaita; };
in
{
zig = zig.packages.${pkgs.stdenv.hostPlatform.system}."0.16.0";
wraptest = pkgs.callPackage ./nix/pkgs/wraptest.nix {};
zon2nix = zon2nix;
@ -90,7 +103,9 @@
wcwidth = pyfinal.callPackage ./nix/pkgs/wcwidth.nix {};
};
};
};
inherit appstream libadwaita blueprint-compiler;
});
});
packages =

View File

@ -27,7 +27,6 @@
wasmtime,
wraptest,
zig,
zig_0_15,
zip,
llvmPackages_latest,
bzip2,

View File

@ -118,16 +118,16 @@ pub fn addPaths(b: *std.Build, step: *std.Build.Step.Compile) !void {
fn findNDKPath(b: *std.Build) ?[]const u8 {
// Check if user has set the environment variable for the NDK path.
if (std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch null) |value| {
if (b.graph.environ_map.get("ANDROID_NDK_HOME")) |value| {
if (value.len == 0) return null;
var dir = std.fs.openDirAbsolute(value, .{}) catch return null;
defer dir.close();
var dir = std.Io.Dir.openDirAbsolute(b.graph.io, value, .{}) catch return null;
defer dir.close(b.graph.io);
return value;
}
// Check the common environment variables for the Android SDK path and look for the NDK inside it.
inline for (.{ "ANDROID_HOME", "ANDROID_SDK_ROOT" }) |env| {
if (std.process.getEnvVarOwned(b.allocator, env) catch null) |sdk| {
if (b.graph.environ_map.get(env)) |sdk| {
if (sdk.len > 0) {
if (findLatestNDK(b, sdk)) |ndk| return ndk;
}
@ -135,10 +135,9 @@ fn findNDKPath(b: *std.Build) ?[]const u8 {
}
// As a fallback, we assume the most common/default SDK path based on the OS.
const home = std.process.getEnvVarOwned(
b.allocator,
const home = b.graph.environ_map.get(
if (builtin.os.tag == .windows) "LOCALAPPDATA" else "HOME",
) catch return null;
) orelse return null;
const default_sdk_path = b.pathJoin(
&.{
@ -157,8 +156,8 @@ fn findNDKPath(b: *std.Build) ?[]const u8 {
fn findLatestNDK(b: *std.Build, sdk_path: []const u8) ?[]const u8 {
const ndk_dir = b.pathJoin(&.{ sdk_path, "ndk" });
var dir = std.fs.openDirAbsolute(ndk_dir, .{ .iterate = true }) catch return null;
defer dir.close();
var dir = std.Io.Dir.openDirAbsolute(b.graph.io, ndk_dir, .{ .iterate = true }) catch return null;
defer dir.close(b.graph.io);
var latest_: ?struct {
name: []const u8,
@ -166,7 +165,7 @@ fn findLatestNDK(b: *std.Build, sdk_path: []const u8) ?[]const u8 {
} = null;
var iterator = dir.iterate();
while (iterator.next() catch null) |file| {
while (iterator.next(b.graph.io) catch null) |file| {
if (file.kind != .directory) continue;
const version = std.SemanticVersion.parse(file.name) catch continue;
if (latest_) |latest| {

View File

@ -53,14 +53,18 @@ pub fn addPaths(
// Detect our SDK using the "findNative" Zig stdlib function.
// This is really important because it forces using `xcrun` to
// find the SDK path.
const libc = std.zig.LibCInstallation.findNative(.{
.allocator = b.allocator,
.target = &step.rootModuleTarget(),
.verbose = false,
}) catch break :darwin;
const libc = std.zig.LibCInstallation.findNative(
b.allocator,
b.graph.io,
.{
.environ_map = &b.graph.environ_map,
.target = &step.rootModuleTarget(),
.verbose = false,
},
) catch break :darwin;
// Render the file compatible with the `--libc` Zig flag.
var stream: std.io.Writer.Allocating = .init(b.allocator);
var stream: std.Io.Writer.Allocating = .init(b.allocator);
defer stream.deinit();
try libc.render(&stream.writer);

View File

@ -9,11 +9,11 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libcpp = true,
}),
.linkage = .static,
});
lib.linkLibCpp();
lib.addIncludePath(b.path("vendor"));
lib.root_module.addIncludePath(b.path("vendor"));
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, lib);
@ -23,20 +23,20 @@ pub fn build(b: *std.Build) !void {
defer flags.deinit(b.allocator);
if (b.lazyDependency("breakpad", .{})) |upstream| {
lib.addIncludePath(upstream.path("src"));
lib.addCSourceFiles(.{
lib.root_module.addIncludePath(upstream.path("src"));
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = common,
.flags = flags.items,
});
if (target.result.os.tag.isDarwin()) {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = common_apple,
.flags = flags.items,
});
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_apple,
.flags = flags.items,
@ -44,19 +44,19 @@ pub fn build(b: *std.Build) !void {
switch (target.result.os.tag) {
.macos => {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = common_mac,
.flags = flags.items,
});
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_mac,
.flags = flags.items,
});
},
.ios => lib.addCSourceFiles(.{
.ios => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_ios,
.flags = flags.items,
@ -67,12 +67,12 @@ pub fn build(b: *std.Build) !void {
}
if (target.result.os.tag == .linux) {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = common_linux,
.flags = flags.items,
});
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = client_linux,
.flags = flags.items,

View File

@ -22,18 +22,16 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime
// headers. The MSVC SDK include directories (added via linkLibC)
// contain both C and C++ headers, so linkLibCpp is not needed.
.link_libcpp = target.result.abi != .msvc,
}),
.linkage = .static,
});
lib.linkLibC();
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime headers.
// The MSVC SDK include directories (added via linkLibC) contain
// both C and C++ headers, so linkLibCpp is not needed.
if (target.result.abi != .msvc) {
lib.linkLibCpp();
}
b.installArtifact(lib);
// Zig module
@ -87,8 +85,8 @@ pub fn build(b: *std.Build) !void {
// Add the core Dear Imgui source files
if (b.lazyDependency("imgui", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.addCSourceFiles(.{
lib.root_module.addIncludePath(upstream.path(""));
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"imgui_demo.cpp",
@ -107,20 +105,20 @@ pub fn build(b: *std.Build) !void {
);
if (freetype) {
lib.addCSourceFile(.{
lib.root_module.addCSourceFile(.{
.file = upstream.path("misc/freetype/imgui_freetype.cpp"),
.flags = flags.items,
});
if (b.systemIntegrationOption("freetype", .{})) {
lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts);
} else {
const freetype_dep = b.dependency("freetype", .{
.target = target,
.optimize = optimize,
.@"enable-libpng" = true,
});
lib.linkLibrary(freetype_dep.artifact("freetype"));
lib.root_module.linkLibrary(freetype_dep.artifact("freetype"));
if (freetype_dep.builder.lazyDependency(
"freetype",
.{},
@ -131,7 +129,7 @@ pub fn build(b: *std.Build) !void {
}
if (backend_metal) {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path("backends"),
.files = &.{"imgui_impl_metal.mm"},
.flags = flags.items,
@ -143,7 +141,7 @@ pub fn build(b: *std.Build) !void {
);
}
if (backend_osx) {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path("backends"),
.files = &.{"imgui_impl_osx.mm"},
.flags = flags.items,
@ -155,7 +153,7 @@ pub fn build(b: *std.Build) !void {
);
}
if (backend_opengl3) {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path("backends"),
.files = &.{"imgui_impl_opengl3.cpp"},
.flags = flags.items,
@ -170,8 +168,8 @@ pub fn build(b: *std.Build) !void {
// Add the C bindings
if (b.lazyDependency("bindings", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.addCSourceFiles(.{
lib.root_module.addIncludePath(upstream.path(""));
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"dcimgui.cpp",
@ -179,7 +177,7 @@ pub fn build(b: *std.Build) !void {
},
.flags = flags.items,
});
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = b.path(""),
.files = &.{"ext.cpp"},
.flags = flags.items,
@ -201,7 +199,7 @@ pub fn build(b: *std.Build) !void {
}),
});
test_exe.root_module.addOptions("build_options", options);
test_exe.linkLibrary(lib);
test_exe.root_module.linkLibrary(lib);
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);

View File

@ -41,7 +41,7 @@ pub fn build(b: *std.Build) !void {
if (b.systemIntegrationOption("fontconfig", .{})) {
module.linkSystemLibrary("fontconfig", dynamic_link_opts);
test_exe.linkSystemLibrary2("fontconfig", dynamic_link_opts);
test_exe.root_module.linkSystemLibrary("fontconfig", dynamic_link_opts);
} else {
const lib = try buildLib(b, module, .{
.target = target,
@ -54,7 +54,7 @@ pub fn build(b: *std.Build) !void {
.dynamic_link_opts = dynamic_link_opts,
});
test_exe.linkLibrary(lib);
test_exe.root_module.linkLibrary(lib);
}
}
@ -71,15 +71,18 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.linkage = .static,
});
lib.linkLibC();
const dynamic_link_opts = options.dynamic_link_opts;
if (target.result.os.tag != .windows) {
lib.linkSystemLibrary("pthread");
lib.root_module.linkSystemLibrary("pthread", dynamic_link_opts);
}
lib.addIncludePath(b.path("override/include"));
lib.root_module.addIncludePath(b.path("override/include"));
module.addIncludePath(b.path("override/include"));
var flags: std.ArrayList([]const u8) = .empty;
@ -194,19 +197,17 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
}
}
const dynamic_link_opts = options.dynamic_link_opts;
// Freetype2
_ = b.systemIntegrationOption("freetype", .{}); // So it shows up in help
if (freetype_enabled) {
if (b.systemIntegrationOption("freetype", .{})) {
lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts);
} else {
if (b.lazyDependency(
"freetype",
.{ .target = target, .optimize = optimize },
)) |freetype_dep| {
lib.linkLibrary(freetype_dep.artifact("freetype"));
lib.root_module.linkLibrary(freetype_dep.artifact("freetype"));
}
}
}
@ -227,22 +228,22 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
}
if (b.systemIntegrationOption("libxml2", .{})) {
lib.linkSystemLibrary2("libxml-2.0", dynamic_link_opts);
lib.root_module.linkSystemLibrary("libxml-2.0", dynamic_link_opts);
} else {
if (b.lazyDependency("libxml2", .{
.target = target,
.optimize = optimize,
.iconv = libxml2_iconv_enabled,
})) |libxml2_dep| {
lib.linkLibrary(libxml2_dep.artifact("xml2"));
lib.root_module.linkLibrary(libxml2_dep.artifact("xml2"));
}
}
}
if (b.lazyDependency("fontconfig", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.root_module.addIncludePath(upstream.path(""));
module.addIncludePath(upstream.path(""));
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,

View File

@ -39,7 +39,7 @@ pub fn build(b: *std.Build) !void {
if (b.systemIntegrationOption("freetype", .{})) {
module.linkSystemLibrary("freetype2", dynamic_link_opts);
if (test_exe) |exe| {
exe.linkSystemLibrary2("freetype2", dynamic_link_opts);
exe.root_module.linkSystemLibrary("freetype2", dynamic_link_opts);
}
} else {
const lib = try buildLib(b, module, .{
@ -52,7 +52,7 @@ pub fn build(b: *std.Build) !void {
});
if (test_exe) |exe| {
exe.linkLibrary(lib);
exe.root_module.linkLibrary(lib);
}
}
}
@ -68,10 +68,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.linkage = .static,
});
lib.linkLibC();
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, lib);
@ -101,10 +101,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
// Zlib
if (b.systemIntegrationOption("zlib", .{})) {
lib.linkSystemLibrary2("zlib", dynamic_link_opts);
lib.root_module.linkSystemLibrary("zlib", dynamic_link_opts);
} else {
const zlib_dep = b.dependency("zlib", .{ .target = target, .optimize = optimize });
lib.linkLibrary(zlib_dep.artifact("z"));
lib.root_module.linkLibrary(zlib_dep.artifact("z"));
}
// Libpng
@ -113,50 +113,50 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
try flags.append(b.allocator, "-DFT_CONFIG_OPTION_USE_PNG=1");
if (b.systemIntegrationOption("libpng", .{})) {
lib.linkSystemLibrary2("libpng", dynamic_link_opts);
lib.root_module.linkSystemLibrary("libpng", dynamic_link_opts);
} else {
const libpng_dep = b.dependency(
"libpng",
.{ .target = target, .optimize = optimize },
);
lib.linkLibrary(libpng_dep.artifact("png"));
lib.root_module.linkLibrary(libpng_dep.artifact("png"));
}
}
if (b.lazyDependency("freetype", .{})) |upstream| {
lib.addIncludePath(upstream.path("include"));
lib.root_module.addIncludePath(upstream.path("include"));
module.addIncludePath(upstream.path("include"));
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,
});
switch (target.result.os.tag) {
.linux => lib.addCSourceFile(.{
.linux => lib.root_module.addCSourceFile(.{
.file = upstream.path("builds/unix/ftsystem.c"),
.flags = flags.items,
}),
.windows => lib.addCSourceFile(.{
.windows => lib.root_module.addCSourceFile(.{
.file = upstream.path("builds/windows/ftsystem.c"),
.flags = flags.items,
}),
else => lib.addCSourceFile(.{
else => lib.root_module.addCSourceFile(.{
.file = upstream.path("src/base/ftsystem.c"),
.flags = flags.items,
}),
}
switch (target.result.os.tag) {
.windows => {
lib.addCSourceFile(.{
lib.root_module.addCSourceFile(.{
.file = upstream.path("builds/windows/ftdebug.c"),
.flags = flags.items,
});
lib.addWin32ResourceFile(.{
lib.root_module.addWin32ResourceFile(.{
.file = upstream.path("src/base/ftver.rc"),
});
},
else => lib.addCSourceFile(.{
else => lib.root_module.addCSourceFile(.{
.file = upstream.path("src/base/ftdebug.c"),
.flags = flags.items,
}),

View File

@ -26,7 +26,7 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
}),
});
test_exe.linkLibrary(lib);
test_exe.root_module.linkLibrary(lib);
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);
@ -47,20 +47,18 @@ fn buildGlslang(
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime
// headers. The MSVC SDK include directories (added via linkLibC)
// contain both C and C++ headers, so linkLibCpp is not needed.
.link_libcpp = target.result.abi != .msvc,
}),
.linkage = .static,
});
lib.linkLibC();
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime headers.
// The MSVC SDK include directories (added via linkLibC) contain
// both C and C++ headers, so linkLibCpp is not needed.
if (target.result.abi != .msvc) {
lib.linkLibCpp();
}
if (upstream_) |upstream| lib.addIncludePath(upstream.path(""));
lib.addIncludePath(b.path("override"));
if (upstream_) |upstream| lib.root_module.addIncludePath(upstream.path(""));
lib.root_module.addIncludePath(b.path("override"));
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, lib);
@ -82,7 +80,7 @@ fn buildGlslang(
}
if (upstream_) |upstream| {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.flags = flags.items,
.files = &.{
@ -141,7 +139,7 @@ fn buildGlslang(
});
if (target.result.os.tag != .windows) {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.flags = flags.items,
.files = &.{
@ -149,7 +147,7 @@ fn buildGlslang(
},
});
} else {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.flags = flags.items,
.files = &.{

View File

@ -1,4 +1,5 @@
const std = @import("std");
const Translator = @import("translate_c").Translator;
const version = @import("build.zig.zon").version;
@ -17,11 +18,21 @@ pub fn build(b: *std.Build) !void {
.target = target,
.optimize = optimize,
});
const translate_c = b.dependency("translate_c", .{});
const headers: Translator = .init(translate_c, .{
.c_source_file = b.addWriteFiles().add("c.h",
\\#include <gtk4-layer-shell.h>
),
.target = target,
.optimize = optimize,
});
// Needs the gtk.h header
module.linkSystemLibrary("gtk4", dynamic_link_opts);
headers.linkSystemLibrary("gtk4", dynamic_link_opts);
if (b.systemIntegrationOption("gtk4-layer-shell", .{})) {
module.linkSystemLibrary("gtk4-layer-shell-0", dynamic_link_opts);
headers.linkSystemLibrary("gtk4-layer-shell-0", dynamic_link_opts);
module.addImport("c", headers.mod);
} else {
_ = try buildLib(b, module, .{
.target = target,
@ -42,6 +53,7 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
b.installArtifact(lib);
@ -52,13 +64,12 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
const upstream = upstream_ orelse return lib;
const wayland_protocols = wayland_protocols_ orelse return lib;
lib.linkLibC();
lib.addIncludePath(upstream.path("include"));
lib.addIncludePath(upstream.path("src"));
lib.root_module.addIncludePath(upstream.path("include"));
lib.root_module.addIncludePath(upstream.path("src"));
module.addIncludePath(upstream.path("include"));
// GTK
lib.linkSystemLibrary2("gtk4", dynamic_link_opts);
lib.root_module.linkSystemLibrary("gtk4", dynamic_link_opts);
// Wayland headers and source files
{
@ -92,9 +103,9 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
const source_scanner = b.addSystemCommand(&.{ "wayland-scanner", "private-code" });
source_scanner.addFileArg(xml);
const source = source_scanner.addOutputFileArg(b.fmt("{s}.c", .{name}));
lib.addCSourceFile(.{ .file = source });
lib.root_module.addCSourceFile(.{ .file = source });
}
lib.addIncludePath(wf.getDirectory());
lib.root_module.addIncludePath(wf.getDirectory());
}
lib.installHeadersDirectory(
@ -113,7 +124,7 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
"stubbed-surface.c",
"xdg-surface-server.c",
};
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path("src"),
.files = srcs,
.flags = &.{

View File

@ -14,5 +14,10 @@
.hash = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S",
.lazy = true,
},
.translate_c = .{
.url = "https://codeberg.org/vancluever/translate-c/archive/c401682d0cbc6bc1f883d84886b8b1346922268d.tar.gz",
.hash = "translate_c-1.0.0-Q_BUWjnzBgDZX5ADyqP5K0kVqd-otnnj6Sld8Kzouvsa",
.lazy = true,
},
},
}

View File

@ -1,8 +1,9 @@
const std = @import("std");
const c = @cImport({
@cInclude("gtk4-layer-shell.h");
});
// const c = @cImport({
// @cInclude("gtk4-layer-shell.h");
// });
const c = @import("c");
const gdk = @import("gdk");
const gtk = @import("gtk");

View File

@ -54,9 +54,9 @@ pub fn build(b: *std.Build) !void {
var it = module.import_table.iterator();
while (it.next()) |entry| test_exe.root_module.addImport(entry.key_ptr.*, entry.value_ptr.*);
if (b.systemIntegrationOption("freetype", .{})) {
test_exe.linkSystemLibrary2("freetype2", dynamic_link_opts);
test_exe.root_module.linkSystemLibrary("freetype2", dynamic_link_opts);
} else {
test_exe.linkLibrary(freetype.artifact("freetype"));
test_exe.root_module.linkLibrary(freetype.artifact("freetype"));
}
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
@ -65,7 +65,7 @@ pub fn build(b: *std.Build) !void {
if (b.systemIntegrationOption("harfbuzz", .{})) {
module.linkSystemLibrary("harfbuzz", dynamic_link_opts);
test_exe.linkSystemLibrary2("harfbuzz", dynamic_link_opts);
test_exe.root_module.linkSystemLibrary("harfbuzz", dynamic_link_opts);
} else {
const lib = try buildLib(b, module, .{
.target = target,
@ -77,7 +77,7 @@ pub fn build(b: *std.Build) !void {
.dynamic_link_opts = dynamic_link_opts,
});
test_exe.linkLibrary(lib);
test_exe.root_module.linkLibrary(lib);
}
}
@ -99,18 +99,16 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime
// headers. The MSVC SDK include directories (added via linkLibC)
// contain both C and C++ headers, so linkLibCpp is not needed.
.link_libcpp = target.result.abi != .msvc,
}),
.linkage = .static,
});
lib.linkLibC();
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime headers.
// The MSVC SDK include directories (added via linkLibC) contain
// both C and C++ headers, so linkLibCpp is not needed.
if (target.result.abi != .msvc) {
lib.linkLibCpp();
}
if (target.result.os.tag.isDarwin()) {
try apple_sdk.addPaths(b, lib);
@ -154,10 +152,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
});
if (b.systemIntegrationOption("freetype", .{})) {
lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts);
module.linkSystemLibrary("freetype2", dynamic_link_opts);
} else {
lib.linkLibrary(freetype.artifact("freetype"));
lib.root_module.linkLibrary(freetype.artifact("freetype"));
if (freetype.builder.lazyDependency(
"freetype",
@ -170,14 +168,14 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
if (coretext_enabled) {
try flags.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"});
lib.linkFramework("CoreText");
lib.root_module.linkFramework("CoreText", .{});
module.linkFramework("CoreText", .{});
}
if (b.lazyDependency("harfbuzz", .{})) |upstream| {
lib.addIncludePath(upstream.path("src"));
lib.root_module.addIncludePath(upstream.path("src"));
module.addIncludePath(upstream.path("src"));
lib.addCSourceFile(.{
lib.root_module.addCSourceFile(.{
.file = upstream.path("src/harfbuzz.cc"),
.flags = flags.items,
});

View File

@ -18,17 +18,16 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/detect.zig"),
.target = target,
.optimize = optimize,
// Our highway package is free of libc at runtime (uses no symbols)
// but does require libc headers at compile time.
.link_libc = true,
}),
.linkage = .static,
});
// Our highway package is free of libc at runtime (uses no symbols)
// but does require libc headers at compile time.
lib.linkLibC();
lib.addIncludePath(b.path("src/cpp"));
lib.root_module.addIncludePath(b.path("src/cpp"));
if (upstream_) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.root_module.addIncludePath(upstream.path(""));
module.addIncludePath(upstream.path(""));
}
@ -95,7 +94,7 @@ pub fn build(b: *std.Build) !void {
});
}
lib.addCSourceFiles(.{ .flags = flags.items, .files = &.{
lib.root_module.addCSourceFiles(.{ .flags = flags.items, .files = &.{
"src/cpp/abort.cc",
"src/cpp/per_target.cc",
"src/cpp/targets.cpp",
@ -120,7 +119,7 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
}),
});
test_exe.linkLibrary(lib);
test_exe.root_module.linkLibrary(lib);
var it = module.import_table.iterator();
while (it.next()) |entry| test_exe.root_module.addImport(entry.key_ptr.*, entry.value_ptr.*);

View File

@ -35,11 +35,11 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.linkage = .static,
});
lib.linkLibC();
lib.addIncludePath(b.path(""));
lib.root_module.addIncludePath(b.path(""));
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
@ -47,9 +47,9 @@ pub fn build(b: *std.Build) !void {
}
if (b.lazyDependency("gettext", .{})) |upstream| {
lib.addIncludePath(upstream.path("gettext-runtime/intl"));
lib.addIncludePath(upstream.path("gettext-runtime/intl/gnulib-lib"));
lib.addCSourceFiles(.{
lib.root_module.addIncludePath(upstream.path("gettext-runtime/intl"));
lib.root_module.addIncludePath(upstream.path("gettext-runtime/intl/gnulib-lib"));
lib.root_module.addCSourceFiles(.{
.root = upstream.path("gettext-runtime/intl"),
.files = srcs,
.flags = flags.items,

View File

@ -9,18 +9,10 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.linkage = .static,
});
lib.linkLibC();
if (target.result.os.tag == .linux) {
lib.linkSystemLibrary("m");
}
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, lib);
}
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.
@ -28,20 +20,27 @@ pub fn build(b: *std.Build) !void {
.preferred_link_mode = .dynamic,
.search_strategy = .mode_first,
};
if (target.result.os.tag == .linux) {
lib.root_module.linkSystemLibrary("m", dynamic_link_opts);
}
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, lib);
}
if (b.systemIntegrationOption("zlib", .{})) {
lib.linkSystemLibrary2("zlib", dynamic_link_opts);
lib.root_module.linkSystemLibrary("zlib", dynamic_link_opts);
} else {
if (b.lazyDependency(
"zlib",
.{ .target = target, .optimize = optimize },
)) |zlib_dep| {
lib.linkLibrary(zlib_dep.artifact("z"));
lib.addIncludePath(b.path(""));
lib.root_module.linkLibrary(zlib_dep.artifact("z"));
lib.root_module.addIncludePath(b.path(""));
}
if (b.lazyDependency("libpng", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.root_module.addIncludePath(upstream.path(""));
}
}
@ -61,7 +60,7 @@ pub fn build(b: *std.Build) !void {
});
}
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,

View File

@ -11,18 +11,25 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.linkage = .static,
});
lib.linkLibC();
if (upstream_) |upstream| lib.addIncludePath(upstream.path("include"));
lib.addIncludePath(b.path("override/include"));
if (upstream_) |upstream| lib.root_module.addIncludePath(upstream.path("include"));
lib.root_module.addIncludePath(b.path("override/include"));
if (target.result.os.tag == .windows) {
lib.addIncludePath(b.path("override/config/win32"));
lib.linkSystemLibrary("ws2_32");
lib.root_module.addIncludePath(b.path("override/config/win32"));
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.
const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
.preferred_link_mode = .dynamic,
.search_strategy = .mode_first,
};
lib.root_module.linkSystemLibrary("ws2_32", dynamic_link_opts);
} else {
lib.addIncludePath(b.path("override/config/posix"));
lib.root_module.addIncludePath(b.path("override/config/posix"));
}
var flags: std.ArrayList([]const u8) = .empty;
@ -98,7 +105,7 @@ pub fn build(b: *std.Build) !void {
}
if (upstream_) |upstream| {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,

View File

@ -21,21 +21,21 @@ pub fn build(b: *std.Build) !void {
.linkage = .static,
});
lib.addCSourceFile(.{
lib.root_module.addCSourceFile(.{
.file = b.path("os/zig_macos.c"),
.flags = &.{"-std=c99"},
});
lib.addCSourceFile(.{
lib.root_module.addCSourceFile(.{
.file = b.path("text/ext.c"),
});
lib.linkFramework("CoreFoundation");
lib.linkFramework("CoreGraphics");
lib.linkFramework("CoreText");
lib.linkFramework("CoreVideo");
lib.linkFramework("QuartzCore");
lib.linkFramework("IOSurface");
lib.root_module.linkFramework("CoreFoundation", .{});
lib.root_module.linkFramework("CoreGraphics", .{});
lib.root_module.linkFramework("CoreText", .{});
lib.root_module.linkFramework("CoreVideo", .{});
lib.root_module.linkFramework("QuartzCore", .{});
lib.root_module.linkFramework("IOSurface", .{});
if (target.result.os.tag == .macos) {
lib.linkFramework("Carbon");
lib.root_module.linkFramework("Carbon", .{});
module.linkFramework("Carbon", .{});
}
@ -63,7 +63,7 @@ pub fn build(b: *std.Build) !void {
if (target.result.os.tag.isDarwin()) {
try apple_sdk.addPaths(b, test_exe);
}
test_exe.linkLibrary(lib);
test_exe.root_module.linkLibrary(lib);
var it = module.import_table.iterator();
while (it.next()) |entry| {

View File

@ -41,7 +41,7 @@ pub fn build(b: *std.Build) !void {
module.linkSystemLibrary("oniguruma", dynamic_link_opts);
if (test_exe) |exe| {
exe.linkSystemLibrary2("oniguruma", dynamic_link_opts);
exe.root_module.linkSystemLibrary("oniguruma", dynamic_link_opts);
}
} else {
const lib = try buildLib(b, module, .{
@ -50,7 +50,7 @@ pub fn build(b: *std.Build) !void {
});
if (test_exe) |exe| {
exe.linkLibrary(lib);
exe.root_module.linkLibrary(lib);
}
}
}
@ -64,12 +64,12 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.linkage = .static,
});
const t = target.result;
const is_windows = t.os.tag == .windows;
lib.linkLibC();
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
@ -77,10 +77,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
}
if (b.lazyDependency("oniguruma", .{})) |upstream| {
lib.addIncludePath(upstream.path("src"));
lib.root_module.addIncludePath(upstream.path("src"));
module.addIncludePath(upstream.path("src"));
lib.addConfigHeader(b.addConfigHeader(.{
lib.root_module.addConfigHeader(b.addConfigHeader(.{
.style = .{ .cmake = upstream.path("src/config.h.cmake.in") },
}, .{
.PACKAGE = "oniguruma",
@ -109,7 +109,7 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
"-fno-sanitize-trap=undefined",
});
}
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.flags = flags.items,
.files = &.{

View File

@ -17,10 +17,10 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.linkage = .static,
});
lib.linkLibC();
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, lib);
@ -46,9 +46,9 @@ pub fn build(b: *std.Build) !void {
if (b.lazyDependency("sentry", .{})) |upstream| {
module.addIncludePath(upstream.path("include"));
lib.addIncludePath(upstream.path("include"));
lib.addIncludePath(upstream.path("src"));
lib.addCSourceFiles(.{
lib.root_module.addIncludePath(upstream.path("include"));
lib.root_module.addIncludePath(upstream.path("src"));
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,
@ -56,7 +56,7 @@ pub fn build(b: *std.Build) !void {
// Linux-only
if (target.result.os.tag == .linux) {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"vendor/stb_sprintf.c",
@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void {
// Symbolizer + Unwinder
if (target.result.os.tag == .windows) {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/sentry_windows_dbghelp.c",
@ -78,7 +78,7 @@ pub fn build(b: *std.Build) !void {
.flags = flags.items,
});
} else {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/sentry_unix_pageallocator.c",
@ -92,7 +92,7 @@ pub fn build(b: *std.Build) !void {
// Module finder
switch (target.result.os.tag) {
.windows => lib.addCSourceFiles(.{
.windows => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/modulefinder/sentry_modulefinder_windows.c",
@ -100,7 +100,7 @@ pub fn build(b: *std.Build) !void {
.flags = flags.items,
}),
.macos, .ios => lib.addCSourceFiles(.{
.macos, .ios => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/modulefinder/sentry_modulefinder_apple.c",
@ -108,7 +108,7 @@ pub fn build(b: *std.Build) !void {
.flags = flags.items,
}),
.linux => lib.addCSourceFiles(.{
.linux => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/modulefinder/sentry_modulefinder_linux.c",
@ -126,7 +126,7 @@ pub fn build(b: *std.Build) !void {
// Transport
switch (transport) {
.curl => lib.addCSourceFiles(.{
.curl => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/transports/sentry_transport_curl.c",
@ -134,7 +134,7 @@ pub fn build(b: *std.Build) !void {
.flags = flags.items,
}),
.winhttp => lib.addCSourceFiles(.{
.winhttp => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/transports/sentry_transport_winhttp.c",
@ -142,7 +142,7 @@ pub fn build(b: *std.Build) !void {
.flags = flags.items,
}),
.none => lib.addCSourceFiles(.{
.none => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/transports/sentry_transport_none.c",
@ -153,7 +153,7 @@ pub fn build(b: *std.Build) !void {
// Backend
switch (backend) {
.crashpad => lib.addCSourceFiles(.{
.crashpad => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/backends/sentry_backend_crashpad.cpp",
@ -162,7 +162,7 @@ pub fn build(b: *std.Build) !void {
}),
.breakpad => {
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/backends/sentry_backend_breakpad.cpp",
@ -174,15 +174,15 @@ pub fn build(b: *std.Build) !void {
.target = target,
.optimize = optimize,
})) |breakpad_dep| {
lib.linkLibrary(breakpad_dep.artifact("breakpad"));
lib.root_module.linkLibrary(breakpad_dep.artifact("breakpad"));
// We need to add this because Sentry includes some breakpad
// headers that include this vendored file...
lib.addIncludePath(breakpad_dep.path("vendor"));
lib.root_module.addIncludePath(breakpad_dep.path("vendor"));
}
},
.inproc => lib.addCSourceFiles(.{
.inproc => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/backends/sentry_backend_inproc.c",
@ -190,7 +190,7 @@ pub fn build(b: *std.Build) !void {
.flags = flags.items,
}),
.none => lib.addCSourceFiles(.{
.none => lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = &.{
"src/backends/sentry_backend_none.c",

View File

@ -10,26 +10,21 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
// We link libcpp even with no_libcxx because simdutf requires
// libc++ headers at build time. But it doesn't require libc++ at
// runtime. For Ghostty itself, we have CI tests to verify this.
//
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime
// headers. The MSVC SDK include directories (added via linkLibC)
// contain both C and C++ headers, so linkLibCpp is not needed.
.link_libcpp = target.result.abi != .msvc,
}),
.linkage = .static,
});
lib.addIncludePath(b.path("vendor"));
lib.linkLibC();
libcpp: {
if (target.result.abi == .msvc) {
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime headers.
// The MSVC SDK include directories (added via linkLibC) contain
// both C and C++ headers, so linkLibCpp is not needed.
break :libcpp;
}
// We link libcpp even with no_libcxx because simdutf requires
// libc++ headers at build time. But it doesn't require libc++
// at runtime. For Ghostty itself, we have CI tests to verify this.
lib.linkLibCpp();
}
lib.root_module.addIncludePath(b.path("vendor"));
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
@ -74,7 +69,7 @@ pub fn build(b: *std.Build) !void {
try flags.append(b.allocator, "-fPIC");
}
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.flags = flags.items,
.files = &.{
"vendor/simdutf.cpp",

View File

@ -34,12 +34,12 @@ pub fn build(b: *std.Build) !void {
if (b.systemIntegrationOption("spirv-cross", .{})) {
module.linkSystemLibrary("spirv-cross-c-shared", dynamic_link_opts);
if (test_exe) |exe| {
exe.linkSystemLibrary2("spirv-cross-c-shared", dynamic_link_opts);
exe.root_module.linkSystemLibrary("spirv-cross-c-shared", dynamic_link_opts);
}
} else {
const lib = try buildSpirvCross(b, module, target, optimize);
b.installArtifact(lib);
if (test_exe) |exe| exe.linkLibrary(lib);
if (test_exe) |exe| exe.root_module.linkLibrary(lib);
}
}
@ -54,18 +54,16 @@ fn buildSpirvCross(
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime
// headers. The MSVC SDK include directories (added via linkLibC)
// contain both C and C++ headers, so linkLibCpp is not needed.
.link_libcpp = target.result.abi != .msvc,
}),
.linkage = .static,
});
lib.linkLibC();
// On MSVC, we must not use linkLibCpp because Zig unconditionally
// passes -nostdinc++ and then adds its bundled libc++/libc++abi
// include paths, which conflict with MSVC's own C++ runtime headers.
// The MSVC SDK include directories (added via linkLibC) contain
// both C and C++ headers, so linkLibCpp is not needed.
if (target.result.abi != .msvc) {
lib.linkLibCpp();
}
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, lib);
@ -86,9 +84,9 @@ fn buildSpirvCross(
}
if (b.lazyDependency("spirv_cross", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.root_module.addIncludePath(upstream.path(""));
module.addIncludePath(upstream.path(""));
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.flags = flags.items,
.files = &.{

View File

@ -1,4 +1,38 @@
const std = @import("std");
const Translator = @import("translate_c").Translator;
// All the C macros defined so that the header matches the build.
const defines = [_][]const u8{
"WUFFS_CONFIG__MODULES",
"WUFFS_CONFIG__MODULE__AUX__BASE",
"WUFFS_CONFIG__MODULE__AUX__IMAGE",
"WUFFS_CONFIG__MODULE__BASE",
"WUFFS_CONFIG__MODULE__ADLER32",
"WUFFS_CONFIG__MODULE__CRC32",
"WUFFS_CONFIG__MODULE__DEFLATE",
"WUFFS_CONFIG__MODULE__JPEG",
"WUFFS_CONFIG__MODULE__PNG",
"WUFFS_CONFIG__MODULE__ZLIB",
};
// Generated C code, includes the macros above. Designed to mimic old c.zig.
// TODO: is this still needed, or are the -D flags enough?
const wuffs_c_source = wuffs_c_source: {
const include: []const u8 = "#include <wuffs-v0.4.c>";
const len = len: {
var len: usize = 0;
for (defines) |d| len += std.fmt.count("#define {s}\n", .{d});
len += std.fmt.count("{s}\n", .{include});
break :len len;
};
var buf: [len:0]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
for (defines) |d| writer.print("#define {s}\n", .{d}) catch unreachable;
writer.print("{s}\n", .{include}) catch unreachable;
buf[len] = 0;
break :wuffs_c_source buf;
};
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
@ -15,25 +49,35 @@ pub fn build(b: *std.Build) !void {
.name = "test",
.root_module = module,
});
unit_tests.linkLibC();
var flags: std.ArrayList([]const u8) = .empty;
defer flags.deinit(b.allocator);
try flags.append(b.allocator, "-DWUFFS_IMPLEMENTATION");
if (target.result.abi == .msvc) {
try flags.append(b.allocator, "-fno-sanitize=undefined");
try flags.append(b.allocator, "-fno-sanitize-trap=undefined");
}
inline for (@import("src/c.zig").defines) |key| {
try flags.append(b.allocator, "-D" ++ key);
}
if (b.lazyDependency("wuffs", .{})) |wuffs_dep| {
module.addIncludePath(wuffs_dep.path("release/c"));
module.addCSourceFile(.{
.file = wuffs_dep.path("release/c/wuffs-v0.4.c"),
.flags = flags.items,
{
const translate_c = b.dependency("translate_c", .{});
const wuffs_c: Translator = .init(translate_c, .{
.c_source_file = b.addWriteFiles().add("wuffs_c.h", &wuffs_c_source),
.target = target,
.optimize = optimize,
});
var flags: std.ArrayList([]const u8) = .empty;
defer flags.deinit(b.allocator);
try flags.append(b.allocator, "-DWUFFS_IMPLEMENTATION");
if (target.result.abi == .msvc) {
try flags.append(b.allocator, "-fno-sanitize=undefined");
try flags.append(b.allocator, "-fno-sanitize-trap=undefined");
}
inline for (defines) |key| {
try flags.append(b.allocator, "-D" ++ key);
}
if (b.lazyDependency("wuffs", .{})) |wuffs_dep| {
wuffs_c.addIncludePath(wuffs_dep.path("release/c"));
wuffs_c.mod.addCSourceFile(.{
.file = wuffs_dep.path("release/c/wuffs-v0.4.c"),
.flags = flags.items,
});
}
module.addImport("wuffs_c", wuffs_c.mod);
}
if (b.lazyDependency("pixels", .{})) |pixels_dep| {

View File

@ -3,6 +3,11 @@
.version = "0.0.0",
.fingerprint = 0x67c0c059de921c4f,
.dependencies = .{
.translate_c = .{
.url = "https://codeberg.org/vancluever/translate-c/archive/c401682d0cbc6bc1f883d84886b8b1346922268d.tar.gz",
.hash = "translate_c-1.0.0-Q_BUWjnzBgDZX5ADyqP5K0kVqd-otnnj6Sld8Kzouvsa",
},
// google/wuffs
.wuffs = .{
.url = "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",

View File

@ -1,18 +0,0 @@
pub const c = @cImport({
for (defines) |d| @cDefine(d, "1");
@cInclude("wuffs-v0.4.c");
});
/// All the C macros defined so that the header matches the build.
pub const defines: []const []const u8 = &[_][]const u8{
"WUFFS_CONFIG__MODULES",
"WUFFS_CONFIG__MODULE__AUX__BASE",
"WUFFS_CONFIG__MODULE__AUX__IMAGE",
"WUFFS_CONFIG__MODULE__BASE",
"WUFFS_CONFIG__MODULE__ADLER32",
"WUFFS_CONFIG__MODULE__CRC32",
"WUFFS_CONFIG__MODULE__DEFLATE",
"WUFFS_CONFIG__MODULE__JPEG",
"WUFFS_CONFIG__MODULE__PNG",
"WUFFS_CONFIG__MODULE__ZLIB",
};

View File

@ -1,6 +1,6 @@
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("wuffs_c");
pub const Error = std.mem.Allocator.Error || error{ WuffsError, Overflow };

View File

@ -1,6 +1,6 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
const c = @import("wuffs_c");
const Error = @import("error.zig").Error;
const check = @import("error.zig").check;
const ImageData = @import("main.zig").ImageData;

View File

@ -1,6 +1,6 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const c = @import("c.zig").c;
const c = @import("wuffs_c");
const Error = @import("error.zig").Error;
const check = @import("error.zig").check;
const ImageData = @import("main.zig").ImageData;

View File

@ -1,7 +1,7 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const c = @import("c.zig").c;
const c = @import("wuffs_c");
const Error = @import("error.zig").Error;
const log = std.log.scoped(.wuffs_swizzler);

View File

@ -9,17 +9,17 @@ pub fn build(b: *std.Build) !void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
.linkage = .static,
});
lib.linkLibC();
if (target.result.os.tag.isDarwin()) {
const apple_sdk = @import("apple_sdk");
try apple_sdk.addPaths(b, lib);
}
if (b.lazyDependency("zlib", .{})) |upstream| {
lib.addIncludePath(upstream.path(""));
lib.root_module.addIncludePath(upstream.path(""));
lib.installHeadersDirectory(
upstream.path(""),
"",
@ -48,7 +48,7 @@ pub fn build(b: *std.Build) !void {
"-D_CRT_NONSTDC_NO_DEPRECATE",
});
}
lib.addCSourceFiles(.{
lib.root_module.addCSourceFiles(.{
.root = upstream.path(""),
.files = srcs,
.flags = flags.items,

View File

@ -56,7 +56,7 @@ font_grid_set: font.SharedGridSet,
// Used to rate limit desktop notifications. Some platforms (notably macOS) will
// run out of resources if desktop notifications are sent too fast and the OS
// will kill Ghostty.
last_notification_time: ?std.time.Instant = null,
last_notification_time: ?std.Io.Timestamp = null,
last_notification_digest: u64 = 0,
/// The conditional state of the configuration. See the equivalent field
@ -95,7 +95,7 @@ pub fn init(
self.* = .{
.alloc = alloc,
.surfaces = .{},
.surfaces = .empty,
.mailbox = .{},
.font_grid_set = font_grid_set,
.config_conditional_state = .{},

View File

@ -26,11 +26,14 @@ const TempDir = internal_os.TempDir;
const mem = std.mem;
const linux = std.os.linux;
const posix = std.posix;
const compat_dir = @import("lib/compat/dir.zig");
const compat_exec = @import("lib/compat/exec.zig");
const compat_process = @import("lib/compat/process.zig");
const debug = std.debug;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const File = std.fs.File;
const EnvMap = std.process.EnvMap;
const File = std.Io.File;
const EnvMap = std.process.Environ.Map;
const apprt = @import("apprt.zig");
/// Function prototype for a function executed /in the child process/ after the
@ -65,7 +68,7 @@ env: ?*const EnvMap = null,
/// Working directory to change to in the child process. If not set, the
/// working directory of the calling process is preserved.
cwd: ?[]const u8 = null,
cwd: ?[:0]const u8 = null,
/// The file handle to set for stdin/out/err. If this isn't set, we do
/// nothing explicitly so it is up to the behavior of the operating system.
@ -106,7 +109,7 @@ pseudo_console: if (builtin.os.tag == .windows) ?windows.exp.HPCON else void =
data: ?*anyopaque = null,
/// Process ID is set after start is called.
pid: ?posix.pid_t = null,
pid: ?posix.system.pid_t = null,
/// The various methods a process may exit.
pub const Exit = if (builtin.os.tag == .windows) union(enum) {
@ -128,9 +131,9 @@ pub const Exit = if (builtin.os.tag == .windows) union(enum) {
return if (posix.W.IFEXITED(status))
Exit{ .Exited = posix.W.EXITSTATUS(status) }
else if (posix.W.IFSIGNALED(status))
Exit{ .Signal = posix.W.TERMSIG(status) }
Exit{ .Signal = @intFromEnum(posix.W.TERMSIG(status)) }
else if (posix.W.IFSTOPPED(status))
Exit{ .Stopped = posix.W.STOPSIG(status) }
Exit{ .Stopped = @intFromEnum(posix.W.STOPSIG(status)) }
else
Exit{ .Unknown = status };
}
@ -186,7 +189,7 @@ fn startPosix(self: *Command, arena: Allocator) !void {
@compileError("missing env vars");
// Fork.
const pid = try posix.fork();
const pid = try compat_process.fork();
if (pid != 0) {
// Parent, return immediately.
@ -206,7 +209,7 @@ fn startPosix(self: *Command, arena: Allocator) !void {
return error.ExecFailedInChild;
// Setup our working directory
if (self.cwd) |cwd| posix.chdir(cwd) catch {
if (self.cwd) |cwd| compat_dir.chdir(cwd) catch {
// This can fail if we don't have permission to go to
// this directory or if due to race conditions it doesn't
// exist or any various other reasons. We don't want to
@ -219,20 +222,20 @@ fn startPosix(self: *Command, arena: Allocator) !void {
global_state.rlimits.restore();
// If there are pre exec callbacks, call them now.
if (self.os_pre_exec) |f| if (f(self)) |exitcode| posix.exit(exitcode);
if (self.rt_pre_exec) |f| if (f(self)) |exitcode| posix.exit(exitcode);
if (self.os_pre_exec) |f| if (f(self)) |exitcode| posix.system.exit(exitcode);
if (self.rt_pre_exec) |f| if (f(self)) |exitcode| posix.system.exit(exitcode);
// Finally, replace our process.
// Note: we must use the "p"-variant of exec here because we
// do not guarantee our command is looked up already in the path.
const err = posix.execvpeZ(self.path, argsZ, envp);
const err = compat_exec.execvpeZ(self.path, argsZ, envp);
// If we are executing this code, the exec failed. We're in the
// child process so there isn't much we can do. We try to output
// something reasonable. Its important to note we MUST NOT return
// any other error condition from here on out.
var stderr_buf: [1024]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&stderr_buf);
var stderr_writer = std.Io.File.stderr().writer(std.Io.Threaded.global_single_threaded.io(), &stderr_buf);
const stderr = &stderr_writer.interface;
switch (err) {
error.FileNotFound => stderr.print(
@ -283,7 +286,7 @@ fn startWindows(self: *Command, arena: Allocator) !void {
.creation = windows.OPEN_EXISTING,
},
) else null;
defer if (null_fd) |fd| posix.close(fd);
defer if (null_fd) |fd| posix.system.close(fd);
// TODO: In the case of having FDs instead of pty, need to set up
// attributes such that the child process only inherits these handles,
@ -395,9 +398,9 @@ fn setupFd(src: File.Handle, target: i32) !void {
.freebsd, .ios, .macos => {
// Mac doesn't support dup3 so we use dup2. We purposely clear
// CLO_ON_EXEC for this fd.
const flags = try posix.fcntl(src, posix.F.GETFD, 0);
const flags = try posix.system.fcntl(src, posix.F.GETFD, 0);
if (flags & posix.FD_CLOEXEC != 0) {
_ = try posix.fcntl(src, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC));
_ = try posix.system.fcntl(src, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC));
}
try posix.dup2(src, target);
@ -425,7 +428,7 @@ pub fn wait(self: Command, block: bool) !Exit {
return .{ .Exited = exit_code };
}
const res = if (block) posix.waitpid(self.pid.?, 0) else res: {
const res = if (block) compat_process.waitpid(self.pid.?, 0) else res: {
// We specify NOHANG because its not our fault if the process we launch
// for the tty doesn't properly waitpid its children. We don't want
// to hang the terminal over it.
@ -434,7 +437,7 @@ pub fn wait(self: Command, block: bool) !Exit {
// wait call has not been performed, so we need to keep trying until we get
// a non-zero pid back, otherwise we end up with zombie processes.
while (true) {
const res = posix.waitpid(self.pid.?, std.c.W.NOHANG);
const res = compat_process.waitpid(self.pid.?, std.c.W.NOHANG);
if (res.pid != 0) break :res res;
}
};
@ -587,7 +590,7 @@ test "Command: os pre exec 1" {
fn do(_: *Command) ?u8 {
// This runs in the child, so we can exit and it won't
// kill the test runner.
posix.exit(42);
posix.system.exit(42);
}
}).do,
.rt_pre_exec = null,
@ -638,7 +641,7 @@ test "Command: rt pre exec 1" {
fn do(_: *Command) ?u8 {
// This runs in the child, so we can exit and it won't
// kill the test runner.
posix.exit(42);
posix.system.exit(42);
}
}).do,
.rt_post_fork = null,
@ -697,8 +700,8 @@ test "Command: rt post fork 1" {
try testing.expectError(error.PostForkError, cmd.testingStart());
}
fn createTestStdout(dir: std.fs.Dir) !File {
const file = try dir.createFile("stdout.txt", .{ .read = true });
fn createTestStdout(io: std.Io, dir: std.Io.Dir) !File {
const file = try dir.createFile(io, "stdout.txt", .{ .read = true });
if (builtin.os.tag == .windows) {
try windows.SetHandleInformation(
file.handle,
@ -710,8 +713,8 @@ fn createTestStdout(dir: std.fs.Dir) !File {
return file;
}
fn createTestStderr(dir: std.fs.Dir) !File {
const file = try dir.createFile("stderr.txt", .{ .read = true });
fn createTestStderr(io: std.Io, dir: std.Io.Dir) !File {
const file = try dir.createFile(io, "stderr.txt", .{ .read = true });
if (builtin.os.tag == .windows) {
try windows.SetHandleInformation(
file.handle,
@ -726,8 +729,8 @@ fn createTestStderr(dir: std.fs.Dir) !File {
test "Command: redirect stdout to file" {
var td = try TempDir.init();
defer td.deinit();
var stdout = try createTestStdout(td.dir);
defer stdout.close();
var stdout = try createTestStdout(testing.io, td.dir);
defer stdout.close(testing.io);
var cmd: Command = if (builtin.os.tag == .windows) .{
.path = "C:\\Windows\\System32\\whoami.exe",
@ -756,8 +759,13 @@ test "Command: redirect stdout to file" {
try testing.expectEqual(@as(u32, 0), @as(u32, exit.Exited));
// Read our stdout
try stdout.seekTo(0);
const contents = try stdout.readToEndAlloc(testing.allocator, 1024 * 128);
const contents = contents: {
const size = (try stdout.stat(testing.io)).size;
const data = try testing.allocator.alloc(u8, size);
errdefer testing.allocator.free(data);
try testing.expectEqual(size, try stdout.readPositionalAll(testing.io, data, 0));
break :contents data;
};
defer testing.allocator.free(contents);
try testing.expect(contents.len > 0);
}
@ -765,8 +773,8 @@ test "Command: redirect stdout to file" {
test "Command: custom env vars" {
var td = try TempDir.init();
defer td.deinit();
var stdout = try createTestStdout(td.dir);
defer stdout.close();
var stdout = try createTestStdout(testing.io, td.dir);
defer stdout.close(testing.io);
var env = EnvMap.init(testing.allocator);
defer env.deinit();
@ -801,8 +809,13 @@ test "Command: custom env vars" {
try testing.expect(exit.Exited == 0);
// Read our stdout
try stdout.seekTo(0);
const contents = try stdout.readToEndAlloc(testing.allocator, 4096);
const contents = contents: {
const size = (try stdout.stat(testing.io)).size;
const data = try testing.allocator.alloc(u8, size);
errdefer testing.allocator.free(data);
try testing.expectEqual(size, try stdout.readPositionalAll(testing.io, data, 0));
break :contents data;
};
defer testing.allocator.free(contents);
if (builtin.os.tag == .windows) {
@ -815,8 +828,8 @@ test "Command: custom env vars" {
test "Command: custom working directory" {
var td = try TempDir.init();
defer td.deinit();
var stdout = try createTestStdout(td.dir);
defer stdout.close();
var stdout = try createTestStdout(testing.io, td.dir);
defer stdout.close(testing.io);
var cmd: Command = if (builtin.os.tag == .windows) .{
.path = "C:\\Windows\\System32\\cmd.exe",
@ -847,8 +860,13 @@ test "Command: custom working directory" {
try testing.expect(exit.Exited == 0);
// Read our stdout
try stdout.seekTo(0);
const contents = try stdout.readToEndAlloc(testing.allocator, 4096);
const contents = contents: {
const size = (try stdout.stat(testing.io)).size;
const data = try testing.allocator.alloc(u8, size);
errdefer testing.allocator.free(data);
try testing.expectEqual(size, try stdout.readPositionalAll(testing.io, data, 0));
break :contents data;
};
defer testing.allocator.free(contents);
if (builtin.os.tag == .windows) {
@ -872,10 +890,10 @@ test "Command: posix fork handles execveZ failure" {
}
var td = try TempDir.init();
defer td.deinit();
var stdout = try createTestStdout(td.dir);
defer stdout.close();
var stderr = try createTestStderr(td.dir);
defer stderr.close();
var stdout = try createTestStdout(testing.io, td.dir);
defer stdout.close(testing.io);
var stderr = try createTestStderr(testing.io, td.dir);
defer stderr.close(testing.io);
var cmd: Command = .{
.path = "/not/a/binary",
@ -904,7 +922,7 @@ fn testingStart(self: *Command) !void {
self.start(testing.allocator) catch |err| {
if (err == error.ExecFailedInChild) {
// I am a child process, I must not get confused and continue running the rest of the test suite.
posix.exit(1);
posix.system.exit(1);
}
return err;
};

View File

@ -168,13 +168,13 @@ readonly: bool = false,
/// precision timestamp. It does not necessarily need to correspond to the
/// actual time, but we must be able to compare two subsequent timestamps to get
/// the wall clock time that has elapsed between timestamps.
command_timer: ?std.time.Instant = null,
command_timer: ?std.Io.Timestamp = null,
/// Search state
search: ?Search = null,
/// Used to rate limit BEL handling.
last_bell_time: ?std.time.Instant = null,
last_bell_time: ?std.Io.Timestamp = null,
/// The effect of an input event. This can be used by callers to take
/// the appropriate action after an input event. For example, key
@ -239,7 +239,7 @@ const Mouse = struct {
/// The left click time was the last time the left click was done. This
/// is always set on the first left click.
left_click_count: u8 = 0,
left_click_time: std.time.Instant = undefined,
left_click_time: std.Io.Timestamp = undefined,
/// The last x/y sent for mouse reports.
event_point: ?terminal.point.Coordinate = null,
@ -568,8 +568,8 @@ pub fn init(
errdefer renderer_impl.deinit();
// The mutex used to protect our renderer state.
const mutex = try alloc.create(std.Thread.Mutex);
mutex.* = .{};
const mutex = try alloc.create(std.Io.Mutex);
mutex.* = .init;
errdefer alloc.destroy(mutex);
// Create the renderer thread
@ -590,7 +590,11 @@ pub fn init(
self.* = .{
.id = id: {
while (true) {
const candidate = std.crypto.random.int(u64);
const candidate = candidate: {
const rng_impl: std.Random.IoSource = .{ .io = std.Io.Threaded.global_single_threaded.io() };
const rng = rng_impl.interface();
break :candidate rng.int(u64);
};
if (candidate == 0) continue;
break :id candidate;
}
@ -641,12 +645,12 @@ pub fn init(
// If an error occurs, we don't want to block surface startup.
log.warn("error getting env map for surface err={}", .{err});
break :env internal_os.getEnvMap(alloc) catch
std.process.EnvMap.init(alloc);
std.process.Environ.Map.init(alloc);
};
errdefer env.deinit();
// don't leak GHOSTTY_LOG to any subprocesses
env.remove("GHOSTTY_LOG");
_ = env.orderedRemove("GHOSTTY_LOG");
var buf: [18]u8 = undefined;
try env.put(
@ -726,7 +730,7 @@ pub fn init(
rendererpkg.Thread.threadMain,
.{&self.renderer_thread},
);
self.renderer_thr.setName("renderer") catch {};
self.renderer_thr.setName(std.Io.Threaded.global_single_threaded.io(), "renderer") catch {};
// Start our IO thread
self.io_thr = try std.Thread.spawn(
@ -734,7 +738,7 @@ pub fn init(
termio.Thread.threadMain,
.{ &self.io_thread, &self.io },
);
self.io_thr.setName("io") catch {};
self.io_thr.setName(std.Io.Threaded.global_single_threaded.io(), "io") catch {};
// Determine our initial window size if configured. We need to do this
// quite late in the process because our height/width are in grid dimensions,
@ -908,8 +912,8 @@ pub fn activateInspector(self: *Surface) !void {
// Put the inspector onto the render state
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
assert(self.renderer_state.inspector == null);
self.renderer_state.inspector = self.inspector;
}
@ -925,8 +929,8 @@ pub fn deactivateInspector(self: *Surface) void {
// Remove the inspector from the render state
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
assert(self.renderer_state.inspector != null);
self.renderer_state.inspector = null;
}
@ -956,8 +960,8 @@ pub fn needsConfirmQuit(self: *Surface) bool {
.always => true,
.false => false,
.true => true: {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
break :true !self.io.terminal.cursorIsAtPrompt();
},
};
@ -1104,9 +1108,9 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
.password_input => |v| try self.passwordInput(v),
.ring_bell => bell: {
const now = std.time.Instant.now() catch unreachable;
const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
if (self.last_bell_time) |last| {
if (now.since(last) < 100 * std.time.ns_per_ms) break :bell;
if (last.durationTo(now).toMilliseconds() < 100) break :bell;
}
self.last_bell_time = now;
_ = self.rt_app.performAction(
@ -1134,15 +1138,22 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
},
.start_command => {
self.command_timer = try .now();
self.command_timer = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
},
.stop_command => |v| timer: {
const end: std.time.Instant = try .now();
const end: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
const start = self.command_timer orelse break :timer;
self.command_timer = null;
const duration_raw = start.durationTo(end).nanoseconds;
assert(duration_raw >= 0 and duration_raw <= std.math.maxInt(u64));
const duration: Duration = .{ .duration = end.since(start) };
const duration: Duration = .{
.duration = @as(
u64,
@intCast(std.math.clamp(start.durationTo(end).nanoseconds, 0, std.math.maxInt(u64))),
),
};
log.debug("command took {f}", .{duration});
_ = self.rt_app.performAction(
@ -1188,8 +1199,8 @@ fn selectionScrollTick(self: *Surface) !void {
const delta: isize = if (pos.y < 0) -1 else 1;
// We need our locked state for the remainder
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
// If our screen changed while this is happening, we stop our
@ -1277,8 +1288,8 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void {
// If the native GUI can't be shown, display a text message in the
// terminal.
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
t.carriageReturn();
t.linefeed() catch break :terminal;
@ -1317,8 +1328,8 @@ fn childExitedAbnormally(
});
const runtime_str = try std.fmt.allocPrint(alloc, "{d} ms", .{info.runtime_ms});
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
// No matter what move the cursor back to the column 0.
@ -1384,8 +1395,8 @@ fn childExitedAbnormally(
/// Called when the terminal detects there is a password input prompt.
fn passwordInput(self: *Surface, v: bool) !void {
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// If our password input state is unchanged then we don't
// waste time doing anything more.
@ -1540,8 +1551,8 @@ fn modsChanged(self: *Surface, mods: input.Mods) void {
// highlight links. Additionally, mark the screen as dirty so
// that the highlight state of all links is properly updated.
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
self.renderer_state.mouse.mods = self.mouseModsWithCapture(self.mouse.mods);
// We use the clear screen dirty flag to force a rebuild of all
@ -1908,8 +1919,8 @@ pub fn dumpText(
alloc: Allocator,
sel: terminal.Selection,
) !Text {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
return try self.dumpTextLocked(alloc, sel);
}
@ -2034,15 +2045,15 @@ pub fn dumpTextLocked(
/// Returns true if the terminal has a selection.
pub fn hasSelection(self: *const Surface) bool {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
return self.io.terminal.screens.active.selection != null;
}
/// Returns the selected text. This is allocated.
pub fn selectionString(self: *Surface, alloc: Allocator) !?[:0]const u8 {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const sel = self.io.terminal.screens.active.selection orelse return null;
return try self.io.terminal.screens.active.selectionString(alloc, .{
.sel = sel,
@ -2057,8 +2068,8 @@ pub fn pwd(
self: *const Surface,
alloc: Allocator,
) Allocator.Error!?[]const u8 {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const terminal_pwd = self.io.terminal.getPwd() orelse return null;
return try alloc.dupe(u8, terminal_pwd);
}
@ -2075,7 +2086,7 @@ fn resolvePathForOpening(
const resolved = try std.fs.path.resolve(self.alloc, &.{ terminal_pwd, path });
std.fs.accessAbsolute(resolved, .{}) catch {
std.Io.Dir.accessAbsolute(std.Io.Threaded.global_single_threaded.io(), resolved, .{}) catch {
self.alloc.free(resolved);
return null;
};
@ -2089,10 +2100,10 @@ fn resolvePathForOpening(
/// Returns the x/y coordinate of where the IME (Input Method Editor)
/// keyboard should be rendered.
pub fn imePoint(self: *const Surface) apprt.IMEPos {
self.renderer_state.mutex.lock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
const cursor = self.renderer_state.terminal.screens.active.cursor;
const preedit_width: usize = if (self.renderer_state.preedit) |preedit| preedit.width() else 0;
self.renderer_state.mutex.unlock();
self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// TODO: need to handle when scrolling and the cursor is not
// in the visible portion of the screen.
@ -2508,8 +2519,8 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
crash.sentry.thread_state = self.crashThreadState();
defer crash.sentry.thread_state = null;
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// We clear our selection when ANY OF:
// 1. We have an existing preedit
@ -2544,7 +2555,7 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void {
// Allocate the codepoints slice
const Codepoint = rendererpkg.State.Preedit.Codepoint;
var codepoints: std.ArrayListUnmanaged(Codepoint) = .{};
var codepoints: std.ArrayList(Codepoint) = .empty;
defer codepoints.deinit(self.alloc);
while (it.nextCodepoint()) |cp| {
const width: usize = @intCast(unicode.table.get(cp).width);
@ -2675,8 +2686,8 @@ pub fn keyCallback(
)) |v| return v;
// If we allow KAM and KAM is enabled then we do nothing.
if (self.config.vt_kam_allowed) {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (self.io.terminal.modes.get(.disable_keyboard)) return .consumed;
}
@ -2706,8 +2717,8 @@ pub fn keyCallback(
{
// Refresh our link state
const pos = self.rt_surface.getCursorPos() catch break :mouse_mods;
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
self.mouseRefreshLinks(
pos,
self.posToViewport(pos.x, pos.y),
@ -2799,8 +2810,8 @@ pub fn keyCallback(
// some data to send to the pty, then we move the viewport down to the
// bottom. We also clear the selection for any key other then modifiers.
if (!event.key.modifier()) {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (self.config.selection_clear_on_typing or
event.key == .escape)
@ -3231,8 +3242,8 @@ fn encodeKey(
}
fn encodeKeyOpts(self: *const Surface) input.key_encode.Options {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const t = &self.io.terminal;
var opts: input.key_encode.Options = .fromTerminal(t);
@ -3360,9 +3371,9 @@ pub fn focusCallback(self: *Surface, focused: bool) !void {
// Update the focus state and notify the terminal
{
self.renderer_state.mutex.lock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
self.io.terminal.flags.focused = focused;
self.renderer_state.mutex.unlock();
self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
self.queueIo(.{ .focused = focused }, .unlocked);
}
}
@ -3494,8 +3505,8 @@ pub fn scrollCallback(
// log.info("SCROLL: delta_y={} delta_x={}", .{ y.delta, x.delta });
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// If we have an active mouse reporting mode, clear the selection.
// The selection can occur if the user uses the shift mod key to
@ -3695,8 +3706,8 @@ fn mouseShiftCapture(self: *const Surface, lock: bool) bool {
.false, .true => {},
}
if (lock) self.renderer_state.mutex.lock();
defer if (lock) self.renderer_state.mutex.unlock();
if (lock) self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer if (lock) self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// If the terminal explicitly requests it then we always allow it
// since we processed never/always at this point.
@ -3717,8 +3728,8 @@ fn mouseShiftCapture(self: *const Surface, lock: bool) bool {
/// Returns true if the mouse is currently captured by the terminal
/// (i.e. reporting events).
pub fn mouseCaptured(self: *Surface) bool {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
return self.io.terminal.flags.mouse_event != .none;
}
@ -3774,19 +3785,13 @@ pub fn mouseButtonCallback(
// If we are within the interval that the click would register
// an increment then we do not extend the selection.
if (std.time.Instant.now()) |now| {
const since = now.since(self.mouse.left_click_time);
if (since <= self.config.mouse_interval) {
// Click interval very short, we may be increasing
// click counts so we don't extend the selection.
break :extend_selection;
}
} else |err| {
// This is a weird behavior, I think either behavior is actually
// fine. This failure should be exceptionally rare anyways.
// My thinking here is that we can't be sure if we should extend
// the selection or not so we just don't.
log.warn("failed to get time, not extending selection err={}", .{err});
const since = self.mouse.left_click_time.untilNow(
std.Io.Threaded.global_single_threaded.io(),
.awake,
).nanoseconds;
if (since <= self.config.mouse_interval) {
// Click interval very short, we may be increasing
// click counts so we don't extend the selection.
break :extend_selection;
}
@ -3810,8 +3815,8 @@ pub fn mouseButtonCallback(
// the left button is released. This is to avoid the clipboard
// being updated on every mouse move which would be noisy.
if (self.config.copy_on_select != .false) {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const prev_ = self.io.terminal.screens.active.selection;
if (prev_) |prev| {
try self.setSelection(terminal.Selection.init(
@ -3827,8 +3832,8 @@ pub fn mouseButtonCallback(
// clicked link will swallow the event.
if (self.mouse.over_link) {
const pos = try self.rt_surface.getCursorPos();
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (self.processLinks(pos)) |processed| {
if (processed) return true;
} else |err| {
@ -3848,8 +3853,8 @@ pub fn mouseButtonCallback(
// Report mouse events if enabled
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (self.isMouseReporting()) report: {
// If we have shift-pressed and we aren't allowed to capture it,
// then we do not do a mouse report.
@ -3888,8 +3893,8 @@ pub fn mouseButtonCallback(
// For left button clicks we always record some information for
// selection/highlighting purposes.
if (button == .left and action == .press) click: {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
const screen: *terminal.Screen = self.renderer_state.terminal.screens.active;
@ -3940,26 +3945,22 @@ pub fn mouseButtonCallback(
self.mouse.left_click_ypos = pos.y;
// Setup our click counter and timer
if (std.time.Instant.now()) |now| {
// If we have mouse clicks, then we check if the time elapsed
// is less than and our interval and if so, increase the count.
if (self.mouse.left_click_count > 0) {
const since = now.since(self.mouse.left_click_time);
if (since > self.config.mouse_interval) {
self.mouse.left_click_count = 0;
}
const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
// If we have mouse clicks, then we check if the time elapsed
// is less than and our interval and if so, increase the count.
if (self.mouse.left_click_count > 0) {
const since = self.mouse.left_click_time.durationTo(now);
if (since.nanoseconds > self.config.mouse_interval) {
self.mouse.left_click_count = 0;
}
self.mouse.left_click_time = now;
self.mouse.left_click_count += 1;
// We only support up to triple-clicks.
if (self.mouse.left_click_count > 3) self.mouse.left_click_count = 1;
} else |err| {
self.mouse.left_click_count = 1;
log.err("error reading time, mouse multi-click won't work err={}", .{err});
}
self.mouse.left_click_time = now;
self.mouse.left_click_count += 1;
// We only support up to triple-clicks.
if (self.mouse.left_click_count > 3) self.mouse.left_click_count = 1;
// In all cases below, we set the selection directly rather than use
// `setSelection` because we want to avoid copying the selection
// to the selection clipboard. For left mouse clicks we only set
@ -4040,8 +4041,8 @@ pub fn mouseButtonCallback(
// want to be careful in the future we can add a function to apprts
// that let's us know.
if (button == .right and action == .press) sel: {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// Get our viewport pin
const screen: *terminal.Screen = self.renderer_state.terminal.screens.active;
@ -4112,8 +4113,8 @@ pub fn mouseButtonCallback(
} else {
// Pasting can trigger a lock grab in complete clipboard
// request so we need to unlock.
self.renderer_state.mutex.unlock();
defer self.renderer_state.mutex.lock();
self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
_ = try self.startClipboardRequest(.standard, .paste);
// We don't need to clear selection because we didn't have
@ -4127,8 +4128,8 @@ pub fn mouseButtonCallback(
// Pasting can trigger a lock grab in complete clipboard
// request so we need to unlock.
self.renderer_state.mutex.unlock();
defer self.renderer_state.mutex.lock();
self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
_ = try self.startClipboardRequest(.standard, .paste);
},
}
@ -4141,8 +4142,8 @@ pub fn mouseButtonCallback(
}
fn maybePromptClick(self: *Surface) !bool {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
const screen: *terminal.Screen = t.screens.active;
@ -4461,8 +4462,8 @@ pub fn mousePressureCallback(
if (self.mouse.click_state[left_idx] == .press and
stage == .deep)
select: {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// This should always be set in this state but we don't want
// to handle state inconsistency here.
@ -4520,8 +4521,8 @@ pub fn cursorPosCallback(
try self.queueRender();
}
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// No mouse point so we don't highlight links
self.renderer_state.mouse.point = null;
@ -4547,8 +4548,8 @@ pub fn cursorPosCallback(
self.mouse.over_link = false;
// We are reading/writing state for the remainder
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// Stop selection scrolling when inside the viewport within a 1px buffer
// for fullscreen windows, but only when selection scrolling is active.
@ -5041,8 +5042,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
// CSI/ESC triggers a scroll.
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
self.scrollToBottom() catch |err| {
log.warn("error scrolling to bottom err={}", .{err});
};
@ -5068,8 +5069,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
// Text triggers a scroll.
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
self.scrollToBottom() catch |err| {
log.warn("error scrolling to bottom err={}", .{err});
};
@ -5081,8 +5082,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
// in cursor keys mode. We're in "normal" mode if cursor
// keys mode is NOT set.
const normal = normal: {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// With the lock held, we must scroll to the bottom.
// We always scroll to the bottom for these inputs.
@ -5101,8 +5102,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
},
.reset => {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
self.renderer_state.terminal.fullReset();
},
@ -5172,7 +5173,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
terminal.search.Thread.threadMain,
.{&s.state},
);
s.thread.setName("search") catch {};
s.thread.setName(std.Io.Threaded.global_single_threaded.io(), "search") catch {};
break :init s;
};
@ -5207,8 +5208,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
},
.copy_to_clipboard => |format| {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (self.io.terminal.screens.active.selection) |sel| {
try self.copySelectionToClipboards(
@ -5239,8 +5240,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
if (!self.mouse.over_link) return false;
const pos = try self.rt_surface.getCursorPos();
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (try self.linkAtPos(pos)) |link_info| {
const url_text = switch (link_info.action) {
.open => url_text: {
@ -5385,8 +5386,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
// alternate screen then clear screen does nothing so we want to
// return false so the keybind can be unconsumed.
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (self.io.terminal.screens.active_key == .alternate) return false;
}
@ -5409,8 +5410,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.scroll_to_row => |n| {
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
t.screens.active.scroll(.{ .row = n });
}
@ -5420,8 +5421,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.scroll_to_selection => {
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const sel = self.io.terminal.screens.active.selection orelse return false;
const tl = sel.topLeft(self.io.terminal.screens.active);
self.io.terminal.screens.active.scroll(.{ .pin = tl });
@ -5659,8 +5660,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
),
.select_all => {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const sel = self.io.terminal.screens.active.selectAll();
if (sel) |s| {
@ -5792,8 +5793,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
},
.adjust_selection => |direction| {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const screen: *terminal.Screen = self.io.terminal.screens.active;
const sel = if (screen.selection) |*sel| sel else {
@ -5889,23 +5890,24 @@ fn writeScreenFile(
// Open our scrollback file
var file = try tmp_dir.dir.createFile(
std.Io.Threaded.global_single_threaded.io(),
filename,
switch (builtin.os.tag) {
.windows => .{},
else => .{ .mode = 0o600 },
else => .{ .permissions = .fromMode(0o600) },
},
);
defer file.close();
defer file.close(std.Io.Threaded.global_single_threaded.io());
// Screen.dumpString writes byte-by-byte, so buffer it
var buf: [4096]u8 = undefined;
var file_writer = file.writer(&buf);
var file_writer = file.writer(std.Io.Threaded.global_single_threaded.io(), &buf);
var buf_writer = &file_writer.interface;
// Write the scrollback contents. This requires a lock.
{
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// We only dump history if we have history. We still keep
// the file and write the empty file to the pty so that this
@ -5968,7 +5970,11 @@ fn writeScreenFile(
// Get the final path
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try tmp_dir.dir.realpath(filename, &path_buf);
const path = path_buf[0..try tmp_dir.dir.realPathFile(
std.Io.Threaded.global_single_threaded.io(),
filename,
&path_buf,
)];
switch (write_screen.action) {
.copy => {
@ -6066,8 +6072,8 @@ fn completeClipboardPaste(
if (data.len == 0) return;
const encode_opts: input.paste.Options = encode_opts: {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const opts: input.paste.Options = .fromTerminal(&self.io.terminal);
// If we have paste protection enabled, we detect unsafe pastes and return
@ -6187,12 +6193,12 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const
// how fast identical notifications can be sent sequentially.
const hash_algorithm = std.hash.Wyhash;
const now = try std.time.Instant.now();
const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
// Set a limit of one desktop notification per second so that the OS
// doesn't kill us when we run out of resources.
if (self.app.last_notification_time) |last| {
if (now.since(last) < 1 * std.time.ns_per_s) {
if (last.durationTo(now).toSeconds() < 1) {
log.warn("rate limiting desktop notifications", .{});
return;
}
@ -6209,7 +6215,7 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const
// notifications with identical content.
if (self.app.last_notification_time) |last| {
if (self.app.last_notification_digest == new_digest) {
if (now.since(last) < 5 * std.time.ns_per_s) {
if (last.durationTo(now).toSeconds() < 5) {
log.warn("suppressing identical desktop notification", .{});
return;
}

View File

@ -419,8 +419,11 @@ pub const Action = union(Key) {
/// Sync with: ghostty_action_u
pub const CValue = cvalue: {
const key_fields = @typeInfo(Key).@"enum".fields;
var union_fields: [key_fields.len]std.builtin.Type.UnionField = undefined;
for (key_fields, 0..) |field, i| {
var names: [key_fields.len][]const u8 = undefined;
var types: [key_fields.len]type = undefined;
var attrs: [key_fields.len]std.builtin.Type.UnionField.Attributes = undefined;
for (key_fields, &names, &types, &attrs) |field, *name, *ty, *attr| {
const action = @unionInit(Action, field.name, undefined);
const Type = t: {
const Type = @TypeOf(@field(action, field.name));
@ -429,19 +432,12 @@ pub const Action = union(Key) {
break :t Type;
};
union_fields[i] = .{
.name = field.name,
.type = Type,
.alignment = @alignOf(Type),
};
name.* = field.name;
ty.* = Type;
attr.* = .{ .@"align" = @alignOf(Type) };
}
break :cvalue @Type(.{ .@"union" = .{
.layout = .@"extern",
.tag_type = null,
.fields = &union_fields,
.decls = &.{},
} });
break :cvalue @Union(.@"extern", null, &names, &types, &attrs);
};
/// Sync with: ghostty_action_s
@ -1003,5 +999,5 @@ pub const SearchSelected = struct {
};
test {
_ = std.testing.refAllDeclsRecursive(@This());
_ = std.testing.refAllDecls(@This());
}

View File

@ -949,7 +949,7 @@ pub const Surface = struct {
};
}
pub fn defaultTermioEnv(self: *const Surface) !std.process.EnvMap {
pub fn defaultTermioEnv(self: *const Surface) !std.process.Environ.Map {
const alloc = self.app.core_app.alloc;
var env = try internal_os.getEnvMap(alloc);
errdefer env.deinit();
@ -999,7 +999,7 @@ pub const Inspector = struct {
content_scale: f64 = 1,
/// Our previous instant used to calculate delta time for animations.
instant: ?std.time.Instant = null,
instant: ?std.Io.Timestamp = null,
const Backend = enum {
metal,
@ -1228,7 +1228,7 @@ pub const Inspector = struct {
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
// Determine our delta time
const now = try std.time.Instant.now();
const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
io.DeltaTime = if (self.instant) |prev| delta: {
const since_ns: f64 = @floatFromInt(now.since(prev));
const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s);
@ -1611,8 +1611,8 @@ pub const CAPI = struct {
result: *Text,
) bool {
const core_surface = &surface.core_surface;
core_surface.renderer_state.mutex.lock();
defer core_surface.renderer_state.mutex.unlock();
core_surface.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer core_surface.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// If we don't have a selection, do nothing.
const core_sel = core_surface.io.terminal.screens.active.selection orelse return false;
@ -1631,8 +1631,8 @@ pub const CAPI = struct {
sel: Selection,
result: *Text,
) bool {
surface.core_surface.renderer_state.mutex.lock();
defer surface.core_surface.renderer_state.mutex.unlock();
surface.core_surface.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer surface.core_surface.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
const core_sel = sel.core(
surface.core_surface.renderer_state.terminal.screens.active,
@ -2194,8 +2194,8 @@ pub const CAPI = struct {
result: *Text,
) bool {
const surface = &ptr.core_surface;
surface.renderer_state.mutex.lock();
defer surface.renderer_state.mutex.unlock();
surface.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer surface.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// Get our word selection
const sel = sel: {

View File

@ -94,7 +94,7 @@ pub fn setClipboard(
);
}
pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap {
pub fn defaultTermioEnv(self: *Self) !std.process.Environ.Map {
return try self.surface.defaultTermioEnv();
}

View File

@ -4,18 +4,16 @@ const std = @import("std");
// Ghostty, we need to import `adwaita.h` directly to ensure that the version
// macros match the version of `libadwaita` that we are building/linking
// against.
const c = @cImport({
@cInclude("adwaita.h");
});
const adw_c = @import("adw_c");
const adw = @import("adw");
const log = std.log.scoped(.gtk);
pub const comptime_version: std.SemanticVersion = .{
.major = c.ADW_MAJOR_VERSION,
.minor = c.ADW_MINOR_VERSION,
.patch = c.ADW_MICRO_VERSION,
.major = adw_c.ADW_MAJOR_VERSION,
.minor = adw_c.ADW_MINOR_VERSION,
.patch = adw_c.ADW_MICRO_VERSION,
};
pub fn getRuntimeVersion() std.SemanticVersion {
@ -89,14 +87,14 @@ test "versionAtLeast" {
const funs = &.{ atLeast, runtimeAtLeast };
inline for (funs) |fun| {
try testing.expect(fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION));
try testing.expect(!fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION + 1));
try testing.expect(!fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION + 1, c.ADW_MICRO_VERSION));
try testing.expect(!fun(c.ADW_MAJOR_VERSION + 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION));
try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION));
try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION + 1, c.ADW_MICRO_VERSION));
try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION + 1));
try testing.expect(fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION - 1, c.ADW_MICRO_VERSION + 1));
try testing.expect(fun(adw_c.ADW_MAJOR_VERSION, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION));
try testing.expect(!fun(adw_c.ADW_MAJOR_VERSION, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION + 1));
try testing.expect(!fun(adw_c.ADW_MAJOR_VERSION, adw_c.ADW_MINOR_VERSION + 1, adw_c.ADW_MICRO_VERSION));
try testing.expect(!fun(adw_c.ADW_MAJOR_VERSION + 1, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION));
try testing.expect(fun(adw_c.ADW_MAJOR_VERSION - 1, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION));
try testing.expect(fun(adw_c.ADW_MAJOR_VERSION - 1, adw_c.ADW_MINOR_VERSION + 1, adw_c.ADW_MICRO_VERSION));
try testing.expect(fun(adw_c.ADW_MAJOR_VERSION - 1, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION + 1));
try testing.expect(fun(adw_c.ADW_MAJOR_VERSION, adw_c.ADW_MINOR_VERSION - 1, adw_c.ADW_MICRO_VERSION + 1));
}
}

View File

@ -6,10 +6,7 @@
//! Example: blueprint.zig 1 5 output.ui input.blp
const std = @import("std");
pub const c = @cImport({
@cInclude("adwaita.h");
});
const adw_c = @import("adw_c");
pub const blueprint_compiler_help =
\\
@ -26,23 +23,24 @@ pub const blueprint_compiler_help =
;
const adwaita_version = std.SemanticVersion{
.major = c.ADW_MAJOR_VERSION,
.minor = c.ADW_MINOR_VERSION,
.patch = c.ADW_MICRO_VERSION,
.major = adw_c.ADW_MAJOR_VERSION,
.minor = adw_c.ADW_MINOR_VERSION,
.patch = adw_c.ADW_MICRO_VERSION,
};
const required_blueprint_version = std.SemanticVersion{
.major = 0,
.minor = 16,
.patch = 0,
};
pub fn main() !void {
pub fn main(init: std.process.Init) !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_allocator.deinit();
const alloc = debug_allocator.allocator();
// Get our args
var it = try std.process.argsWithAllocator(alloc);
var it = try init.minimal.args.iterateAllocator(alloc);
defer it.deinit();
_ = it.next(); // Skip argv0
const arg_major = it.next() orelse return error.NoMajorVersion;
@ -63,51 +61,37 @@ pub fn main() !void {
\\compile this blueprint. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty.
, .{required_adwaita_version});
std.posix.exit(1);
std.process.exit(1);
}
// Version checks
{
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",
"--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) {
const blueprint_compiler = std.process.run(alloc, init.io, .{
.argv = &.{ "blueprint-compiler", "--version" },
}) catch |err| switch (err) {
error.FileNotFound => {
std.debug.print(
\\`blueprint-compiler` not found.
++ blueprint_compiler_help,
.{required_blueprint_version},
);
std.posix.exit(1);
std.process.exit(1);
},
else => return err,
};
switch (term) {
.Exited => |rc| if (rc != 0) std.process.exit(1),
defer {
alloc.free(blueprint_compiler.stdout);
alloc.free(blueprint_compiler.stderr);
}
switch (blueprint_compiler.term) {
.exited => |rc| if (rc != 0) std.process.exit(1),
else => std.process.exit(1),
}
const version = try std.SemanticVersion.parse(std.mem.trim(
u8,
stdout.items,
blueprint_compiler.stdout,
&std.ascii.whitespace,
));
if (version.order(required_blueprint_version) == .lt) {
@ -116,57 +100,45 @@ pub fn main() !void {
++ blueprint_compiler_help,
.{required_blueprint_version},
);
std.posix.exit(1);
std.process.exit(1);
}
}
// Compilation
{
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(
&.{
const blueprint_compiler = std.process.run(alloc, init.io, .{
.argv = &.{
"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) {
}) catch |err| switch (err) {
error.FileNotFound => {
std.debug.print(
\\`blueprint-compiler` not found.
++ blueprint_compiler_help,
.{required_blueprint_version},
);
std.posix.exit(1);
std.process.exit(1);
},
else => return err,
};
defer {
alloc.free(blueprint_compiler.stdout);
alloc.free(blueprint_compiler.stderr);
}
switch (term) {
.Exited => |rc| {
switch (blueprint_compiler.term) {
.exited => |rc| {
if (rc != 0) {
std.debug.print("{s}", .{stderr.items});
std.debug.print("{s}", .{blueprint_compiler.stderr});
std.process.exit(1);
}
},
else => {
std.debug.print("{s}", .{stderr.items});
std.debug.print("{s}", .{blueprint_compiler.stderr});
std.process.exit(1);
},
}

View File

@ -122,18 +122,17 @@ pub fn blueprint(comptime bp: Blueprint) [:0]const u8 {
}
}
pub fn main() !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer _ = debug_allocator.deinit();
const alloc = debug_allocator.allocator();
pub fn main(init: std.process.Init) !void {
const alloc = init.arena.allocator();
// Collect the UI files that are passed in as arguments.
var ui_files: std.ArrayListUnmanaged([]const u8) = .empty;
var ui_files: std.ArrayList([]const u8) = .empty;
defer {
for (ui_files.items) |item| alloc.free(item);
ui_files.deinit(alloc);
}
var it = try std.process.argsWithAllocator(alloc);
var it = try init.minimal.args.iterateAllocator(alloc);
defer it.deinit();
while (it.next()) |arg| {
if (!std.mem.endsWith(u8, arg, ".ui")) continue;
@ -144,7 +143,7 @@ pub fn main() !void {
}
var buf: [4096]u8 = undefined;
var stdout = std.fs.File.stdout().writer(&buf);
var stdout = std.Io.File.stdout().writer(init.io, &buf);
const writer = &stdout.interface;
try writer.writeAll(
\\<?xml version="1.0" encoding="UTF-8"?>
@ -152,8 +151,8 @@ pub fn main() !void {
\\
);
try genRoot(writer);
try genIcons(writer);
try genRoot(init.io, writer);
try genIcons(init.io, writer);
try genUi(alloc, writer, &ui_files);
try writer.writeAll(
@ -167,19 +166,19 @@ pub fn main() !void {
/// 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: *std.Io.Writer) !void {
fn genIcons(io: std.Io, writer: *std.Io.Writer) !void {
try writer.print(
\\ <gresource prefix="{s}/icons">
\\
, .{build_info.resource_path});
const cwd = std.fs.cwd();
const cwd: std.Io.Dir = .cwd();
inline for (icon_sizes) |size| {
// 1x
{
const alias = std.fmt.comptimePrint("{d}x{d}", .{ size, size });
const source = std.fmt.comptimePrint("images/gnome/{d}.png", .{size});
try cwd.access(source, .{});
try cwd.access(io, source, .{});
try writer.print(
\\ <file alias="{s}/apps/{s}.png">{s}</file>
\\
@ -192,7 +191,7 @@ fn genIcons(writer: *std.Io.Writer) !void {
{
const alias = std.fmt.comptimePrint("{d}x{d}@2", .{ size, size });
const source = std.fmt.comptimePrint("images/gnome/{d}.png", .{size * 2});
try cwd.access(source, .{});
try cwd.access(io, source, .{});
try writer.print(
\\ <file alias="{s}/apps/{s}.png">{s}</file>
\\
@ -209,19 +208,19 @@ fn genIcons(writer: *std.Io.Writer) !void {
}
/// Generate the resources at the root prefix.
fn genRoot(writer: *std.Io.Writer) !void {
fn genRoot(io: std.Io, writer: *std.Io.Writer) !void {
try writer.print(
\\ <gresource prefix="{s}">
\\
, .{build_info.resource_path});
const cwd = std.fs.cwd();
const cwd: std.Io.Dir = .cwd();
inline for (css) |name| {
const source = std.fmt.comptimePrint(
"{s}/{s}",
.{ css_path, name },
);
try cwd.access(source, .{});
try cwd.access(io, source, .{});
try writer.print(
\\ <file compressed="true" alias="{s}">{s}</file>
\\
@ -242,7 +241,7 @@ fn genRoot(writer: *std.Io.Writer) !void {
fn genUi(
alloc: Allocator,
writer: *std.Io.Writer,
files: *const std.ArrayListUnmanaged([]const u8),
files: *const std.ArrayList([]const u8),
) !void {
try writer.print(
\\ <gresource prefix="{s}/ui">

View File

@ -145,16 +145,24 @@ pub fn Common(
/// as the virtual method but the self parameter points to the
/// target instead of the original class.
fn ImplementFunc(comptime T: type) type {
var params: [fn_info.params.len]std.builtin.Type.Fn.Param = undefined;
@memcpy(&params, fn_info.params);
params[0].type = *ClassInstance(T);
return @Type(.{ .@"fn" = .{
.calling_convention = fn_info.calling_convention,
.is_generic = fn_info.is_generic,
.is_var_args = fn_info.is_var_args,
.return_type = fn_info.return_type,
.params = &params,
} });
var types: [fn_info.params.len]type = undefined;
var attrs: [fn_info.params.len]std.builtin.Type.Fn.Param.Attributes = undefined;
for (fn_info.params, &types, &attrs) |info, *ty, *attr| {
ty.* = info.type.?;
attr.* = .{ .@"noalias" = info.is_noalias };
}
types[0] = *ClassInstance(T);
return @Fn(
&types,
&attrs,
fn_info.return_type.?,
.{
.@"callconv" = fn_info.calling_convention,
.varargs = fn_info.is_var_args,
},
);
}
};
}

View File

@ -14,6 +14,7 @@ const state = &@import("../../../global.zig").state;
const i18n = @import("../../../os/main.zig").i18n;
const apprt = @import("../../../apprt.zig");
const CoreApp = @import("../../../App.zig");
const compat_file = @import("../../../lib/compat/file.zig");
const configpkg = @import("../../../config.zig");
const input = @import("../../../input.zig");
const internal_os = @import("../../../os/main.zig");
@ -294,7 +295,7 @@ pub const Application = extern struct {
// Setup our GTK init env vars
setGtkEnv(&config) catch |err| switch (err) {
error.NoSpaceLeft => {
error.WriteFailed => {
// If we fail to set GTK environment variables then we still
// try to start the application...
log.warn(
@ -338,7 +339,7 @@ pub const Application = extern struct {
// I'm unsure of any scenario where this happens. Because we don't
// want to litter null checks everywhere, we just exit here.
log.warn("gdk display is null, exiting", .{});
std.posix.exit(1);
std.process.exit(1);
};
// Setup our windowing protocol logic
@ -1060,7 +1061,11 @@ pub const Application = extern struct {
}
}
fn loadCustomCss(self: *Self) (std.fs.File.ReadError || Allocator.Error)!void {
const LoadCustomCssError = std.Io.File.OpenError ||
compat_file.ReadToEndAllocError ||
std.mem.Allocator.Error;
fn loadCustomCss(self: *Self) LoadCustomCssError!void {
const priv: *Private = self.private();
const alloc = self.allocator();
const display = gdk.Display.getDefault() orelse {
@ -1084,7 +1089,11 @@ pub const Application = extern struct {
.optional => |path| .{ path, true },
.required => |path| .{ path, false },
};
const file = std.fs.openFileAbsolute(path, .{}) catch |err| {
const file = std.Io.Dir.openFileAbsolute(
std.Io.Threaded.global_single_threaded.io(),
path,
.{},
) catch |err| {
if (err != error.FileNotFound or !optional) {
log.warn(
"error opening gtk-custom-css file {s}: {}",
@ -1093,12 +1102,14 @@ pub const Application = extern struct {
}
continue;
};
defer file.close();
defer file.close(std.Io.Threaded.global_single_threaded.io());
const css_file_size_limit = 5 * 1024 * 1024; // 5MB
log.info("loading gtk-custom-css path={s}", .{path});
const contents = file.readToEndAlloc(
const contents = compat_file.readToEndAlloc(
file,
alloc,
css_file_size_limit,
) catch |err| switch (err) {
@ -1108,6 +1119,7 @@ pub const Application = extern struct {
},
else => |e| return e,
};
defer alloc.free(contents);
const bytes = glib.Bytes.new(contents.ptr, contents.len);
@ -1398,7 +1410,7 @@ pub const Application = extern struct {
const priv = self.private();
assert(priv.signal_source == null);
priv.signal_source = glib.unixSignalAdd(
std.posix.SIG.USR2,
@intFromEnum(std.posix.SIG.USR2),
handleSigusr2,
self,
);
@ -1851,11 +1863,8 @@ pub const Application = extern struct {
fn init(class: *Class) callconv(.c) void {
// Register our compiled resources exactly once.
{
const c = @cImport({
// generated header files
@cInclude("ghostty_resources.h");
});
if (c.ghostty_get_resource()) |ptr| {
const ghostty_gtk_resources = @import("ghostty_gtk_resources");
if (ghostty_gtk_resources.ghostty_get_resource()) |ptr| {
gio.resourcesRegister(@ptrCast(@alignCast(ptr)));
} else {
// If we fail to load resources then things will
@ -2831,7 +2840,7 @@ const Action = struct {
/// given the runtime environment or configuration.
///
/// This must be called BEFORE GTK initialization.
fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void {
fn setGtkEnv(config: *const CoreConfig) std.Io.Writer.Error!void {
assert(gtk.isInitialized() == 0);
var gdk_debug: struct {
@ -2900,8 +2909,7 @@ fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void {
{
var buf: [1024]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
const writer = fmt.writer();
var writer: std.Io.Writer = .fixed(&buf);
var first: bool = true;
inline for (@typeInfo(@TypeOf(gdk_debug)).@"struct".fields) |field| {
if (@field(gdk_debug, field.name)) {
@ -2911,15 +2919,14 @@ fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void {
}
}
try writer.writeByte(0);
const value = fmt.getWritten();
const value = writer.buffered();
log.warn("setting GDK_DEBUG={s}", .{value[0 .. value.len - 1]});
_ = internal_os.setenv("GDK_DEBUG", value[0 .. value.len - 1 :0]);
}
{
var buf: [1024]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
const writer = fmt.writer();
var writer: std.Io.Writer = .fixed(&buf);
var first: bool = true;
inline for (@typeInfo(@TypeOf(gdk_disable)).@"struct".fields) |field| {
if (@field(gdk_disable, field.name)) {
@ -2929,7 +2936,7 @@ fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void {
}
}
try writer.writeByte(0);
const value = fmt.getWritten();
const value = writer.buffered();
log.warn("setting GDK_DISABLE={s}", .{value[0 .. value.len - 1]});
_ = internal_os.setenv("GDK_DISABLE", value[0 .. value.len - 1 :0]);
}

View File

@ -153,7 +153,7 @@ pub const CommandPalette = extern struct {
priv.source.removeAll();
const alloc = Application.default().allocator();
var commands: std.ArrayList(*Command) = .{};
var commands: std.ArrayList(*Command) = .empty;
defer {
for (commands.items) |cmd| cmd.unref();
commands.deinit(alloc);

View File

@ -628,6 +628,9 @@ fn generateToken(buf: *Token) [:0]const u8 {
return std.fmt.bufPrintZ(
buf,
"ghostty_{x:0<7}",
.{std.crypto.random.int(u28)},
.{rand_int: {
const rng_impl: std.Random.IoSource = .{ .io = std.Io.Threaded.global_single_threaded.io() };
break :rand_int rng_impl.interface().int(u28);
}},
) catch unreachable;
}

View File

@ -61,13 +61,13 @@ pub const ImguiWidget = extern struct {
ig_context: ?*cimgui.c.ImGuiContext = null,
/// Our previous instant used to calculate delta time for animations.
instant: ?std.time.Instant = null,
instant: ?std.Io.Timestamp = null,
/// Tick callback ID for timed updates.
tick_callback_id: c_uint = 0,
/// Last render time for throttling to 30 FPS.
last_render_time: ?std.time.Instant = null,
last_render_time: ?std.Io.Timestamp = null,
pub var offset: c_int = 0;
};
@ -140,10 +140,11 @@ pub const ImguiWidget = extern struct {
const priv = self.private();
const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO();
const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
// Determine our delta time
const now = std.time.Instant.now() catch unreachable;
io.DeltaTime = if (priv.instant) |prev| delta: {
const since_ns: f64 = @floatFromInt(now.since(prev));
const since_ns: f64 = @floatFromInt(prev.durationTo(now).nanoseconds);
const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s);
const since_s: f32 = @floatCast(since_ns / ns_per_s);
break :delta @max(0.00001, since_s);
@ -298,7 +299,7 @@ pub const ImguiWidget = extern struct {
// Update last render time for tick callback throttling.
const priv = self.private();
priv.last_render_time = std.time.Instant.now() catch null;
priv.last_render_time = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
// Setup our frame. We render twice because some ImGui behaviors
// take multiple renders to process. I don't know how to make this
@ -457,15 +458,10 @@ pub const ImguiWidget = extern struct {
const self: *Self = gobject.ext.cast(Self, widget) orelse return 0;
const priv = self.private();
const now = std.time.Instant.now() catch {
self.queueRender();
return 1;
};
// Throttle to 30 FPS (~33ms between frames)
const frame_time_ns: u64 = std.time.ns_per_s / 30;
const should_render = if (priv.last_render_time) |last|
now.since(last) >= frame_time_ns
last.untilNow(std.Io.Threaded.global_single_threaded.io(), .awake).nanoseconds >= frame_time_ns
else
true;

View File

@ -1561,7 +1561,7 @@ pub const Surface = extern struct {
return self.private().cursor_pos;
}
pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap {
pub fn defaultTermioEnv(self: *Self) !std.process.Environ.Map {
const app = Application.default();
const alloc = app.allocator();
var env = try internal_os.getEnvMap(alloc);
@ -1570,23 +1570,23 @@ pub const Surface = extern struct {
if (app.savedLanguage()) |language| {
try env.put("LANG", language);
} else {
env.remove("LANG");
_ = env.orderedRemove("LANG");
}
// Don't leak these GTK environment variables to child processes.
env.remove("GDK_DEBUG");
env.remove("GDK_DISABLE");
env.remove("GSK_RENDERER");
_ = env.orderedRemove("GDK_DEBUG");
_ = env.orderedRemove("GDK_DISABLE");
_ = env.orderedRemove("GSK_RENDERER");
// Remove some environment variables that are set when Ghostty is launched
// from a `.desktop` file, by D-Bus activation, or systemd.
env.remove("GIO_LAUNCHED_DESKTOP_FILE");
env.remove("GIO_LAUNCHED_DESKTOP_FILE_PID");
env.remove("DBUS_STARTER_ADDRESS");
env.remove("DBUS_STARTER_BUS_TYPE");
env.remove("INVOCATION_ID");
env.remove("JOURNAL_STREAM");
env.remove("NOTIFY_SOCKET");
_ = env.orderedRemove("GIO_LAUNCHED_DESKTOP_FILE");
_ = env.orderedRemove("GIO_LAUNCHED_DESKTOP_FILE_PID");
_ = env.orderedRemove("DBUS_STARTER_ADDRESS");
_ = env.orderedRemove("DBUS_STARTER_BUS_TYPE");
_ = env.orderedRemove("INVOCATION_ID");
_ = env.orderedRemove("JOURNAL_STREAM");
_ = env.orderedRemove("NOTIFY_SOCKET");
// Unset environment varies set by snaps if we're running in a snap.
// This allows Ghostty to further launch additional snaps.
@ -1613,7 +1613,7 @@ pub const Surface = extern struct {
}
/// Filter out environment variables that start with forbidden prefixes.
fn filterSnapPaths(gpa: std.mem.Allocator, env_map: *std.process.EnvMap) !void {
fn filterSnapPaths(gpa: std.mem.Allocator, env_map: *std.process.Environ.Map) !void {
comptime assert(build_config.snap);
const snap_vars = [_][]const u8{

View File

@ -176,7 +176,11 @@ test "adding actions to an object" {
_ = addAsGroup(gtk.Box, box, "test", &actions);
}
const expected = std.crypto.random.intRangeAtMost(i32, 1, std.math.maxInt(u31));
const expected = expected: {
const rng_impl: std.Random.IoSource = .{ .io = testing.io };
const rng = rng_impl.interface();
break :expected rng.intRangeAtMost(i32, 1, std.math.maxInt(u31));
};
const parameter = glib.Variant.newInt32(expected);
try testing.expect(box.as(gtk.Widget).activateActionVariant("test.test", parameter) != 0);

View File

@ -3,18 +3,16 @@ const std = @import("std");
// Until the gobject bindings are built at the same time we are building
// Ghostty, we need to import `gtk/gtk.h` directly to ensure that the version
// macros match the version of `gtk4` that we are building/linking against.
const c = @cImport({
@cInclude("gtk/gtk.h");
});
const gtk_c = @import("gtk_c");
const gtk = @import("gtk");
const log = std.log.scoped(.gtk);
pub const comptime_version: std.SemanticVersion = .{
.major = c.GTK_MAJOR_VERSION,
.minor = c.GTK_MINOR_VERSION,
.patch = c.GTK_MICRO_VERSION,
.major = gtk_c.GTK_MAJOR_VERSION,
.minor = gtk_c.GTK_MINOR_VERSION,
.patch = gtk_c.GTK_MICRO_VERSION,
};
pub fn getRuntimeVersion() std.SemanticVersion {
@ -105,17 +103,17 @@ test "atLeast" {
const funs = &.{ atLeast, runtimeAtLeast };
inline for (funs) |fun| {
try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION));
try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION));
try testing.expect(!fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION + 1));
try testing.expect(!fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION + 1, c.GTK_MICRO_VERSION));
try testing.expect(!fun(c.GTK_MAJOR_VERSION + 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION));
try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION + 1));
try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION + 1, gtk_c.GTK_MICRO_VERSION));
try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION + 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION));
try testing.expect(fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION));
try testing.expect(fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION + 1, c.GTK_MICRO_VERSION));
try testing.expect(fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION + 1));
try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION));
try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION + 1, gtk_c.GTK_MICRO_VERSION));
try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION + 1));
try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION - 1, c.GTK_MICRO_VERSION + 1));
try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION - 1, gtk_c.GTK_MICRO_VERSION + 1));
}
}
@ -125,16 +123,16 @@ test "runtimeUntil" {
// This is an array in case we add a comptime variant.
const funs = &.{runtimeUntil};
inline for (funs) |fun| {
try testing.expect(!fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION));
try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION));
try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION + 1));
try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION + 1, c.GTK_MICRO_VERSION));
try testing.expect(fun(c.GTK_MAJOR_VERSION + 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION));
try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION + 1));
try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION + 1, gtk_c.GTK_MICRO_VERSION));
try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION + 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION));
try testing.expect(!fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION));
try testing.expect(!fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION + 1, c.GTK_MICRO_VERSION));
try testing.expect(!fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION + 1));
try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION));
try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION + 1, gtk_c.GTK_MICRO_VERSION));
try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION + 1));
try testing.expect(!fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION - 1, c.GTK_MICRO_VERSION + 1));
try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION - 1, gtk_c.GTK_MICRO_VERSION + 1));
}
}

View File

@ -28,10 +28,12 @@ payload_builder: *glib.VariantBuilder,
/// Used to build the parameters for the IPC.
parameters_builder: *glib.VariantBuilder,
pub const InitError = Allocator.Error || std.Io.Writer.Error || apprt.ipc.Errors;
/// Initialize the helper.
pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (Allocator.Error || std.Io.Writer.Error || apprt.ipc.Errors)!Self {
pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) InitError!Self {
var buf: [256]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&buf);
var stderr_writer = std.Io.File.stderr().writer(std.Io.Threaded.global_single_threaded.io(), &buf);
const stderr = &stderr_writer.interface;
// Get the appropriate bus name and object path for contacting the
@ -133,7 +135,7 @@ pub fn addParameter(self: *Self, variant: *glib.Variant) void {
/// should be done with this object other than call `deinit`.
pub fn send(self: *Self) (std.Io.Writer.Error || apprt.ipc.Errors)!void {
var buf: [256]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&buf);
var stderr_writer = std.Io.File.stderr().writer(std.Io.Threaded.global_single_threaded.io(), &buf);
const stderr = &stderr_writer.interface;
// finish building the parameters

View File

@ -10,7 +10,11 @@ const gtk = @import("gtk");
pub fn fromFilename(path: [:0]const u8) ?*gtk.MediaFile {
assert(std.fs.path.isAbsolute(path));
std.fs.accessAbsolute(path, .{ .mode = .read_only }) catch |err| {
std.Io.Dir.accessAbsolute(
std.Io.Threaded.global_single_threaded.io(),
path,
.{ .read = true },
) catch |err| {
log.warn("unable to access {s}: {t}", .{ path, err });
return null;
};

View File

@ -11,7 +11,8 @@ const token_format = std.fmt.comptimePrint("{{x:0>{}}}", .{token_hex_len});
/// Generate a token suitable for use in requests to the XDG Desktop Portal
pub fn generateToken() usize {
return std.crypto.random.int(usize);
const rng_impl: std.Random.IoSource = .{ .io = std.Io.Threaded.global_single_threaded.io() };
return rng_impl.interface().int(usize);
}
/// Format a request token consistently for use in portal object paths and payloads.

View File

@ -23,7 +23,7 @@ app: *App,
dbus: ?*gio.DBusConnection = null,
/// Mutex to protect modification of the entries map or the cleanup timer.
mutex: std.Thread.Mutex = .{},
mutex: std.Io.Mutex = .init,
/// Map to store data about any in-flight calls to the portal.
entries: std.AutoArrayHashMapUnmanaged(usize, *Entry) = .empty,
@ -78,7 +78,7 @@ const RequestData = struct {
/// Data about any in-flight calls to the portal.
pub const Entry = struct {
/// When the request started.
start: std.time.Instant,
start: std.Io.Timestamp,
/// A token used by the portal to identify requests and responses. The
/// actual format of the token does not really matter as long as it can be
/// used as part of a D-Bus object path. `usize` was chosen since it's easy
@ -123,8 +123,8 @@ pub fn setDbusConnection(self: *OpenURI, dbus: ?*gio.DBusConnection) void {
pub fn deinit(self: *OpenURI) void {
const alloc = self.app.app.allocator();
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (!self.alive) return;
self.alive = false;
@ -157,8 +157,8 @@ pub fn start(self: *OpenURI, value: apprt.action.OpenUrl) (Allocator.Error || Er
alloc.destroy(request);
}
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
// Create an entry that is used to track the results of the D-Bus method
// call.
@ -166,7 +166,7 @@ pub fn start(self: *OpenURI, value: apprt.action.OpenUrl) (Allocator.Error || Er
const entry = try alloc.create(Entry);
errdefer alloc.destroy(entry);
entry.* = .{
.start = std.time.Instant.now() catch return error.TimerUnavailable,
.start = std.Io.Timestamp.now(std.Io.Threaded.global_single_threaded.io(), .awake),
.token = token,
.kind = value.kind,
.uri = try alloc.dupeZ(u8, value.url),
@ -235,8 +235,8 @@ fn destroyEntry(alloc: Allocator, entry: *Entry) void {
}
fn failRequest(self: *OpenURI, token: usize) ?*Entry {
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (!self.alive) return null;
@ -422,8 +422,8 @@ fn requestCallback(
return;
}
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (!self.alive) return;
@ -461,8 +461,8 @@ fn responseReceived(
return;
};
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
if (!self.alive) return;
@ -534,22 +534,18 @@ fn cleanup(ud: ?*anyopaque) callconv(.c) c_int {
const alloc = self.app.app.allocator();
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io());
defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io());
self.cleanup_timer = null;
if (!self.alive) return @intFromBool(glib.SOURCE_REMOVE);
const now = std.time.Instant.now() catch {
// `now()` should never fail, but if it does, don't crash, just return.
// This might cause a small memory leak in rare circumstances but it
// should get cleaned up the next time a URL is clicked.
return @intFromBool(glib.SOURCE_REMOVE);
};
loop: while (true) {
for (self.entries.entries.items(.value)) |entry| {
if (now.since(entry.start) > cleanup_timeout * std.time.ns_per_s) {
if (entry.start.untilNow(
std.Io.Threaded.global_single_threaded.io(),
.awake,
).toSeconds() > cleanup_timeout) {
log.warn("open uri request timed out token={x}", .{entry.token});
self.unsubscribeFromResponse(entry);
_ = self.entries.swapRemove(entry.token);

View File

@ -85,12 +85,10 @@ pub fn postFork(cmd: *Command) Command.PostForkError!void {
return;
};
const start = std.time.Instant.now() catch unreachable;
const start: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
loop: while (true) {
const now = std.time.Instant.now() catch unreachable;
if (now.since(start) > 250 * std.time.ns_per_ms) {
if (start.untilNow(std.Io.Threaded.global_single_threaded.io(), .awake).toMilliseconds() > 250) {
if (cmd.rt_pre_exec_info.linux_cgroup_hard_fail) {
log.err("transition to new transient systemd scope {s} took too long", .{expected_cgroup});
return error.PostForkError;
@ -116,6 +114,6 @@ pub fn postFork(cmd: *Command) Command.PostForkError!void {
}
}
std.Thread.sleep(25 * std.time.ns_per_ms);
std.Io.sleep(std.Io.Threaded.global_single_threaded.io(), .fromMilliseconds(25), .awake) catch unreachable;
}
}

View File

@ -47,12 +47,10 @@ pub fn preExec(cmd: *Command) ?u8 {
var expected_cgroup_buf: [256]u8 = undefined;
const expected_cgroup = cgroup.fmtScope(&expected_cgroup_buf, pid);
const start = std.time.Instant.now() catch unreachable;
const start: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
while (true) {
const now = std.time.Instant.now() catch unreachable;
if (now.since(start) > 250 * std.time.ns_per_ms) {
if (start.untilNow(std.Io.Threaded.global_single_threaded.io(), .awake).toMilliseconds() > 250) {
if (cmd.rt_pre_exec_info.linux_cgroup_hard_fail) {
log.err("transition to new transient systemd scope took too long", .{});
return 127;
@ -74,7 +72,7 @@ pub fn preExec(cmd: *Command) ?u8 {
if (std.mem.eql(u8, current_cgroup, expected_cgroup)) return null;
}
std.Thread.sleep(25 * std.time.ns_per_ms);
std.Io.sleep(std.Io.Threaded.global_single_threaded.io(), .fromMilliseconds(25), .awake) catch unreachable;
}
return null;

View File

@ -141,7 +141,7 @@ pub const Window = union(Protocol) {
};
}
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
pub fn addSubprocessEnv(self: *Window, env: *std.process.Environ.Map) !void {
switch (self.*) {
inline else => |*v| try v.addSubprocessEnv(env),
}

View File

@ -67,7 +67,7 @@ pub const Window = struct {
return true;
}
pub fn addSubprocessEnv(_: *Window, _: *std.process.EnvMap) !void {}
pub fn addSubprocessEnv(_: *Window, _: *std.process.Environ.Map) !void {}
pub fn setUrgent(_: *Window, _: bool) !void {}
};

View File

@ -225,7 +225,7 @@ pub const Window = struct {
};
}
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
pub fn addSubprocessEnv(self: *Window, env: *std.process.Environ.Map) !void {
_ = self;
_ = env;
}

View File

@ -317,7 +317,7 @@ pub const Window = struct {
self.last_applied_decoration_hints = hints;
}
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
pub fn addSubprocessEnv(self: *Window, env: *std.process.Environ.Map) !void {
var buf: [64]u8 = undefined;
const window_id = try std.fmt.bufPrint(
&buf,

View File

@ -122,8 +122,12 @@ pub const Action = union(enum) {
/// Sync with: ghostty_ipc_action_u
pub const CValue = cvalue: {
const key_fields = @typeInfo(Key).@"enum".fields;
var union_fields: [key_fields.len]std.builtin.Type.UnionField = undefined;
for (key_fields, 0..) |field, i| {
var names: [key_fields.len][]const u8 = undefined;
var types: [key_fields.len]type = undefined;
var attrs: [key_fields.len]std.builtin.Type.UnionField.Attributes = undefined;
for (key_fields, &names, &types, &attrs) |field, *name, *ty, *attr| {
const action = @unionInit(Action, field.name, undefined);
const Type = t: {
const Type = @TypeOf(@field(action, field.name));
@ -131,20 +135,12 @@ pub const Action = union(enum) {
if (Type != void and @hasDecl(Type, "C")) break :t Type.C;
break :t Type;
};
union_fields[i] = .{
.name = field.name,
.type = Type,
.alignment = @alignOf(Type),
};
name.* = field.name;
ty.* = Type;
attr.* = .{ .@"align" = @alignOf(Type) };
}
break :cvalue @Type(.{ .@"union" = .{
.layout = .@"extern",
.tag_type = null,
.fields = &union_fields,
.decls = &.{},
} });
break :cvalue @Union(.@"extern", null, &names, &types, &attrs);
};
/// Sync with: ghostty_ipc_action_s

View File

@ -64,21 +64,23 @@ pub fn run(
signpost.log.release();
};
const start = std.time.Instant.now() catch return error.BenchmarkFailed;
const start: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
while (true) {
// Run our step function. If it fails, we return the error.
try self.vtable.stepFn(self.ptr);
result.iterations += 1;
// Get our current monotonic time and check our exit conditions.
const now = std.time.Instant.now() catch return error.BenchmarkFailed;
const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake);
const elapsed = start.durationTo(now).nanoseconds;
assert(elapsed >= 0);
const exit = switch (mode) {
.once => true,
.duration => |ns| now.since(start) >= ns,
.duration => |ns| elapsed >= ns,
};
if (exit) {
result.duration = now.since(start);
result.duration = @as(u64, @intCast(std.math.clamp(elapsed, 0, std.math.maxInt(u64))));
return result;
}
}

View File

@ -20,7 +20,7 @@ const log = std.log.scoped(.@"terminal-stream-bench");
opts: Options,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
data_f: ?std.Io.File = null,
pub const Options = struct {
/// The type of codepoint width calculation to use.
@ -93,7 +93,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
fn teardown(ptr: *anyopaque) void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
f.close(std.Io.Threaded.global_single_threaded.io());
self.data_f = null;
}
}
@ -114,7 +114,7 @@ fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
@ -141,7 +141,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
@ -173,7 +173,7 @@ fn stepSimd(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};

View File

@ -17,7 +17,7 @@ const log = std.log.scoped(.@"terminal-stream-bench");
opts: Options,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
data_f: ?std.Io.File = null,
pub const Options = struct {
/// The type of codepoint width calculation to use.
@ -82,7 +82,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
fn teardown(ptr: *anyopaque) void {
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
f.close(std.Io.Threaded.global_single_threaded.io());
self.data_f = null;
}
}
@ -92,7 +92,7 @@ fn stepNoop(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
@ -115,7 +115,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};

View File

@ -17,7 +17,7 @@ const log = std.log.scoped(.@"is-symbol-bench");
opts: Options,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
data_f: ?std.Io.File = null,
pub const Options = struct {
/// Which test to run.
@ -80,7 +80,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
fn teardown(ptr: *anyopaque) void {
const self: *IsSymbol = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
f.close(std.Io.Threaded.global_single_threaded.io());
self.data_f = null;
}
}
@ -90,7 +90,7 @@ fn stepUucode(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
@ -117,7 +117,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};

View File

@ -13,7 +13,7 @@ const log = std.log.scoped(.@"osc-parser-bench");
opts: Options,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
data_f: ?std.Io.File = null,
parser: Parser,
@ -70,7 +70,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
fn teardown(ptr: *anyopaque) void {
const self: *OscParser = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
f.close(std.Io.Threaded.global_single_threaded.io());
self.data_f = null;
}
}
@ -80,7 +80,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var r = f.reader(&read_buf);
var r = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var osc_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) {

View File

@ -99,7 +99,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
s.nextSlice("hello");
// Setup our terminal state
const data_f: std.fs.File = (options.dataFile(
const data_f: std.Io.File = (options.dataFile(
self.opts.data,
) catch |err| {
log.warn("error opening data file err={}", .{err});
@ -110,7 +110,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
defer stream.deinit();
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = data_f.reader(&read_buf);
var f_reader = data_f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
const r = &f_reader.interface;
var buf: [4096]u8 = undefined;

View File

@ -13,7 +13,7 @@ const log = std.log.scoped(.@"terminal-stream-bench");
opts: Options,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
data_f: ?std.Io.File = null,
pub const Options = struct {
/// The data to read as a filepath. If this is "-" then
@ -61,7 +61,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
fn teardown(ptr: *anyopaque) void {
const self: *TerminalParser = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
f.close(std.Io.Threaded.global_single_threaded.io());
self.data_f = null;
}
}
@ -76,7 +76,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
// aren't currently IO bound.
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
var r = &f_reader.interface;
var p: terminalpkg.Parser = .init();

View File

@ -30,7 +30,7 @@ handler: Handler,
stream: Stream,
/// The file, opened in the setup function.
data_f: ?std.fs.File = null,
data_f: ?std.Io.File = null,
pub const Options = struct {
/// The size of the terminal. This affects benchmarking when
@ -99,7 +99,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
fn teardown(ptr: *anyopaque) void {
const self: *TerminalStream = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
f.close(std.Io.Threaded.global_single_threaded.io());
self.data_f = null;
}
}
@ -115,7 +115,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf);
const r = &f_reader.interface;
var buf: [4096]u8 = undefined;

View File

@ -1,6 +1,8 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const cli = @import("../cli.zig");
const compat_args = @import("../lib/compat/args.zig");
const compat_init = @import("../lib/compat/init.zig");
/// The available actions for the CLI. This is the list of available
/// benchmarks. View docs for each individual one in the predictably
@ -36,7 +38,8 @@ pub const Action = enum {
};
/// An entrypoint for the benchmark CLI.
pub fn main() !void {
pub fn main(init: std.process.Init.Minimal) !void {
compat_init.run(init);
const alloc = std.heap.c_allocator;
const action_ = try cli.action.detectArgs(Action, alloc);
const action = action_ orelse return error.NoAction;
@ -48,7 +51,7 @@ pub const Args = union(enum) {
/// The arguments passed to the CLI via argc/argv.
cli,
/// Simple string arguments, parsed via std.process.ArgIteratorGeneral.
/// Simple string arguments, parsed via ArgIteratorGeneral.
string: []const u8,
};
@ -81,7 +84,7 @@ fn mainActionImpl(
try cli.args.parse(Options, alloc, &opts, &iter);
},
.string => |str| {
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try compat_args.ArgIteratorGeneral(.{}).init(
alloc,
str,
);

View File

@ -6,15 +6,15 @@ const std = @import("std");
/// across our CLI. If the path is not set then no file is returned.
/// If the path is "-", then we will return stdin. If the path is
/// a file then we will open and return the handle.
pub fn dataFile(path_: ?[]const u8) !?std.fs.File {
pub fn dataFile(path_: ?[]const u8) !?std.Io.File {
const path = path_ orelse return null;
// Stdin
if (std.mem.eql(u8, path, "-")) return .stdin();
// Normal file
const file = try std.fs.cwd().openFile(path, .{});
errdefer file.close();
const file = try std.Io.Dir.cwd().openFile(std.Io.Threaded.global_single_threaded.io(), path, .{});
errdefer file.close(std.Io.Threaded.global_single_threaded.io());
return file;
}

View File

@ -67,7 +67,7 @@ emit_unicode_table_gen: bool = false,
is_dep: bool = false,
/// Environmental properties
env: std.process.EnvMap,
env: *const std.process.Environ.Map,
pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Config {
// Setup our standard Zig target and optimize options, i.e.
@ -124,10 +124,10 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf
// defaults.
const gtk_targets = gtk.targets(b);
// We use env vars throughout the build so we grab them immediately here.
var env = try std.process.getEnvMap(b.allocator);
errdefer env.deinit();
// Grab the environment from build state
const env = &b.graph.environ_map;
// We use env vars throughout the build so we grab them immediately here.
var config: Config = .{
.optimize = optimize,
.target = target,
@ -591,7 +591,7 @@ pub fn terminalOptions(self: *const Config, artifact: TerminalBuildOptions.Artif
}
/// Returns a baseline CPU target retaining all the other CPU configs.
pub fn baselineTarget(self: *const Config) std.Build.ResolvedTarget {
pub fn baselineTarget(self: *const Config, io: std.Io) std.Build.ResolvedTarget {
// Set our cpu model as baseline. There may need to be other modifications
// we need to make such as resetting CPU features but for now this works.
var q = self.target.query;
@ -601,7 +601,7 @@ pub fn baselineTarget(self: *const Config) std.Build.ResolvedTarget {
// handle the native case.
return .{
.query = q,
.result = std.zig.system.resolveTargetQuery(q) catch
.result = std.zig.system.resolveTargetQuery(io, q) catch
@panic("unable to resolve baseline query"),
};
}

View File

@ -23,9 +23,9 @@ pub fn init(
// We always want our datagen to be fast because it
// takes awhile to run.
.optimize = .ReleaseFast,
.link_libc = true,
}),
});
exe.linkLibC();
_ = try deps.add(exe);
try steps.append(b.allocator, exe);
}
@ -39,9 +39,9 @@ pub fn init(
.target = deps.config.target,
// We always want our benchmarks to be in release mode.
.optimize = .ReleaseFast,
.link_libc = true,
}),
});
exe.linkLibC();
_ = try deps.add(exe);
try steps.append(b.allocator, exe);
}

View File

@ -237,11 +237,11 @@ pub const Resource = struct {
/// Returns true if the dist path exists at build time.
pub fn exists(self: *const Resource, b: *std.Build) bool {
if (b.build_root.handle.access(self.dist, .{})) {
if (b.build_root.handle.access(b.graph.io, self.dist, .{})) {
// If we have a ".git" directory then we're a git checkout
// and we never want to use the dist path. This shouldn't happen
// so show a warning to the user.
if (b.build_root.handle.access(".git", .{})) {
if (b.build_root.handle.access(b.graph.io, ".git", .{})) {
std.log.warn(
"dist resource '{s}' should not be in a git checkout",
.{self.dist},

View File

@ -50,7 +50,7 @@ pub fn init(
generate_markdown.root_module.addOptions("build_options", generate_markdown_options);
const generate_markdown_step = b.addRunArtifact(generate_markdown);
const markdown_output = generate_markdown_step.captureStdOut();
const markdown_output = generate_markdown_step.captureStdOut(.{});
try steps.append(b.allocator, &b.addInstallFile(
markdown_output,
@ -68,7 +68,7 @@ pub fn init(
generate_html.addFileArg(markdown_output);
try steps.append(b.allocator, &b.addInstallFile(
generate_html.captureStdOut(),
generate_html.captureStdOut(.{}),
"share/ghostty/doc/" ++ manpage.name ++ "." ++ manpage.section ++ ".html",
).step);
@ -83,7 +83,7 @@ pub fn init(
generate_manpage.addFileArg(markdown_output);
try steps.append(b.allocator, &b.addInstallFile(
generate_manpage.captureStdOut(),
generate_manpage.captureStdOut(.{}),
"share/man/man" ++ manpage.section ++ "/" ++ manpage.name ++ "." ++ manpage.section,
).step);
}

View File

@ -50,7 +50,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
switch (cfg.target.result.os.tag) {
.windows => {
exe.subsystem = .Windows;
exe.addWin32ResourceFile(.{
exe.root_module.addWin32ResourceFile(.{
.file = b.path("dist/windows/ghostty.rc"),
});
},
@ -85,7 +85,7 @@ fn checkNixShell(exe: *std.Build.Step.Compile, cfg: *const Config) !void {
if (!cfg.target.query.isNativeOs()) return;
// Verify we're in NixOS
std.fs.accessAbsolute("/etc/NIXOS", .{}) catch return;
std.Io.Dir.accessAbsolute(exe.step.owner.graph.io, "/etc/NIXOS", .{}) catch return;
// If we're in a nix shell, not a problem
if (cfg.env.get("IN_NIX_SHELL") != null) return;

View File

@ -40,16 +40,16 @@ pub fn distResources(b: *std.Build) struct {
.name = "framegen",
.root_module = b.createModule(.{
.target = b.graph.host,
.link_libc = true,
}),
});
exe.addCSourceFile(.{
exe.root_module.addCSourceFile(.{
.file = b.path("src/build/framegen/main.c"),
.flags = &.{},
});
exe.linkLibC();
if (b.systemIntegrationOption("zlib", .{})) {
exe.linkSystemLibrary2("zlib", .{
exe.root_module.linkSystemLibrary("zlib", .{
.preferred_link_mode = .dynamic,
.search_strategy = .mode_first,
});
@ -58,7 +58,7 @@ pub fn distResources(b: *std.Build) struct {
.target = b.graph.host,
.optimize = .ReleaseFast,
})) |zlib_dep| {
exe.linkLibrary(zlib_dep.artifact("z"));
exe.root_module.linkLibrary(zlib_dep.artifact("z"));
}
}

View File

@ -34,7 +34,7 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyI18n {
msgfmt.addFileArg(b.path("po/" ++ locale ++ ".po"));
try steps.append(b.allocator, &b.addInstallFile(
msgfmt.captureStdOut(),
msgfmt.captureStdOut(.{}),
std.fmt.comptimePrint(
"share/locale/{s}/LC_MESSAGES/{s}.mo",
.{ target_locale, domain },
@ -103,14 +103,15 @@ fn createUpdateStep(b: *std.Build) !*std.Build.Step {
}
var gtk_dir = try b.build_root.handle.openDir(
b.graph.io,
"src/apprt/gtk",
.{ .iterate = true },
);
defer gtk_dir.close();
defer gtk_dir.close(b.graph.io);
var walk = try gtk_dir.walk(b.allocator);
defer walk.deinit();
while (try walk.next()) |src| {
while (try walk.next(b.graph.io)) |src| {
switch (src.kind) {
.file => if (!std.mem.endsWith(
u8,
@ -178,15 +179,15 @@ fn createUpdateStep(b: *std.Build) !*std.Build.Step {
xgettext_merge.addFileArg(gtk_pot);
const usf = b.addUpdateSourceFiles();
usf.addCopyFileToSource(
xgettext_merge.captureStdOut(),
xgettext_merge.captureStdOut(.{}),
"po/" ++ domain ++ ".pot",
);
inline for (locales) |locale| {
const msgmerge = b.addSystemCommand(&.{ "msgmerge", "--quiet", "--no-fuzzy-matching" });
msgmerge.addFileArg(b.path("po/" ++ locale ++ ".po"));
msgmerge.addFileArg(xgettext_merge.captureStdOut());
usf.addCopyFileToSource(msgmerge.captureStdOut(), "po/" ++ locale ++ ".po");
msgmerge.addFileArg(xgettext_merge.captureStdOut(.{}));
usf.addCopyFileToSource(msgmerge.captureStdOut(.{}), "po/" ++ locale ++ ".po");
}
return &usf.step;

View File

@ -29,12 +29,12 @@ pub fn initStatic(
.strip = deps.config.strip,
.omit_frame_pointer = deps.config.strip,
.unwind_tables = if (deps.config.strip) .none else .sync,
.link_libc = true,
}),
// Fails on self-hosted x86_64 on macOS
.use_llvm = true,
});
lib.linkLibC();
// These must be bundled since we're compiling into a static lib.
// Otherwise, you get undefined symbol errors.
@ -72,6 +72,14 @@ pub fn initShared(
b: *std.Build,
deps: *const SharedDeps,
) !GhosttyLib {
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.
const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{
.preferred_link_mode = .dynamic,
.search_strategy = .mode_first,
};
const lib = b.addLibrary(.{
.name = "ghostty",
.linkage = .dynamic,
@ -99,12 +107,12 @@ pub fn initShared(
{
// The CRT initialization code in msvcrt.lib calls __vcrt_initialize
// and __acrt_initialize, which are in the static CRT libraries.
lib.linkSystemLibrary("libvcruntime");
lib.root_module.linkSystemLibrary("libvcruntime", dynamic_link_opts);
// ucrt.lib is in the Windows SDK 'ucrt' dir. Detect the SDK
// installation and add the UCRT library path.
const arch = deps.config.target.result.cpu.arch;
const sdk = std.zig.WindowsSdk.find(b.allocator, arch) catch null;
const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, arch, &b.graph.environ_map) catch null;
if (sdk) |s| {
if (s.windows10sdk) |w10| {
const arch_str: []const u8 = switch (arch) {
@ -120,11 +128,11 @@ pub fn initShared(
) catch null;
if (ucrt_lib_path) |path| {
lib.addLibraryPath(.{ .cwd_relative = path });
lib.root_module.addLibraryPath(.{ .cwd_relative = path });
}
}
}
lib.linkSystemLibrary("libucrt");
lib.root_module.linkSystemLibrary("libucrt", dynamic_link_opts);
}
// Get our debug symbols

View File

@ -174,7 +174,12 @@ pub fn initStaticAppleUniversal(
.os_tag = p.os_tag,
.os_version_min = Config.osVersionMin(p.os_tag),
};
if (detectAppleSDK(b.resolveTargetQuery(target_query).result)) {
if (detectAppleSDK(
b.graph.io,
b.allocator,
&b.graph.environ_map,
b.resolveTargetQuery(target_query).result,
)) {
const dev_zig = try zig.retarget(b, cfg, deps, b.resolveTargetQuery(target_query));
result.put(p.device, try initStatic(b, &dev_zig));
@ -438,12 +443,21 @@ pub fn xcframework(
}
/// Returns true if the Apple SDK for the given target is installed.
fn detectAppleSDK(target: std.Target) bool {
_ = std.zig.LibCInstallation.findNative(.{
.allocator = std.heap.page_allocator,
.target = &target,
.verbose = false,
}) catch return false;
fn detectAppleSDK(
io: std.Io,
alloc: std.mem.Allocator,
environ_map: *const std.process.Environ.Map,
target: std.Target,
) bool {
_ = std.zig.LibCInstallation.findNative(
alloc,
io,
.{
.environ_map = environ_map,
.target = &target,
.verbose = false,
},
) catch return false;
return true;
}

View File

@ -21,9 +21,9 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
.strip = false,
.omit_frame_pointer = false,
.unwind_tables = .sync,
.link_libc = true,
}),
});
build_data_exe.linkLibC();
deps.help_strings.addImport(build_data_exe);
@ -39,7 +39,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
const run = b.addRunArtifact(build_data_exe);
run.addArg("+terminfo");
const wf = b.addWriteFiles();
const source = wf.addCopyFile(run.captureStdOut(), "ghostty.terminfo");
const source = wf.addCopyFile(run.captureStdOut(.{}), "ghostty.terminfo");
if (cfg.emit_terminfo) {
const source_install = b.addInstallFile(
@ -63,8 +63,8 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
const run_step = RunStep.create(b, "infotocap");
run_step.addArg("infotocap");
run_step.addFileArg(source);
const out_source = run_step.captureStdOut();
_ = run_step.captureStdErr(); // so we don't see stderr
const out_source = run_step.captureStdOut(.{});
_ = run_step.captureStdErr(.{}); // so we don't see stderr
const cap_install = b.addInstallFile(
out_source,
@ -84,7 +84,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
const path = run_step.addOutputFileArg(terminfo_share_dir);
run_step.addFileArg(source);
_ = run_step.captureStdErr(); // so we don't see stderr
_ = run_step.captureStdErr(.{}); // so we don't see stderr
// Ensure that `share/terminfo` is a directory, otherwise the `cp
// -R` will create a file named `share/terminfo`
@ -143,7 +143,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
const run = b.addRunArtifact(build_data_exe);
run.addArg("+fish");
const wf = b.addWriteFiles();
_ = wf.addCopyFile(run.captureStdOut(), "ghostty.fish");
_ = wf.addCopyFile(run.captureStdOut(.{}), "ghostty.fish");
const install_step = b.addInstallDirectory(.{
.source_dir = wf.getDirectory(),
@ -158,7 +158,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
const run = b.addRunArtifact(build_data_exe);
run.addArg("+zsh");
const wf = b.addWriteFiles();
_ = wf.addCopyFile(run.captureStdOut(), "_ghostty");
_ = wf.addCopyFile(run.captureStdOut(.{}), "_ghostty");
const install_step = b.addInstallDirectory(.{
.source_dir = wf.getDirectory(),
@ -173,7 +173,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
const run = b.addRunArtifact(build_data_exe);
run.addArg("+bash");
const wf = b.addWriteFiles();
_ = wf.addCopyFile(run.captureStdOut(), "ghostty.bash");
_ = wf.addCopyFile(run.captureStdOut(.{}), "ghostty.bash");
const install_step = b.addInstallDirectory(.{
.source_dir = wf.getDirectory(),
@ -190,22 +190,22 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
{
const run = b.addRunArtifact(build_data_exe);
run.addArg("+vim-syntax");
_ = wf.addCopyFile(run.captureStdOut(), "syntax/ghostty.vim");
_ = wf.addCopyFile(run.captureStdOut(.{}), "syntax/ghostty.vim");
}
{
const run = b.addRunArtifact(build_data_exe);
run.addArg("+vim-ftdetect");
_ = wf.addCopyFile(run.captureStdOut(), "ftdetect/ghostty.vim");
_ = wf.addCopyFile(run.captureStdOut(.{}), "ftdetect/ghostty.vim");
}
{
const run = b.addRunArtifact(build_data_exe);
run.addArg("+vim-ftplugin");
_ = wf.addCopyFile(run.captureStdOut(), "ftplugin/ghostty.vim");
_ = wf.addCopyFile(run.captureStdOut(.{}), "ftplugin/ghostty.vim");
}
{
const run = b.addRunArtifact(build_data_exe);
run.addArg("+vim-compiler");
_ = wf.addCopyFile(run.captureStdOut(), "compiler/ghostty.vim");
_ = wf.addCopyFile(run.captureStdOut(.{}), "compiler/ghostty.vim");
}
const vim_step = b.addInstallDirectory(.{
@ -233,7 +233,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
const run = b.addRunArtifact(build_data_exe);
run.addArg("+sublime");
const wf = b.addWriteFiles();
_ = wf.addCopyFile(run.captureStdOut(), "ghostty.sublime-syntax");
_ = wf.addCopyFile(run.captureStdOut(.{}), "ghostty.sublime-syntax");
const install_step = b.addInstallDirectory(.{
.source_dir = wf.getDirectory(),
@ -358,10 +358,10 @@ fn addLinuxAppResources(
// Template output has a single header line we want to remove.
// We use `tail` to do it since its part of the POSIX standard.
const tail = b.addSystemCommand(&.{ "tail", "-n", "+2" });
tail.setStdIn(.{ .lazy_path = tpl.getOutput() });
tail.setStdIn(.{ .lazy_path = tpl.getOutputFile() });
const copy = b.addInstallFile(
tail.captureStdOut(),
tail.captureStdOut(.{}),
template[1],
);

View File

@ -40,7 +40,7 @@ pub fn init(
}
const webgen_config_step = b.addRunArtifact(webgen_config);
const webgen_config_out = webgen_config_step.captureStdOut();
const webgen_config_out = webgen_config_step.captureStdOut(.{});
try steps.append(b.allocator, &b.addInstallFile(
webgen_config_out,
@ -71,7 +71,7 @@ pub fn init(
}
const webgen_actions_step = b.addRunArtifact(webgen_actions);
const webgen_actions_out = webgen_actions_step.captureStdOut();
const webgen_actions_out = webgen_actions_step.captureStdOut(.{});
try steps.append(b.allocator, &b.addInstallFile(
webgen_actions_out,
@ -102,7 +102,7 @@ pub fn init(
}
const webgen_commands_step = b.addRunArtifact(webgen_commands);
const webgen_commands_out = webgen_commands_step.captureStdOut();
const webgen_commands_out = webgen_commands_step.captureStdOut(.{});
try steps.append(b.allocator, &b.addInstallFile(
webgen_commands_out,

View File

@ -48,21 +48,21 @@ pub fn init(
},
};
const env = try std.process.getEnvMap(b.allocator);
const env = b.graph.environ_map;
const app_path = b.fmt("macos/build/{s}/Ghostty.app", .{xc_config});
// Our step to build the Ghostty macOS app.
const build = build: {
// External environment variables can mess up xcodebuild, so
// we create a new empty environment.
const env_map = try b.allocator.create(std.process.EnvMap);
const env_map = try b.allocator.create(std.process.Environ.Map);
env_map.* = .init(b.allocator);
if (env.get("PATH")) |v| try env_map.put("PATH", v);
const step = RunStep.create(b, "xcodebuild");
step.has_side_effects = true;
step.cwd = b.path("macos");
step.env_map = env_map;
step.environ_map = env_map;
step.addArgs(&.{
"xcodebuild",
"-target",
@ -91,14 +91,14 @@ pub fn init(
};
const xctest = xctest: {
const env_map = try b.allocator.create(std.process.EnvMap);
const env_map = try b.allocator.create(std.process.Environ.Map);
env_map.* = .init(b.allocator);
if (env.get("PATH")) |v| try env_map.put("PATH", v);
const step = RunStep.create(b, "xcodebuild test");
step.has_side_effects = true;
step.cwd = b.path("macos");
step.env_map = env_map;
step.environ_map = env_map;
step.addArgs(&.{
"xcodebuild",
"test",

View File

@ -135,7 +135,7 @@ fn initVt(
deps.unicode_tables.addModuleImport(vt);
// We need uucode for grapheme break support
deps.addUucode(b, vt, cfg.target, cfg.optimize);
vt.addImport("uucode", deps.uucode_mod);
// If SIMD is enabled, add all our SIMD dependencies.
if (cfg.simd) {

View File

@ -23,7 +23,7 @@ pub fn detect(b: *std.Build) !Version {
const tmp: []u8 = b.runAllowFail(
&[_][]const u8{ "git", "-C", b.build_root.path orelse ".", "rev-parse", "--abbrev-ref", "HEAD" },
&code,
.Ignore,
.ignore,
) catch |err| switch (err) {
error.FileNotFound => return error.GitNotFound,
error.ExitCodeFailure => return error.GitNotRepository,
@ -44,19 +44,19 @@ pub fn detect(b: *std.Build) !Version {
const output = b.runAllowFail(
&[_][]const u8{ "git", "-C", b.build_root.path orelse ".", "-c", "log.showSignature=false", "log", "--pretty=format:%h", "-n", "1" },
&code,
.Ignore,
.ignore,
) catch |err| switch (err) {
error.FileNotFound => return error.GitNotFound,
else => return err,
};
break :short_hash std.mem.trimRight(u8, output, "\r\n ");
break :short_hash std.mem.trimEnd(u8, output, "\r\n ");
};
const tag = b.runAllowFail(
&[_][]const u8{ "git", "-C", b.build_root.path orelse ".", "describe", "--exact-match", "--tags" },
&code,
.Ignore,
.ignore,
) catch |err| switch (err) {
error.FileNotFound => return error.GitNotFound,
error.ExitCodeFailure => "", // expected
@ -70,7 +70,7 @@ pub fn detect(b: *std.Build) !Version {
"diff",
"--quiet",
"--exit-code",
}, &code, .Ignore) catch |err| switch (err) {
}, &code, .ignore) catch |err| switch (err) {
error.FileNotFound => return error.GitNotFound,
error.ExitCodeFailure => {}, // expected
else => return err,
@ -80,7 +80,7 @@ pub fn detect(b: *std.Build) !Version {
return .{
.short_hash = short_hash,
.changes = changes,
.tag = if (tag.len > 0) std.mem.trimRight(u8, tag, "\r\n ") else null,
.branch = std.mem.trimRight(u8, branch, "\r\n "),
.tag = if (tag.len > 0) std.mem.trimEnd(u8, tag, "\r\n ") else null,
.branch = std.mem.trimEnd(u8, branch, "\r\n "),
};
}

View File

@ -34,7 +34,7 @@ pub fn init(b: *std.Build, cfg: *const Config) !HelpStrings {
// Generated Zig files have to end with .zig
const wf = b.addWriteFiles();
const output = wf.addCopyFile(help_run.captureStdOut(), "helpgen.zig");
const output = wf.addCopyFile(help_run.captureStdOut(.{}), "helpgen.zig");
return .{
.exe = exe,

View File

@ -2,6 +2,12 @@ const SharedDeps = @This();
const std = @import("std");
const builtin = @import("builtin");
// NOTE: we only translate some headers right now with the external translate-c
// package (for headers that are currently having issues translating in
// 0.16.0). While we likely won't need to use the external package eventually
// once bugs are fixed, we will need to use similar patterns for all
// translations eventually, as cImport is going away.
const Translator = @import("translate_c").Translator;
const Config = @import("Config.zig");
const HelpStrings = @import("HelpStrings.zig");
@ -9,6 +15,7 @@ const MetallibStep = @import("MetallibStep.zig");
const UnicodeTables = @import("UnicodeTables.zig");
const GhosttyFrameData = @import("GhosttyFrameData.zig");
const DistResource = @import("GhosttyDist.zig").Resource;
const gtk_helpers = @import("gtk.zig");
config: *const Config,
@ -19,6 +26,43 @@ unicode_tables: UnicodeTables,
framedata: GhosttyFrameData,
uucode_tables: std.Build.LazyPath,
/// Singleton uucode module, instantiated once in `init` and reused
/// everywhere so that ghostty and vaxis share the same compiled tables in
/// each final binary instead of each linking its own copy.
///
/// Sharing one instance is also a hard requirement (not just an
/// optimization) for Zig 0.16's strict module model. `SharedDeps.add` runs
/// many times across different (target, optimize) tuples (macos-aarch64,
/// macos-x86_64, ios-aarch64, Debug + ReleaseFast, etc.), and on each
/// call we have to wire uucode into both the step's root module and into
/// vaxis_mod (because vaxis's `Parser.zig` does `@import("uucode")` and
/// we pass `external_uucode = true` to vaxis's build.zig so vaxis doesn't
/// instantiate its own uucode dep). If those two import bindings ever
/// resolve to *different* `*Module` pointers within a single Compile
/// step's analysis, Zig fails with:
///
/// vaxis/src/Parser.zig: file exists in modules 'uucode' and 'uucode0'
///
/// because all those uucode module instances share the same physical
/// `uucode/src/root.zig` file on disk, and Zig requires every file to belong
/// to exactly one module within a Compile graph.
///
/// The natural way to keep them the same would be to call
/// `b.lazyDependency("uucode", .{ .tables_path, .build_config_path })`
/// from each call site and let Zig's dependency cache deduplicate
/// identical args. That fails because of a bug in Zig's
/// `userLazyPathsAreTheSame` (Build.zig) where the `.src_path` and
/// `.generated` equality checks are inverted: `if (std.mem.eql(...))
/// return false` instead of `if (!std.mem.eql(...)) return false`. The
/// dep cache key therefore always misses whenever any arg is a
/// `b.path(...)` LazyPath, so each call returns a fresh `*Dependency`
/// with a fresh `*Module`. Hoisting the dep into one eager
/// `b.dependency` call here sidesteps the cache entirely.
///
/// This conflict is independent of whether vaxis itself is acquired as a
/// singleton or per-target dep.
uucode_mod: *std.Build.Module,
/// Used to keep track of a list of file sources.
pub const LazyPathList = std.ArrayList(std.Build.LazyPath);
@ -31,12 +75,20 @@ pub fn init(b: *std.Build, cfg: *const Config) !SharedDeps {
break :blk uucode.namedLazyPath("tables.zig");
};
// Instantiate the singleton uucode module that both ghostty and vaxis
// import. See the doc comment on `uucode_mod`.
const uucode_mod = b.dependency("uucode", .{
.tables_path = uucode_tables,
.build_config_path = b.path("src/build/uucode_config.zig"),
}).module("uucode");
var result: SharedDeps = .{
.config = cfg,
.help_strings = try .init(b, cfg),
.unicode_tables = try .init(b, uucode_tables),
.framedata = try .init(b),
.uucode_tables = uucode_tables,
.uucode_mod = uucode_mod,
// Setup by retarget
.options = undefined,
@ -135,6 +187,9 @@ pub fn add(
// Every exe needs the terminal options
self.config.terminalOptions(.ghostty).add(b, step.root_module);
// Every exe needs the uucode module
step.root_module.addImport("uucode", self.uucode_mod);
// C imports for locale constants and functions
{
const c = b.addTranslateC(.{
@ -143,11 +198,15 @@ pub fn add(
.optimize = optimize,
});
if (target.result.os.tag.isDarwin()) {
const libc = try std.zig.LibCInstallation.findNative(.{
.allocator = b.allocator,
.target = &target.result,
.verbose = false,
});
const libc = try std.zig.LibCInstallation.findNative(
b.allocator,
b.graph.io,
.{
.environ_map = &b.graph.environ_map,
.target = &target.result,
.verbose = false,
},
);
c.addSystemIncludePath(.{ .cwd_relative = libc.sys_include_dir.? });
}
step.root_module.addImport("locale-c", c.createModule());
@ -166,11 +225,15 @@ pub fn add(
});
switch (target.result.os.tag) {
.macos => {
const libc = try std.zig.LibCInstallation.findNative(.{
.allocator = b.allocator,
.target = &target.result,
.verbose = false,
});
const libc = try std.zig.LibCInstallation.findNative(
b.allocator,
b.graph.io,
.{
.environ_map = &b.graph.environ_map,
.target = &target.result,
.verbose = false,
},
);
c.addSystemIncludePath(.{ .cwd_relative = libc.sys_include_dir.? });
},
else => {},
@ -194,10 +257,10 @@ pub fn add(
);
if (b.systemIntegrationOption("freetype", .{})) {
step.linkSystemLibrary2("bzip2", dynamic_link_opts);
step.linkSystemLibrary2("freetype2", dynamic_link_opts);
step.root_module.linkSystemLibrary("bzip2", dynamic_link_opts);
step.root_module.linkSystemLibrary("freetype2", dynamic_link_opts);
} else {
step.linkLibrary(freetype_dep.artifact("freetype"));
step.root_module.linkLibrary(freetype_dep.artifact("freetype"));
try static_libs.append(
b.allocator,
freetype_dep.artifact("freetype").getEmittedBin(),
@ -219,9 +282,9 @@ pub fn add(
harfbuzz_dep.module("harfbuzz"),
);
if (b.systemIntegrationOption("harfbuzz", .{})) {
step.linkSystemLibrary2("harfbuzz", dynamic_link_opts);
step.root_module.linkSystemLibrary("harfbuzz", dynamic_link_opts);
} else {
step.linkLibrary(harfbuzz_dep.artifact("harfbuzz"));
step.root_module.linkLibrary(harfbuzz_dep.artifact("harfbuzz"));
try static_libs.append(
b.allocator,
harfbuzz_dep.artifact("harfbuzz").getEmittedBin(),
@ -243,9 +306,9 @@ pub fn add(
);
if (b.systemIntegrationOption("fontconfig", .{})) {
step.linkSystemLibrary2("fontconfig", dynamic_link_opts);
step.root_module.linkSystemLibrary("fontconfig", dynamic_link_opts);
} else {
step.linkLibrary(fontconfig_dep.artifact("fontconfig"));
step.root_module.linkLibrary(fontconfig_dep.artifact("fontconfig"));
try static_libs.append(
b.allocator,
fontconfig_dep.artifact("fontconfig").getEmittedBin(),
@ -263,7 +326,7 @@ pub fn add(
.target = target,
.optimize = optimize,
})) |libpng_dep| {
step.linkLibrary(libpng_dep.artifact("png"));
step.root_module.linkLibrary(libpng_dep.artifact("png"));
try static_libs.append(
b.allocator,
libpng_dep.artifact("png").getEmittedBin(),
@ -277,7 +340,7 @@ pub fn add(
.target = target,
.optimize = optimize,
})) |zlib_dep| {
step.linkLibrary(zlib_dep.artifact("z"));
step.root_module.linkLibrary(zlib_dep.artifact("z"));
try static_libs.append(
b.allocator,
zlib_dep.artifact("z").getEmittedBin(),
@ -295,9 +358,9 @@ pub fn add(
oniguruma_dep.module("oniguruma"),
);
if (b.systemIntegrationOption("oniguruma", .{})) {
step.linkSystemLibrary2("oniguruma", dynamic_link_opts);
step.root_module.linkSystemLibrary("oniguruma", dynamic_link_opts);
} else {
step.linkLibrary(oniguruma_dep.artifact("oniguruma"));
step.root_module.linkLibrary(oniguruma_dep.artifact("oniguruma"));
try static_libs.append(
b.allocator,
oniguruma_dep.artifact("oniguruma").getEmittedBin(),
@ -312,13 +375,13 @@ pub fn add(
})) |glslang_dep| {
step.root_module.addImport("glslang", glslang_dep.module("glslang"));
if (b.systemIntegrationOption("glslang", .{})) {
step.linkSystemLibrary2("glslang", dynamic_link_opts);
step.linkSystemLibrary2(
step.root_module.linkSystemLibrary("glslang", dynamic_link_opts);
step.root_module.linkSystemLibrary(
"glslang-default-resource-limits",
dynamic_link_opts,
);
} else {
step.linkLibrary(glslang_dep.artifact("glslang"));
step.root_module.linkLibrary(glslang_dep.artifact("glslang"));
try static_libs.append(
b.allocator,
glslang_dep.artifact("glslang").getEmittedBin(),
@ -336,9 +399,9 @@ pub fn add(
spirv_cross_dep.module("spirv_cross"),
);
if (b.systemIntegrationOption("spirv-cross", .{})) {
step.linkSystemLibrary2("spirv-cross-c-shared", dynamic_link_opts);
step.root_module.linkSystemLibrary("spirv-cross-c-shared", dynamic_link_opts);
} else {
step.linkLibrary(spirv_cross_dep.artifact("spirv_cross"));
step.root_module.linkLibrary(spirv_cross_dep.artifact("spirv_cross"));
try static_libs.append(
b.allocator,
spirv_cross_dep.artifact("spirv_cross").getEmittedBin(),
@ -357,7 +420,7 @@ pub fn add(
"sentry",
sentry_dep.module("sentry"),
);
step.linkLibrary(sentry_dep.artifact("sentry"));
step.root_module.linkLibrary(sentry_dep.artifact("sentry"));
try static_libs.append(
b.allocator,
sentry_dep.artifact("sentry").getEmittedBin(),
@ -404,18 +467,18 @@ pub fn add(
if (step.rootModuleTarget().os.tag == .linux) {
const triple = try step.rootModuleTarget().linuxTriple(b.allocator);
const path = b.fmt("/usr/lib/{s}", .{triple});
if (std.fs.accessAbsolute(path, .{})) {
step.addLibraryPath(.{ .cwd_relative = path });
if (std.Io.Dir.accessAbsolute(b.graph.io, path, .{})) {
step.root_module.addLibraryPath(.{ .cwd_relative = path });
} else |_| {}
}
// C files
step.linkLibC();
step.addIncludePath(b.path("src/stb"));
step.root_module.link_libc = true;
step.root_module.addIncludePath(b.path("src/stb"));
// Disable ubsan for MSVC: Zig's ubsan runtime cannot be bundled
// on Windows (LNK4229), leaving __ubsan_handle_* unresolved when
// the static archive is consumed by an external linker.
step.addCSourceFiles(.{
step.root_module.addCSourceFiles(.{
.files = &.{"src/stb/stb.c"},
.flags = if (step.rootModuleTarget().abi == .msvc)
&.{ "-fno-sanitize=undefined", "-fno-sanitize-trap=undefined" }
@ -423,7 +486,7 @@ pub fn add(
&.{},
});
if (step.rootModuleTarget().os.tag == .linux) {
step.addIncludePath(b.path("src/apprt/gtk"));
step.root_module.addIncludePath(b.path("src/apprt/gtk"));
}
// libcpp is required for various dependencies. On MSVC, we must
@ -433,7 +496,7 @@ pub fn add(
// include directories (already added via linkLibC above) contain
// both C and C++ headers, so linkLibCpp is not needed.
if (step.rootModuleTarget().abi != .msvc) {
step.linkLibCpp();
step.root_module.link_libcpp = true;
}
// We always require the system SDK so that our system headers are available.
@ -452,8 +515,14 @@ pub fn add(
if (b.lazyDependency("opengl", .{})) |dep| {
step.root_module.addImport("opengl", dep.module("opengl"));
}
if (b.lazyDependency("vaxis", .{})) |dep| {
step.root_module.addImport("vaxis", dep.module("vaxis"));
if (b.lazyDependency("vaxis", .{
.target = target,
.optimize = optimize,
.external_uucode = true,
})) |dep| {
const vaxis = dep.module("vaxis");
step.root_module.addImport("vaxis", vaxis);
vaxis.addImport("uucode", self.uucode_mod);
}
if (b.lazyDependency("wuffs", .{
.target = target,
@ -473,7 +542,6 @@ pub fn add(
})) |dep| {
step.root_module.addImport("z2d", dep.module("z2d"));
}
self.addUucode(b, step.root_module, target, optimize);
if (b.lazyDependency("zf", .{
.target = target,
.optimize = optimize,
@ -502,7 +570,7 @@ pub fn add(
"macos",
macos_dep.module("macos"),
);
step.linkLibrary(
step.root_module.linkLibrary(
macos_dep.artifact("macos"),
);
try static_libs.append(
@ -512,7 +580,7 @@ pub fn add(
}
if (self.config.renderer == .opengl) {
step.linkFramework("OpenGL");
step.root_module.linkFramework("OpenGL", .{});
}
// Apple platforms do not include libc libintl so we bundle it.
@ -523,7 +591,7 @@ pub fn add(
.target = target,
.optimize = optimize,
})) |libintl_dep| {
step.linkLibrary(libintl_dep.artifact("intl"));
step.root_module.linkLibrary(libintl_dep.artifact("intl"));
try static_libs.append(
b.allocator,
libintl_dep.artifact("intl").getEmittedBin(),
@ -543,7 +611,7 @@ pub fn add(
.@"backend-opengl3" = !target.result.os.tag.isDarwin(),
})) |dep| {
step.root_module.addImport("dcimgui", dep.module("dcimgui"));
step.linkLibrary(dep.artifact("dcimgui"));
step.root_module.linkLibrary(dep.artifact("dcimgui"));
try static_libs.append(
b.allocator,
dep.artifact("dcimgui").getEmittedBin(),
@ -592,15 +660,15 @@ pub fn add(
// If we're building an exe then we have additional dependencies.
if (step.kind != .lib) {
// We always statically compile glad
step.addIncludePath(b.path("vendor/glad/include/"));
step.addCSourceFile(.{
step.root_module.addIncludePath(b.path("vendor/glad/include/"));
step.root_module.addCSourceFile(.{
.file = b.path("vendor/glad/src/gl.c"),
.flags = &.{},
});
// When we're targeting flatpak we ALWAYS link GTK so we
// get access to glib for dbus.
if (self.config.flatpak) step.linkSystemLibrary2("gtk4", dynamic_link_opts);
if (self.config.flatpak) step.root_module.linkSystemLibrary("gtk4", dynamic_link_opts);
switch (self.config.app_runtime) {
.none => {},
@ -643,12 +711,38 @@ fn addGtkNg(
step.root_module.addImport(name, gobject.module(module));
}
}
{
// translate-c stuff
const translate_c = b.dependency("translate_c", .{});
step.linkSystemLibrary2("gtk4", dynamic_link_opts);
step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
{
// GTK headers
const translated: Translator = .init(translate_c, .{
.c_source_file = b.addWriteFiles().add("gtk_c.h",
\\#include <gtk/gtk.h>
),
.target = target,
.optimize = optimize,
});
translated.linkSystemLibrary("gtk4", dynamic_link_opts);
step.root_module.addImport("gtk_c", translated.mod);
}
{
// Adwaita headers
const translated: Translator = .init(translate_c, .{
.c_source_file = b.addWriteFiles().add("adw_c.h",
\\#include <adwaita.h>
),
.target = target,
.optimize = optimize,
});
translated.linkSystemLibrary("libadwaita-1", dynamic_link_opts);
step.root_module.addImport("adw_c", translated.mod);
}
}
if (self.config.x11) {
step.linkSystemLibrary2("X11", dynamic_link_opts);
step.root_module.linkSystemLibrary("X11", dynamic_link_opts);
if (gobject_) |gobject| {
step.root_module.addImport(
"gdk_x11",
@ -732,24 +826,34 @@ fn addGtkNg(
// IMPORTANT: gtk4-layer-shell must be linked BEFORE
// wayland-client, as it relies on shimming libwayland's APIs.
if (b.systemIntegrationOption("gtk4-layer-shell", .{})) {
step.linkSystemLibrary2("gtk4-layer-shell-0", dynamic_link_opts);
step.root_module.linkSystemLibrary("gtk4-layer-shell-0", dynamic_link_opts);
} else {
// gtk4-layer-shell *must* be dynamically linked,
// so we don't add it as a static library
const shared_lib = gtk4_layer_shell.artifact("gtk4-layer-shell");
b.installArtifact(shared_lib);
step.linkLibrary(shared_lib);
step.root_module.linkLibrary(shared_lib);
}
}
step.linkSystemLibrary2("wayland-client", dynamic_link_opts);
step.root_module.linkSystemLibrary("wayland-client", dynamic_link_opts);
}
{
// Get our gresource c/h files and add them to our build.
const dist = gtkNgDistResources(b);
step.addCSourceFile(.{ .file = dist.resources_c.path(b), .flags = &.{} });
step.addIncludePath(dist.resources_h.path(b).dirname());
const translate_c = b.dependency("translate_c", .{});
const translated: Translator = .init(translate_c, .{
.c_source_file = b.addWriteFiles().add("c.h",
\\#include <ghostty_resources.h>
),
.target = target,
.optimize = optimize,
});
translated.linkSystemLibrary("glib-2.0", dynamic_link_opts);
translated.addIncludePath(dist.resources_h.path(b).dirname());
translated.mod.addCSourceFile(.{ .file = dist.resources_c.path(b), .flags = &.{} });
step.root_module.addImport("ghostty_gtk_resources", translated.mod);
}
}
@ -890,11 +994,22 @@ pub fn gtkNgDistResources(
.root_module = b.createModule(.{
.root_source_file = b.path("src/apprt/gtk/build/blueprint.zig"),
.target = b.graph.host,
.link_libc = true,
}),
});
blueprint_exe.linkLibC();
blueprint_exe.linkSystemLibrary2("gtk4", dynamic_link_opts);
blueprint_exe.linkSystemLibrary2("libadwaita-1", dynamic_link_opts);
{
// Adwaita headers
const translate_c = b.dependency("translate_c", .{});
const translated: Translator = .init(translate_c, .{
.c_source_file = b.addWriteFiles().add("adw_c.h",
\\#include <adwaita.h>
),
.target = b.graph.host,
.optimize = .Debug,
});
translated.linkSystemLibrary("libadwaita-1", dynamic_link_opts);
blueprint_exe.root_module.addImport("adw_c", translated.mod);
}
for (gresource.blueprints) |bp| {
const blueprint_run = b.addRunArtifact(blueprint_exe);
@ -923,7 +1038,7 @@ pub fn gtkNgDistResources(
xml_run.addFileArg(ui_file);
}
break :gresource_xml xml_run.captureStdOut();
break :gresource_xml xml_run.captureStdOut(.{});
};
const generate_c = b.addSystemCommand(&.{
@ -964,23 +1079,6 @@ pub fn gtkNgDistResources(
};
}
pub fn addUucode(
self: *const SharedDeps,
b: *std.Build,
module: *std.Build.Module,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
) void {
if (b.lazyDependency("uucode", .{
.target = target,
.optimize = optimize,
.tables_path = self.uucode_tables,
.build_config_path = b.path("src/build/uucode_config.zig"),
})) |dep| {
module.addImport("uucode", dep.module("uucode"));
}
}
// For dynamic linking, we prefer dynamic linking and to search by
// mode first. Mode first will search all paths for a dynamic library
// before falling back to static.

View File

@ -54,8 +54,8 @@ pub fn init(b: *std.Build, uucode_tables: std.Build.LazyPath) !UnicodeTables {
// Generated Zig files have to end with .zig
const wf = b.addWriteFiles();
const props_output = wf.addCopyFile(props_run.captureStdOut(), "props.zig");
const symbols_output = wf.addCopyFile(symbols_run.captureStdOut(), "symbols.zig");
const props_output = wf.addCopyFile(props_run.captureStdOut(.{}), "props.zig");
const symbols_output = wf.addCopyFile(symbols_run.captureStdOut(.{}), "symbols.zig");
return .{
.props_exe = props_exe,

View File

@ -63,8 +63,8 @@ pub fn create(b: *std.Build, opts: Options) *XCFrameworkStep {
run.addArg("-output");
run.addArg(opts.out_path);
run.expectExitCode(0);
_ = run.captureStdOut();
_ = run.captureStdErr();
_ = run.captureStdOut(.{});
_ = run.captureStdErr(.{});
break :run run;
};
run_create.step.dependOn(&run_delete.step);

13
src/build/args.zig Normal file
View File

@ -0,0 +1,13 @@
const std = @import("std");
/// Helper function for args allocation. Caller fully owns the slice and can
/// deinit as need be.
pub fn argsAlloc(alloc: std.mem.Allocator, args: std.process.Args) std.mem.Allocator.Error![][:0]const u8 {
var result: std.ArrayList([:0]const u8) = .empty;
errdefer result.deinit(alloc);
var it = args.iterate();
while (it.next()) |arg| {
try result.append(alloc, arg);
}
return try result.toOwnedSlice(alloc);
}

View File

@ -10,12 +10,14 @@
//! Usage: combine_archives <zig_exe> <output.a> <input1.a> [input2.a ...]
const std = @import("std");
const argsAlloc = @import("args.zig").argsAlloc;
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const alloc = gpa.allocator();
pub fn main(init: std.process.Init) !void {
const alloc = init.arena.allocator();
const args = try argsAlloc(alloc, init.minimal.args);
defer alloc.free(args);
const args = try std.process.argsAlloc(alloc);
if (args.len < 4) {
std.log.err("usage: combine_archives <zig_exe> <output> <input...>", .{});
std.process.exit(1);
@ -37,19 +39,20 @@ pub fn main() !void {
}
try script.appendSlice(alloc, "SAVE\nEND\n");
var child: std.process.Child = .init(&.{ zig_exe, "ar", "-M" }, alloc);
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
var child = try std.process.spawn(init.io, .{
.argv = &.{ zig_exe, "ar", "-M" },
.stdin = .pipe,
.stdout = .inherit,
.stderr = .inherit,
});
try child.spawn();
try child.stdin.?.writeAll(script.items);
child.stdin.?.close();
try child.stdin.?.writeStreamingAll(init.io, script.items);
child.stdin.?.close(init.io);
child.stdin = null;
const term = try child.wait();
if (term.Exited != 0) {
std.log.err("zig ar -M exited with code {d}", .{term.Exited});
const term = try child.wait(init.io);
if (term.exited != 0) {
std.log.err("zig ar -M exited with code {d}", .{term.exited});
std.process.exit(1);
}
}

View File

@ -14,7 +14,7 @@ pub fn targets(b: *std.Build) Targets {
const output = b.runAllowFail(
&.{ "pkg-config", "--variable=targets", "gtk4" },
&code,
.Ignore,
.ignore,
) catch return .{};
const x11 = std.mem.indexOf(u8, output, "x11") != null;

View File

@ -1,12 +1,11 @@
const std = @import("std");
const gen = @import("mdgen.zig");
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const alloc = gpa.allocator();
pub fn main(init: std.process.Init) !void {
const alloc = init.arena.allocator();
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer);
const writer = &stdout_writer.interface;
try gen.substitute(alloc, @embedFile("ghostty_1_header.md"), writer);
try gen.genActions(writer);

View File

@ -1,12 +1,11 @@
const std = @import("std");
const gen = @import("mdgen.zig");
pub fn main() !void {
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const alloc = gpa.allocator();
pub fn main(init: std.process.Init) !void {
const alloc = init.arena.allocator();
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer);
const writer = &stdout_writer.interface;
try gen.substitute(alloc, @embedFile("ghostty_5_header.md"), writer);
try gen.genConfig(writer, false);

View File

@ -1,109 +1,138 @@
const std = @import("std");
const assert = std.debug.assert;
const config = @import("config.zig");
const config_x = @import("config.x.zig");
const d = config.default;
const wcwidth = config_x.wcwidth;
const grapheme_break_no_control = config_x.grapheme_break_no_control;
const Allocator = std.mem.Allocator;
fn computeWidth(
alloc: std.mem.Allocator,
cp: u21,
data: anytype,
backing: anytype,
tracking: anytype,
) Allocator.Error!void {
_ = alloc;
_ = cp;
_ = backing;
_ = tracking;
// This condition is needed as Ghostty currently has a singular concept for
// the `width` of a code point, while `uucode` splits the concept into
// `wcwidth_standalone` and `wcwidth_zero_in_grapheme`. The two cases where
// we want to use the `wcwidth_standalone` despite the code point occupying
// zero width in a grapheme (`wcwidth_zero_in_grapheme`) are emoji
// modifiers and prepend code points. For emoji modifiers we want to
// support displaying them in isolation as color patches, and if prepend
// characters were to be width 0 they would disappear from the output with
// Ghostty's current width 0 handling. Future work will take advantage of
// the new uucode `wcwidth_standalone` vs `wcwidth_zero_in_grapheme` split.
if (data.wcwidth_zero_in_grapheme and !data.is_emoji_modifier and data.grapheme_break_no_control != .prepend) {
data.width = 0;
} else {
data.width = @min(2, data.wcwidth_standalone);
}
}
const width = config.Extension{
.inputs = &.{
"wcwidth_standalone",
"wcwidth_zero_in_grapheme",
"is_emoji_modifier",
"grapheme_break_no_control",
pub const fields = &config.mergeFields(config.fields, &.{
.{ .name = "width", .type = u2 },
.{ .name = "is_symbol", .type = bool },
});
pub const build_components = &config.mergeComponents(config.build_components, &.{
.{
.Impl = WidthComponent,
.inputs = &.{
"wcwidth_standalone",
"wcwidth_zero_in_grapheme",
"is_emoji_modifier",
"grapheme_break_no_control",
},
.fields = &.{"width"},
},
.compute = &computeWidth,
.fields = &.{
.{ .name = "width", .type = u2 },
.{
.Impl = IsSymbolComponent,
.inputs = &.{ "block", "general_category" },
.fields = &.{"is_symbol"},
},
};
});
fn computeIsSymbol(
alloc: Allocator,
cp: u21,
data: anytype,
backing: anytype,
tracking: anytype,
) Allocator.Error!void {
_ = alloc;
_ = cp;
_ = backing;
_ = tracking;
const block = data.block;
data.is_symbol = data.general_category == .other_private_use or
block == .arrows or
block == .dingbats or
block == .emoticons or
block == .miscellaneous_symbols or
block == .enclosed_alphanumerics or
block == .enclosed_alphanumeric_supplement or
block == .miscellaneous_symbols_and_pictographs or
block == .transport_and_map_symbols;
}
const is_symbol = config.Extension{
.inputs = &.{ "block", "general_category" },
.compute = &computeIsSymbol,
.fields = &.{
.{ .name = "is_symbol", .type = bool },
},
};
pub const get_components: []const config.Component = &.{};
pub const tables = [_]config.Table{
.{
.name = "runtime",
.extensions = &.{},
.fields = &.{
d.field("is_emoji_presentation"),
d.field("case_folding_full"),
"is_emoji_presentation",
"case_folding_full",
},
},
.{
// Fields that libvaxis needs that aren't included in the `runtime`
// table.
.name = "libvaxis_only",
.fields = &.{
"east_asian_width",
"general_category",
"grapheme_break",
},
},
.{
.name = "buildtime",
.extensions = &.{
wcwidth,
grapheme_break_no_control,
width,
is_symbol,
},
.fields = &.{
width.field("width"),
wcwidth.field("wcwidth_zero_in_grapheme"),
grapheme_break_no_control.field("grapheme_break_no_control"),
is_symbol.field("is_symbol"),
d.field("is_emoji_vs_base"),
"width",
"wcwidth_zero_in_grapheme",
"grapheme_break_no_control",
"is_symbol",
"is_emoji_vs_base",
},
},
};
const WidthComponent = struct {
pub fn build(
comptime InputRow: type,
comptime Row: type,
allocator: std.mem.Allocator,
io: std.Io,
inputs: config.MultiSlice(InputRow),
rows: *config.MultiSlice(Row),
backing: anytype,
tracking: anytype,
) !void {
_ = allocator;
_ = io;
_ = backing;
_ = tracking;
rows.len = config.num_code_points;
const items = rows.items(.width);
const standalone = inputs.items(.wcwidth_standalone);
const zero_in_grapheme = inputs.items(.wcwidth_zero_in_grapheme);
const is_emoji_modifier = inputs.items(.is_emoji_modifier);
const grapheme_break_no_control = inputs.items(.grapheme_break_no_control);
// This condition is needed as Ghostty currently has a singular concept for
// the `width` of a code point, while `uucode` splits the concept into
// `wcwidth_standalone` and `wcwidth_zero_in_grapheme`. The two cases where
// we want to use the `wcwidth_standalone` despite the code point occupying
// zero width in a grapheme (`wcwidth_zero_in_grapheme`) are emoji
// modifiers and prepend code points. For emoji modifiers we want to
// support displaying them in isolation as color patches, and if prepend
// characters were to be width 0 they would disappear from the output with
// Ghostty's current width 0 handling. Future work will take advantage of
// the new uucode `wcwidth_standalone` vs `wcwidth_zero_in_grapheme` split.
for (0..config.num_code_points) |i| {
if (zero_in_grapheme[i] and !is_emoji_modifier[i] and grapheme_break_no_control[i] != .prepend) {
items[i] = 0;
} else {
items[i] = @min(2, standalone[i]);
}
}
}
};
const IsSymbolComponent = struct {
pub fn build(
comptime InputRow: type,
comptime Row: type,
allocator: std.mem.Allocator,
io: std.Io,
inputs: config.MultiSlice(InputRow),
rows: *config.MultiSlice(Row),
backing: anytype,
tracking: anytype,
) !void {
_ = allocator;
_ = io;
_ = backing;
_ = tracking;
rows.len = config.num_code_points;
const items = rows.items(.is_symbol);
const block = inputs.items(.block);
const general_category = inputs.items(.general_category);
for (0..config.num_code_points) |i| {
items[i] =
general_category[i] == .other_private_use or
block[i] == .arrows or
block[i] == .dingbats or
block[i] == .emoticons or
block[i] == .miscellaneous_symbols or
block[i] == .enclosed_alphanumerics or
block[i] == .enclosed_alphanumeric_supplement or
block[i] == .miscellaneous_symbols_and_pictographs or
block[i] == .transport_and_map_symbols;
}
}
};

View File

@ -11,16 +11,16 @@
const std = @import("std");
const testing = std.testing;
const Allocator = std.mem.Allocator;
const argsAlloc = @import("args.zig").argsAlloc;
pub fn main() !void {
pub fn main(init: std.process.Init) !void {
// This is a one-off patcher, so we leak all our memory on purpose
// and let the OS clean it up when we exit.
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const alloc = gpa.allocator();
const alloc = init.arena.allocator();
// Parse args: program input output
const args = try std.process.argsAlloc(alloc);
defer std.process.argsFree(alloc, args);
const args = try argsAlloc(alloc, init.minimal.args);
defer alloc.free(args);
if (args.len != 3) {
std.log.err("usage: wasm_growable_table <input.wasm> <output.wasm>", .{});
std.process.exit(1);
@ -30,7 +30,8 @@ pub fn main() !void {
// Patch the file.
const output: []const u8 = try patchTableGrowable(
alloc,
try std.fs.cwd().readFileAlloc(
try std.Io.Dir.cwd().readFileAlloc(
init.io,
alloc,
args[1],
std.math.maxInt(usize),
@ -38,9 +39,9 @@ pub fn main() !void {
);
// Write our output
const out_file = try std.fs.cwd().createFile(args[2], .{});
defer out_file.close();
try out_file.writeAll(output);
const out_file = try std.Io.Dir.cwd().createFile(init.io, args[2], .{});
defer out_file.close(init.io);
try out_file.writePositionalAll(init.io, output);
}
/// Patch the WASM binary's table section to remove the maximum size

View File

@ -1,9 +1,9 @@
const std = @import("std");
const helpgen_actions = @import("../../input/helpgen_actions.zig");
pub fn main() !void {
pub fn main(init: std.process.Init) !void {
var buffer: [2048]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer);
const stdout = &stdout_writer.interface;
try helpgen_actions.generate(stdout, .markdown, true, std.heap.page_allocator);
}

View File

@ -2,9 +2,9 @@ const std = @import("std");
const Action = @import("../../cli/ghostty.zig").Action;
const help_strings = @import("help_strings");
pub fn main() !void {
pub fn main(init: std.process.Init) !void {
var buffer: [2048]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer);
const stdout = &stdout_writer.interface;
try genActions(stdout);
}

View File

@ -2,9 +2,9 @@ const std = @import("std");
const Config = @import("../../config/Config.zig");
const help_strings = @import("help_strings");
pub fn main() !void {
pub fn main(init: std.process.Init) !void {
var buffer: [2048]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer);
const stdout = &stdout_writer.interface;
try genConfig(stdout);
}

Some files were not shown because too many files have changed in this diff Show More