Update to Zig 0.16.0

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

Key parts:

* In addition to its previous responsibilities, the global state now
  houses state for global I/O implementations and the process
  environment. It is now also utilized in the main application along
  with the C library. Where necessary, global state is isolated from key
  parts of the implementation (e.g., in libghostty subsystems), and it's
  expected that this list will grow.

* We currently manage our own C translation layer where necessary. In
  these cases, cImport has been removed in favor of the new external
  translate-c package. Due to fixes that have needed be made to properly
  translate the dependencies that were swapped out, as mentioned, we
  have had to backport fixes from the current translate-c package (and
  the upstream Arocc dependency). We will host this ourselves until Zig
  0.17.0 is released with these fixes.

* Where necessary (only a small number of cases), some stdlib code from
  0.15.2 (and even from 0.17.0) has been taken, adopted, and vendored in
  lib/compat.

Co-authored-by: Leah Amelia Chen <hi@pluie.me>
This commit is contained in:
Chris Marchesi
2026-05-07 09:11:14 -07:00
parent 74d0c72fd9
commit e8525c0fd9
357 changed files with 8958 additions and 6098 deletions

View File

@@ -179,4 +179,3 @@ WhitespaceSensitiveMacros:
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
---

View File

@@ -153,7 +153,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Xcode Version
run: xcodebuild -version

View File

@@ -362,7 +362,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Build XCFramework
run: nix develop -c zig build -Demit-lib-vt -Doptimize=ReleaseFast
@@ -454,7 +454,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Xcode Version
run: xcodebuild -version
@@ -710,7 +710,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Xcode Version
run: xcodebuild -version
@@ -907,7 +907,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Xcode Version
run: xcodebuild -version

View File

@@ -335,7 +335,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Build XCFramework
run: nix develop -c zig build -Demit-lib-vt
@@ -645,7 +645,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Build
run: |
@@ -1077,7 +1077,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Xcode Version
run: xcodebuild -version
@@ -1137,7 +1137,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Xcode Version
run: xcodebuild -version
@@ -1196,9 +1196,29 @@ jobs:
- name: Test GTK Build
run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs -Demit-webdata
# This relies on the cache being populated by the commands above.
# NOTE: This used to point to the Zig global cache directory before
# 0.16.0. Now, the cache directory exclusively stores "minified"
# archives of each dependency (i.e., only the paths defined in
# build.zig.zon), and an uncompressed, working copy is stored in
# PROJECT_ROOT/zig-pkg. The zig-pkg directory is warmed up during the
# regular fetching process from the global cache directory if
# possible.
#
# Nothing else should change; zon2nix's store path mimics what a
# zig-pkg directory should look like, so actual build steps should be
# fine, it's just here where we assume that all dependencies have been
# downloaded, and we want to test the validity of the package
# directory, do we need to have it pointing to zig-pkg and not the
# zon2nix store path.
#
# Note that this can also be attempted by running:
# zig build --fetch=needed
#
# Note that --system PKGDIR should not be added to this invocation as
# it disables fetching altogether (even, seemingly, from the local
# cache).
- name: Test System Build
run: nix develop -c zig build --system ${ZIG_GLOBAL_CACHE_DIR}/p
run: nix develop -c zig build --system "$(realpath zig-pkg)"
test-lib-vt:
if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true'
@@ -1377,7 +1397,7 @@ jobs:
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Xcode Select
run: sudo xcode-select -s /Applications/Xcode_26.3.app
run: sudo xcode-select -s /Applications/Xcode_26.6.app
- name: Xcode Version
run: xcodebuild -version

View File

@@ -18,7 +18,7 @@ jobs:
with:
sparse-checkout: .github/issue-unvouched-message
- uses: mitchellh/vouch/action/check-issue@d66fa29a64600490892131ad87597c30c91fcac4 # unreleased main
- uses: mitchellh/vouch/action/check-issue@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0
with:
issue-number: ${{ github.event.issue.number }}
auto-close: true

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

@@ -67,14 +67,6 @@ sudo xcode-select --switch /Applications/Xcode.app
> You do not need to be running on macOS 26 to build Ghostty, you can
> still use Xcode 26 on macOS 15 stable.
> [!WARNING]
>
> Zig 0.15.x has a [known linking issue](https://codeberg.org/ziglang/zig/issues/31658)
> with **Xcode 26.4**. If you are on Xcode 26.4, you must use a
> Homebrew-installed Zig (`brew install zig@0.15`) or our Nix flake,
> both of which contain a patch that works around the issue. Alternatively,
> you can downgrade to **Xcode 26.3**.
## AI and Agents
If you're using AI assistance with Ghostty, Ghostty provides an

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,
@@ -352,19 +353,18 @@ pub fn build(b: *std.Build) !void {
// Crash on x86_64 without this
.use_llvm = true,
});
if (config.emit_test_exe) b.installArtifact(test_exe);
if (config.emit_test_exe) {
const test_exe_install = b.addInstallArtifact(test_exe, .{});
config.addPatchElf(test_exe, &test_exe_install.step);
test_step.dependOn(&test_exe_install.step);
}
_ = try deps.add(test_exe);
// Verify our internal libghostty header.
const ghostty_h = b.addTranslateC(.{
.root_source_file = b.path("include/ghostty.h"),
.target = config.baselineTarget(),
.optimize = .Debug,
});
test_exe.root_module.addImport("ghostty.h", ghostty_h.createModule());
addGhosttyH(b, test_exe.root_module, config.baselineTarget(b.graph.io), .Debug);
// Normal test running
const test_run = b.addRunArtifact(test_exe);
config.addPatchElf(test_exe, &test_run.step);
test_step.dependOn(&test_run.step);
// Normal tests always test our libghostty modules
@@ -379,6 +379,7 @@ pub fn build(b: *std.Build) !void {
"--gen-suppressions=all",
});
valgrind_run.addArtifactArg(test_exe);
config.addPatchElf(test_exe, &valgrind_run.step);
test_valgrind_step.dependOn(&valgrind_run.step);
}
@@ -390,3 +391,28 @@ pub fn build(b: *std.Build) !void {
try translations_step.addError("cannot update translations when i18n is disabled", .{});
}
}
fn addGhosttyH(
b: *std.Build,
module: *std.Build.Module,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
) void {
const translate_c = b.lazyImport(@This(), "translate_c") orelse return;
const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse return;
const translated: translate_c.Translator = .init(translate_c_dep, .{
.c_source_file = b.addWriteFiles().add(
"hb_c.h",
\\#include <ghostty.h>
,
),
.target = target,
.optimize = optimize,
.link_libc = true,
});
translated.addSystemIncludePath(b.path("include"));
module.addImport("ghostty.h", translated.mod);
}

View File

@@ -3,62 +3,71 @@
.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.
.translate_c = .{
.lazy = true,
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
},
// 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,
},

78
build.zig.zon.json generated
View File

@@ -1,4 +1,9 @@
{
"aro-0.0.0-JSD1Qk8rOQDnuVcD4jAwMpHitA6pADRKzQ7M7hKRwxvD": {
"name": "aro",
"url": "https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz",
"hash": "sha256-rjNfhWjmA/1WR/xuHo4ls4fnDCbsI1VZSvb8SFRvwso="
},
"N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr": {
"name": "bindings",
"url": "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz",
@@ -29,10 +34,10 @@
"url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz",
"hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U="
},
"gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-": {
"gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp": {
"name": "gobject",
"url": "https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst",
"hash": "sha256-OxS9/XC5aMJj1KhOcFP1ZZN7PI4ADw4f7ocx6V64mOc="
"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": "sha256-cZGf9a1+sDeORKWYIdzQ/5KLT7l/9pgPc2cIbqfBc4o="
},
"N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": {
"name": "gtk4_layer_shell",
@@ -69,10 +74,10 @@
"url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz",
"hash": "sha256-/syVtGzwXo4/yKQUdQ4LparQDYnp/fF16U/wQcrxoDo="
},
"libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs": {
"libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F": {
"name": "libxev",
"url": "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz",
"hash": "sha256-1B9oJExVyOWRj+Y9d9eHkOBTlOYuEkcwGBUKdlgRhkg="
"url": "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
"hash": "sha256-fOU1oxIxfoEgoLuWz7fVX6M+zmqpo7gqZObWiH/aDE0="
},
"N-V-__8AAG3RoQEyRC2Vw7Qoro5SYBf62IHn3HjqtNVY6aWK": {
"name": "libxml2",
@@ -109,20 +114,25 @@
"url": "https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz",
"hash": "sha256-tStvz8Ref6abHwahNiwVVHNETizAmZVVaxVsU7pmV+M="
},
"uucode-0.1.0-ZZjBPj96QADXyt5sqwBJUnhaDYs_qBeeKijZvlRa0eqM": {
"name": "uucode",
"url": "git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732",
"hash": "sha256-sHPh+TQSdUGus/QTbj7KSJJkTuNTrK4VNmQDjS30Lf8="
"translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB": {
"name": "translate_c",
"url": "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
"hash": "sha256-i7TSSiLU9YGtLtEjgVqy2jSnKLDTeKHX+EFuXEMXJ+U="
},
"uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9": {
"uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA": {
"name": "uucode",
"url": "https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz",
"hash": "sha256-jLrhrmCXQ1T+LQP1JTBBB3Jn+1hCZfODbC4SdlfNdKg="
"url": "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
"hash": "sha256-R5RXW5tWIaDq5JOF2+oWd5YOYOyns6WH7f687WE+b20="
},
"vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS": {
"vaxis-0.6.0-BWNV_MjFCQCs9UDHiRkrgw_ayeiPkzOe4xVbaAqXkUWW": {
"name": "vaxis",
"url": "https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz",
"hash": "sha256-zTyrZrIffM+GJIt973tKDeWHmOCwbn7KLDdQxSiK00Y="
"url": "git+https://github.com/rockorager/libvaxis.git#c1e1f23be38951c425cdf31af455ba23ef178940",
"hash": "sha256-bIXu8lGwGo42QbItC0jOi/eN7u+f4snknBexw7dc0DI="
},
"vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka": {
"name": "vaxis",
"url": "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
"hash": "sha256-z3J3w+oYapfAZZR+MvWXwg+th1hePDA1TptnKFO4r+M="
},
"N-V-__8AAKrHGAAs2shYq8UkE6bGcR1QJtLTyOE_lcosMn6t": {
"name": "wayland",
@@ -144,35 +154,35 @@
"url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz",
"hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM="
},
"z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ": {
"z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR": {
"name": "z2d",
"url": "https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz",
"hash": "sha256-qD+XexnAjSanRAwr5ZIaPY1aQhNW5DFVJ4PYLwhIr2E="
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz",
"hash": "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA="
},
"zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh": {
"zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW": {
"name": "zf",
"url": "https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz",
"hash": "sha256-BfAZILill3I/nBf1oWwol77N34Jcpm4hudC+XSeMgZY="
"url": "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
"hash": "sha256-LHurWwqK8jhP8AZd/CXrMJNeVsKftRCTN31/EgEgKaA="
},
"zig_js-0.0.0-rjCAV-6GAADxFug7rDmPH-uM_XcnJ5NmuAMJCAscMjhi": {
"zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj": {
"name": "zig_js",
"url": "https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz",
"hash": "sha256-r6GdXwrv+jTu0AkTlyN/FuO+N4X+l20gsbS59wrE7V4="
"url": "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
"hash": "sha256-Q1643wb/MCX0QkPa9QEY9xeQbqpC2IOrjER0ZyL3mH0="
},
"zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK": {
"zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi": {
"name": "zig_objc",
"url": "https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz",
"hash": "sha256-jWFQ5BrV880qqa9KypltWuRLqNSh21rDxt6Jxp0EoMM="
"url": "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
"hash": "sha256-t8cTTJm0crcoGAyOqXO0wB2w7Yit80J2Orwr6prd/G0="
},
"wayland-0.5.0-dev-lQa1khrMAQDJDwYFKpdH3HizherB7sHo5dKMECfvxQHe": {
"wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX": {
"name": "zig_wayland",
"url": "https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz",
"hash": "sha256-1wRkixysjdFMyrATxlXdukAc34MwfNj0B6ydYVn+UKw="
"url": "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
"hash": "sha256-3m/ITNhZUJ/5uD/Tqm+0uZSktGoYgWF5oldOqOCUkIE="
},
"zigimg-0.1.0-8_eo2vHnEwCIVW34Q14Ec-xUlzIoVg86-7FU2ypPtxms": {
"zigimg-0.1.0-8_eo2oyaFwBZwJpmqPkCfVXWBrHcqbYwmrp1I6bTD3lI": {
"name": "zigimg",
"url": "https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz",
"hash": "sha256-vkcTloGX+vRw7e6GYJLO9eocYaEOYjXYE0dT7jscZ4A="
"url": "git+https://github.com/zigimg/zigimg#d695acd97c02e57bb151e8f659d1280f5cd6ca70",
"hash": "sha256-0IYATQldT6eJxRR2T/2CsIYZuzomqjvmdVyjmsjguyE="
},
"N-V-__8AAB0eQwD-0MdOEBmz7intriBReIsIDNlukNVoNu6o": {
"name": "zlib",

100
build.zig.zon.nix generated
View File

@@ -6,7 +6,7 @@
fetchurl,
fetchgit,
runCommandLocal,
zig_0_15,
zig_0_16,
zstd,
name ? "zig-packages",
}: let
@@ -16,12 +16,16 @@
}:
runCommandLocal name
{
nativeBuildInputs = [zig_0_15];
nativeBuildInputs = [zig_0_16];
}
''
hash="$(cd "$TMPDIR" && zig fetch --global-cache-dir "$TMPDIR" ${artifact})"
mv "$TMPDIR/p/$hash" "$out"
chmod 755 "$out"
# workaround https://codeberg.org/ziglang/zig/issues/31866
# https://github.com/Cloudef/zig2nix/issues/54
mkdir "$TMPDIR/src" "$TMPDIR/cache"
touch "$TMPDIR/src/build.zig"
hash="$(cd "$TMPDIR/src" && zig fetch --global-cache-dir "$TMPDIR/cache" ${artifact})"
mkdir "$out"
tar zxvf "$TMPDIR/cache/p/$hash.tar.gz" --directory "$out/" --strip-components=1
'';
fetchZig = {
@@ -93,6 +97,15 @@
fetcher.${proto};
in
linkFarm name [
{
name = "aro-0.0.0-JSD1Qk8rOQDnuVcD4jAwMpHitA6pADRKzQ7M7hKRwxvD";
path = fetchZigArtifact {
name = "aro";
url = "https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz";
hash = "sha256-rjNfhWjmA/1WR/xuHo4ls4fnDCbsI1VZSvb8SFRvwso=";
unpack = true;
};
}
{
name = "N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr";
path = fetchZigArtifact {
@@ -148,11 +161,11 @@ in
};
}
{
name = "gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-";
name = "gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp";
path = fetchZigArtifact {
name = "gobject";
url = "https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst";
hash = "sha256-OxS9/XC5aMJj1KhOcFP1ZZN7PI4ADw4f7ocx6V64mOc=";
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 = "sha256-cZGf9a1+sDeORKWYIdzQ/5KLT7l/9pgPc2cIbqfBc4o=";
unpack = true;
};
}
@@ -220,11 +233,11 @@ in
};
}
{
name = "libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs";
name = "libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F";
path = fetchZigArtifact {
name = "libxev";
url = "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz";
hash = "sha256-1B9oJExVyOWRj+Y9d9eHkOBTlOYuEkcwGBUKdlgRhkg=";
url = "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz";
hash = "sha256-fOU1oxIxfoEgoLuWz7fVX6M+zmqpo7gqZObWiH/aDE0=";
unpack = true;
};
}
@@ -292,29 +305,38 @@ in
};
}
{
name = "uucode-0.1.0-ZZjBPj96QADXyt5sqwBJUnhaDYs_qBeeKijZvlRa0eqM";
name = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB";
path = fetchZigArtifact {
name = "uucode";
url = "git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732";
hash = "sha256-sHPh+TQSdUGus/QTbj7KSJJkTuNTrK4VNmQDjS30Lf8=";
name = "translate_c";
url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz";
hash = "sha256-i7TSSiLU9YGtLtEjgVqy2jSnKLDTeKHX+EFuXEMXJ+U=";
unpack = true;
};
}
{
name = "uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9";
name = "uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA";
path = fetchZigArtifact {
name = "uucode";
url = "https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz";
hash = "sha256-jLrhrmCXQ1T+LQP1JTBBB3Jn+1hCZfODbC4SdlfNdKg=";
url = "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz";
hash = "sha256-R5RXW5tWIaDq5JOF2+oWd5YOYOyns6WH7f687WE+b20=";
unpack = true;
};
}
{
name = "vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS";
name = "vaxis-0.6.0-BWNV_MjFCQCs9UDHiRkrgw_ayeiPkzOe4xVbaAqXkUWW";
path = fetchZigArtifact {
name = "vaxis";
url = "https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz";
hash = "sha256-zTyrZrIffM+GJIt973tKDeWHmOCwbn7KLDdQxSiK00Y=";
url = "git+https://github.com/rockorager/libvaxis.git#c1e1f23be38951c425cdf31af455ba23ef178940";
hash = "sha256-bIXu8lGwGo42QbItC0jOi/eN7u+f4snknBexw7dc0DI=";
unpack = true;
};
}
{
name = "vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka";
path = fetchZigArtifact {
name = "vaxis";
url = "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz";
hash = "sha256-z3J3w+oYapfAZZR+MvWXwg+th1hePDA1TptnKFO4r+M=";
unpack = true;
};
}
@@ -355,56 +377,56 @@ in
};
}
{
name = "z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ";
name = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR";
path = fetchZigArtifact {
name = "z2d";
url = "https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz";
hash = "sha256-qD+XexnAjSanRAwr5ZIaPY1aQhNW5DFVJ4PYLwhIr2E=";
url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz";
hash = "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA=";
unpack = true;
};
}
{
name = "zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh";
name = "zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW";
path = fetchZigArtifact {
name = "zf";
url = "https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz";
hash = "sha256-BfAZILill3I/nBf1oWwol77N34Jcpm4hudC+XSeMgZY=";
url = "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz";
hash = "sha256-LHurWwqK8jhP8AZd/CXrMJNeVsKftRCTN31/EgEgKaA=";
unpack = true;
};
}
{
name = "zig_js-0.0.0-rjCAV-6GAADxFug7rDmPH-uM_XcnJ5NmuAMJCAscMjhi";
name = "zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj";
path = fetchZigArtifact {
name = "zig_js";
url = "https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz";
hash = "sha256-r6GdXwrv+jTu0AkTlyN/FuO+N4X+l20gsbS59wrE7V4=";
url = "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz";
hash = "sha256-Q1643wb/MCX0QkPa9QEY9xeQbqpC2IOrjER0ZyL3mH0=";
unpack = true;
};
}
{
name = "zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK";
name = "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi";
path = fetchZigArtifact {
name = "zig_objc";
url = "https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz";
hash = "sha256-jWFQ5BrV880qqa9KypltWuRLqNSh21rDxt6Jxp0EoMM=";
url = "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz";
hash = "sha256-t8cTTJm0crcoGAyOqXO0wB2w7Yit80J2Orwr6prd/G0=";
unpack = true;
};
}
{
name = "wayland-0.5.0-dev-lQa1khrMAQDJDwYFKpdH3HizherB7sHo5dKMECfvxQHe";
name = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX";
path = fetchZigArtifact {
name = "zig_wayland";
url = "https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz";
hash = "sha256-1wRkixysjdFMyrATxlXdukAc34MwfNj0B6ydYVn+UKw=";
url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz";
hash = "sha256-3m/ITNhZUJ/5uD/Tqm+0uZSktGoYgWF5oldOqOCUkIE=";
unpack = true;
};
}
{
name = "zigimg-0.1.0-8_eo2vHnEwCIVW34Q14Ec-xUlzIoVg86-7FU2ypPtxms";
name = "zigimg-0.1.0-8_eo2oyaFwBZwJpmqPkCfVXWBrHcqbYwmrp1I6bTD3lI";
path = fetchZigArtifact {
name = "zigimg";
url = "https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz";
hash = "sha256-vkcTloGX+vRw7e6GYJLO9eocYaEOYjXYE0dT7jscZ4A=";
url = "git+https://github.com/zigimg/zigimg#d695acd97c02e57bb151e8f659d1280f5cd6ca70";
hash = "sha256-0IYATQldT6eJxRR2T/2CsIYZuzomqjvmdVyjmsjguyE=";
unpack = true;
};
}

24
build.zig.zon.txt generated
View File

@@ -1,4 +1,7 @@
git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732
git+https://github.com/rockorager/libvaxis.git#c1e1f23be38951c425cdf31af455ba23ef178940
git+https://github.com/zigimg/zigimg#d695acd97c02e57bb151e8f659d1280f5cd6ca70
https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz
https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz
https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz
https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz
https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz
@@ -8,29 +11,28 @@ https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab0
https://deps.files.ghostty.org/gettext-0.24.tar.gz
https://deps.files.ghostty.org/ghostty-themes-release-20260713-155359-c3968b3.tgz
https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz
https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst
https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz
https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz
https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz
https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz
https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz
https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz
https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz
https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz
https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz
https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz
https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz
https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz
https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz
https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz
https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz
https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz
https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz
https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz
https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz
https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz
https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz
https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz
https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz
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
https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz
https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz
https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz
https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz
https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz
https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz
https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz
https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz
https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz
https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz

View File

@@ -1,24 +1,20 @@
const std = @import("std");
const ghostty_vt = @import("ghostty-vt");
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const alloc = gpa.allocator();
pub fn main(init: std.process.Init) !void {
// Create a terminal
var t: ghostty_vt.Terminal = try .init(alloc, .{ .cols = 150, .rows = 80 });
defer t.deinit(alloc);
var t: ghostty_vt.Terminal = try .init(init.io, init.gpa, .{ .cols = 150, .rows = 80 });
defer t.deinit(init.gpa);
// Create a read-only VT stream for parsing terminal sequences
var stream = t.vtStream();
defer stream.deinit();
// Read from stdin
const stdin = std.fs.File.stdin();
const stdin = std.Io.File.stdin();
var buf: [4096]u8 = undefined;
while (true) {
const n = try stdin.readAll(&buf);
const n = try stdin.readStreaming(init.io, &.{&buf});
if (n == 0) break;
// Replace \n with \r\n
@@ -35,7 +31,7 @@ pub fn main() !void {
});
// Write to stdout
var stdout_writer = std.fs.File.stdout().writer(&buf);
var stdout_writer = std.Io.File.stdout().writer(init.io, &buf);
const stdout = &stdout_writer.interface;
try stdout.print("{f}", .{formatter});
try stdout.flush();

View File

@@ -1,13 +1,9 @@
const std = @import("std");
const ghostty_vt = @import("ghostty-vt");
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var t: ghostty_vt.Terminal = try .init(alloc, .{ .cols = 80, .rows = 24 });
defer t.deinit(alloc);
pub fn main(init: std.process.Init) !void {
var t: ghostty_vt.Terminal = try .init(init.io, init.gpa, .{ .cols = 80, .rows = 24 });
defer t.deinit(init.gpa);
// Create a read-only VT stream for parsing terminal sequences
var stream = t.vtStream();
@@ -34,7 +30,7 @@ pub fn main() !void {
stream.nextSlice("Line A\r\nLine B\r\nLine C\r\n");
// Get the final terminal state as a plain string
const str = try t.plainString(alloc);
defer alloc.free(str);
const str = try t.plainString(init.gpa);
defer init.gpa.free(str);
std.debug.print("{s}\n", .{str});
}

View File

@@ -1,26 +1,20 @@
const std = @import("std");
const ghostty_vt = @import("ghostty-vt");
pub fn main() !void {
// Use a debug allocator so we get leak checking. You probably want
// to replace this for release builds.
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const alloc = gpa.allocator();
pub fn main(init: std.process.Init) !void {
// Initialize a terminal.
var t: ghostty_vt.Terminal = try .init(alloc, .{
var t: ghostty_vt.Terminal = try .init(init.io, init.gpa, .{
.cols = 6,
.rows = 40,
});
defer t.deinit(alloc);
defer t.deinit(init.gpa);
// Write some text. It'll wrap because this is too long for our
// columns size above (6).
try t.printString("Hello, World!");
// Get the plain string view of the terminal screen.
const str = try t.plainString(alloc);
defer alloc.free(str);
const str = try t.plainString(init.gpa);
defer init.gpa.free(str);
std.debug.print("{s}\n", .{str});
}

56
flake.lock generated
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": 1782657028,
"narHash": "sha256-PHTCpYZCMzJYS3phhywqRAZphKVr2zjvlGYa+H20ZZ4=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "b1f916ba052341edc1f80d4b2399f1092a4873ca",
"rev": "4ad9aaae70c9aaab504127f926c0fa9cfbc2b365",
"type": "github"
},
"original": {
@@ -38,11 +38,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1770537093,
"narHash": "sha256-XV30uo8tXuxdzuV8l3sojmlPRLd/8tpMsOp4lNzLGUo=",
"rev": "fef9403a3e4d31b0a23f0bacebbec52c248fbb51",
"lastModified": 1782545840,
"narHash": "sha256-CAi8oAZaE6pTkcYQBnnOlvmfMgG/p1AO0FohKKN3J7I=",
"rev": "3d46470bb3030020f7e1361f33514854f5bfa86d",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre942631.fef9403a3e4d/nixexprs.tar.xz"
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1023445.3d46470bb303/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
@@ -88,11 +88,11 @@
]
},
"locked": {
"lastModified": 1776789209,
"narHash": "sha256-G6B7Q4TXn7MZ1mB+f9rymjsYF5PLWoSvmbxijb/99bw=",
"lastModified": 1782609341,
"narHash": "sha256-OfCPJFS/2Z2ZyjE4adR7PL+ZEqvDQFi2XNI1L8s6wKU=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "14fe971844e841297ddd2ce9783d6892b467af39",
"rev": "5386fea92c45b154bbeb52d14ef5504f82eda86c",
"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": 1784117571,
"narHash": "sha256-Hvnmnuu0VpHiZl+8z+UkMoxC7JZJvRYBqu2Asb0ZJOE=",
"owner": "jcollie",
"repo": "zon2nix",
"rev": "a5a1d412ad1ab6305511997bbc92b3a9dd6cb784",
"rev": "0ce628fb78309cdb13d098ae9c6fdbf87c75d25b",
"type": "github"
},
"original": {

View File

@@ -74,23 +74,42 @@
};
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";
wraptest = pkgs.callPackage ./nix/pkgs/wraptest.nix {};
zon2nix = zon2nix;
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;
python3 = pkgs.python3.override {
self = pkgs.python3;
packageOverrides = pyfinal: pyprev: {
blessed = pyfinal.callPackage ./nix/pkgs/blessed.nix {};
ucs-detect = pyfinal.callPackage ./nix/pkgs/ucs-detect.nix {};
wcwidth = pyfinal.callPackage ./nix/pkgs/wcwidth.nix {};
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;
python3 = pkgs.python3.override {
self = pkgs.python3;
packageOverrides = pyfinal: pyprev: {
blessed = pyfinal.callPackage ./nix/pkgs/blessed.nix {};
ucs-detect = pyfinal.callPackage ./nix/pkgs/ucs-detect.nix {};
wcwidth = pyfinal.callPackage ./nix/pkgs/wcwidth.nix {};
};
};
};
};
inherit appstream libadwaita blueprint-compiler;
});
});
packages =

View File

@@ -1,4 +1,10 @@
[
{
"type": "archive",
"url": "https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz",
"dest": "vendor/p/aro-0.0.0-JSD1Qk8rOQDnuVcD4jAwMpHitA6pADRKzQ7M7hKRwxvD",
"sha256": "aa54487bd727e8fa36744ff0a824dedb2f4ca45f4c2d4d9d2437cf5fd3d0ad1a"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz",
@@ -37,9 +43,9 @@
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst",
"dest": "vendor/p/gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-",
"sha256": "d9bd4306f0081d8e4b848b6adfeabd2fab49822ee2b679eb4801dcedf5d60c48"
"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",
"dest": "vendor/p/gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp",
"sha256": "642e324b3644c00464a2161e4af497f73cbbe8ce02a4c0edb92aacb365748047"
},
{
"type": "archive",
@@ -85,9 +91,9 @@
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz",
"dest": "vendor/p/libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs",
"sha256": "6003ea6b96e4a518a128f932327d79a11bd30996b13b73baeb29916379487dd7"
"url": "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz",
"dest": "vendor/p/libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F",
"sha256": "e2b9b224cd78101156abb8f46479a51f6cce66c4815c632856e20a1440dd02b7"
},
{
"type": "archive",
@@ -131,23 +137,29 @@
"dest": "vendor/p/N-V-__8AANb6pwD7O1WG6L5nvD_rNMvnSc9Cpg1ijSlTYywv",
"sha256": "b52b6fcfc45e7fa69b1f06a1362c155473444e2cc09995556b156c53ba6657e3"
},
{
"type": "archive",
"url": "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
"dest": "vendor/p/translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
"sha256": "4fd2a2b21dbc49c22bea6854a59a7568dc77183760531ace3cf8da1267696afd"
},
{
"type": "archive",
"url": "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz",
"dest": "vendor/p/uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA",
"sha256": "7e76fc7fab1e7ac728c52b35bbb3e5b8c639841abfc7fe1a4bcb13050594bc9e"
},
{
"type": "git",
"url": "https://github.com/jacobsandlund/uucode",
"commit": "5f05f8f83a75caea201f12cc8ea32a2d82ea9732",
"dest": "vendor/p/uucode-0.1.0-ZZjBPj96QADXyt5sqwBJUnhaDYs_qBeeKijZvlRa0eqM"
"url": "https://github.com/rockorager/libvaxis.git",
"commit": "c1e1f23be38951c425cdf31af455ba23ef178940",
"dest": "vendor/p/vaxis-0.6.0-BWNV_MjFCQCs9UDHiRkrgw_ayeiPkzOe4xVbaAqXkUWW"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz",
"dest": "vendor/p/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9",
"sha256": "d0abee0f4f8bd6eae3c051777e16e7c42d8964aaaa015591c4e565703f465f95"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz",
"dest": "vendor/p/vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS",
"sha256": "2e72332bc89c5b541ec6e6bd48769e1f3fb757c4006f3d1af940b54f9b088ef6"
"url": "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz",
"dest": "vendor/p/vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka",
"sha256": "3ea050e905887c46bca1a2c18e41d6edf574c1aa6ebfbc5cf3c52846275e4fa8"
},
{
"type": "archive",
@@ -175,39 +187,39 @@
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz",
"dest": "vendor/p/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ",
"sha256": "69f21da2efd5ee0937fe55c4d09e48afc4fb2f91a01ef167c8c275ae046797f7"
"url": "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz",
"dest": "vendor/p/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR",
"sha256": "d48b117307f767ca379bf31bd171880e66dcafb89eca4a8f923222619992fcb4"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz",
"dest": "vendor/p/zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh",
"sha256": "3b015d928af04e9e26272bc15eb4dbb4d9a9d469eb6d290a0ddae673b77c4568"
"url": "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz",
"dest": "vendor/p/zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW",
"sha256": "ca4bc109fd9d97cedae2f5bdb879cc78c346d43c14a34e3a2abd2ee9208c8547"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz",
"dest": "vendor/p/zig_js-0.0.0-rjCAV-6GAADxFug7rDmPH-uM_XcnJ5NmuAMJCAscMjhi",
"sha256": "4c2018e56015d39504b8090386ad9ce9393f38380085d9c32373bf7e56fc73a3"
"url": "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz",
"dest": "vendor/p/zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj",
"sha256": "a2ff488a244301f4e59469a5232aad6869b01acc50f7d7ed2647c029a57d4b3b"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz",
"dest": "vendor/p/zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK",
"sha256": "dd84af737625356fcd722cb30909f3b2e8d702667cf579714aa7eabc0ac08ecc"
"url": "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz",
"dest": "vendor/p/zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi",
"sha256": "831eaec554bb23ef20229f1e705d8f53e8a29c829840bb8d0f5edb05c76d6df0"
},
{
"type": "archive",
"url": "https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz",
"dest": "vendor/p/wayland-0.5.0-dev-lQa1khrMAQDJDwYFKpdH3HizherB7sHo5dKMECfvxQHe",
"sha256": "4f146b735ed0d527f520e3bf71d3e93f72c3d0fa583ae8edd3a4851f7079124e"
"url": "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz",
"dest": "vendor/p/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX",
"sha256": "759a632e36a948e0e412d2d74a43a69d2f34e65d40289a16af7aa3725852ef25"
},
{
"type": "archive",
"url": "https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz",
"dest": "vendor/p/zigimg-0.1.0-8_eo2vHnEwCIVW34Q14Ec-xUlzIoVg86-7FU2ypPtxms",
"sha256": "2c1ed76ba2b35514544b0c27c9633ecba7c31be9080e37e7a010c93b5a1bc553"
"type": "git",
"url": "https://github.com/zigimg/zigimg",
"commit": "d695acd97c02e57bb151e8f659d1280f5cd6ca70",
"dest": "vendor/p/zigimg-0.1.0-8_eo2oyaFwBZwJpmqPkCfVXWBrHcqbYwmrp1I6bTD3lI"
},
{
"type": "archive",

View File

@@ -79,7 +79,7 @@ elif [ "$1" != "--update" ]; then
exit 1
fi
zon2nix "$BUILD_ZIG_ZON" --15 --nix "$WORK_DIR/build.zig.zon.nix" --txt "$WORK_DIR/build.zig.zon.txt" --json "$WORK_DIR/build.zig.zon.json" --flatpak "$WORK_DIR/zig-packages.json"
zon2nix "$BUILD_ZIG_ZON" --16 --nix "$WORK_DIR/build.zig.zon.nix" --txt "$WORK_DIR/build.zig.zon.txt" --json "$WORK_DIR/build.zig.zon.json" --flatpak "$WORK_DIR/zig-packages.json"
alejandra --quiet "$WORK_DIR/build.zig.zon.nix"
prettier --log-level warn --write "$WORK_DIR/build.zig.zon.json"
prettier --log-level warn --write "$WORK_DIR/zig-packages.json"

View File

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

View File

@@ -11,7 +11,7 @@
versionCheckHook,
darwin,
xcbuild,
zig_0_15,
zig_0_16,
revision ? "dirty",
optimize ? "Debug",
simd ? true,
@@ -59,7 +59,7 @@ stdenv.mkDerivation (finalAttrs: {
[
git
pkg-config
zig_0_15
zig_0_16
]
++ lib.optionals stdenv.hostPlatform.isDarwin [
darwin.cctools
@@ -264,7 +264,7 @@ stdenv.mkDerivation (finalAttrs: {
meta = {
homepage = "https://ghostty.org";
license = lib.licenses.mit;
platforms = zig_0_15.meta.platforms;
platforms = zig_0_16.meta.platforms;
pkgConfigModules = [
"libghostty-vt"
"libghostty-vt-static"

View File

@@ -10,7 +10,7 @@
git,
ncurses,
pkg-config,
zig_0_15,
zig_0_16,
pandoc,
revision ? "dirty",
optimize ? "Debug",
@@ -63,7 +63,7 @@ in
ncurses
pandoc
pkg-config
zig_0_15
zig_0_16
gobject-introspection
wrapGAppsHook4
blueprint-compiler

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

@@ -1,6 +1,31 @@
const std = @import("std");
const builtin = @import("builtin");
// The cache. This always uses b.allocator and never frees memory
// (which is idiomatic for a Zig build exe). We cache the libc txt
// file we create because it is expensive to generate (subprocesses).
pub const Cache = struct {
const Key = struct {
arch: std.Target.Cpu.Arch,
os: std.Target.Os.Tag,
abi: std.Target.Abi,
};
pub const Value = union(enum) {
native: struct {
libc: std.Build.LazyPath,
framework: []const u8,
system_include: []const u8,
library: []const u8,
},
cross: struct {
libc: std.Build.LazyPath,
},
};
var map: std.AutoHashMapUnmanaged(Key, ?Value) = .{};
};
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
@@ -8,40 +33,11 @@ pub fn build(b: *std.Build) !void {
_ = optimize;
}
/// Setup the step to point to the proper Apple SDK for libc and
/// Fetch or load the paths for the proper Apple SDK for libc and
/// frameworks. When running on a Darwin host, this uses the native
/// SDK installed on the system via `xcrun`. When cross-compiling from
/// a non-Darwin host, it falls back to Zig's bundled Darwin headers.
pub fn addPaths(
b: *std.Build,
step: *std.Build.Step.Compile,
) !void {
// The cache. This always uses b.allocator and never frees memory
// (which is idiomatic for a Zig build exe). We cache the libc txt
// file we create because it is expensive to generate (subprocesses).
const Cache = struct {
const Key = struct {
arch: std.Target.Cpu.Arch,
os: std.Target.Os.Tag,
abi: std.Target.Abi,
};
const Value = union(enum) {
native: struct {
libc: std.Build.LazyPath,
framework: []const u8,
system_include: []const u8,
library: []const u8,
},
cross: struct {
libc: std.Build.LazyPath,
},
};
var map: std.AutoHashMapUnmanaged(Key, ?Value) = .{};
};
const target = step.rootModuleTarget();
pub fn pathsForTarget(b: *std.Build, target: std.Target) !Cache.Value {
const gop = try Cache.map.getOrPut(b.allocator, .{
.arch = target.cpu.arch,
.os = target.os.tag,
@@ -53,14 +49,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 = &target,
.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);
@@ -112,7 +112,7 @@ pub fn addPaths(
// Fall back to Zig's bundled Darwin headers for libc resolution.
const zig_lib_path = b.graph.zig_lib_directory.path.?;
const include_dir = b.pathJoin(&.{
zig_lib_path, "libc", "include", "any-macos-any",
zig_lib_path, "libc", "include", "any-darwin-any",
});
const wf = b.addWriteFiles();
@@ -129,7 +129,7 @@ pub fn addPaths(
gop.value_ptr.* = .{ .cross = .{ .libc = path } };
}
const value = gop.value_ptr.* orelse return switch (target.os.tag) {
return gop.value_ptr.* orelse return switch (target.os.tag) {
// Return a more descriptive error. Before we just returned the
// generic error but this was confusing a lot of community members.
// It costs us nothing in the build script to return something better.
@@ -139,8 +139,18 @@ pub fn addPaths(
.watchos => error.XcodeWatchOSSDKNotFound,
else => error.XcodeAppleSDKNotFound,
};
}
switch (value) {
/// Setup the step to point to the proper Apple SDK for libc and
/// frameworks. When running on a Darwin host, this uses the native
/// SDK installed on the system via `xcrun`. When cross-compiling from
/// a non-Darwin host, it falls back to Zig's bundled Darwin headers.
pub fn addPaths(
b: *std.Build,
step: *std.Build.Step.Compile,
) !void {
const target = step.rootModuleTarget();
switch (try pathsForTarget(b, target)) {
.native => |native| {
step.setLibCFile(native.libc);

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

@@ -17,12 +17,32 @@ pub fn build(b: *std.Build) !void {
.target = target,
.optimize = optimize,
});
// Needs the gtk.h header
module.linkSystemLibrary("gtk4", dynamic_link_opts);
if (b.systemIntegrationOption("gtk4-layer-shell", .{})) {
module.linkSystemLibrary("gtk4-layer-shell-0", dynamic_link_opts);
} else {
translate: {
const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate;
const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate;
const Translator = translate_c.Translator;
const link_system_libs_full: [2]Translator.LinkSystemLib = .{
.{ .name = "gtk4", .options = dynamic_link_opts },
.{ .name = "gtk4-layer-shell-0", .options = dynamic_link_opts },
};
const headers = Translator.init(translate_c_dep, .{
.c_source_file = b.addWriteFiles().add("c.h",
\\#include <gtk4-layer-shell.h>
),
.target = target,
.optimize = optimize,
.link_system_libs = if (b.systemIntegrationOption("gtk4-layer-shell", .{}))
&link_system_libs_full
else
link_system_libs_full[0..1],
});
module.addImport("c", headers.mod);
}
if (!b.systemIntegrationOption("gtk4-layer-shell", .{})) {
_ = try buildLib(b, module, .{
.target = target,
.optimize = optimize,
@@ -42,6 +62,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 +73,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 +112,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 +133,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/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
.lazy = true,
},
},
}

View File

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

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
const Error = @import("errors.zig").Error;
/// Data type holding the memory modes available to client programs.

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
const common = @import("common.zig");
const Error = @import("errors.zig").Error;
const Direction = common.Direction;

View File

@@ -1,6 +1,8 @@
const std = @import("std");
const apple_sdk = @import("apple_sdk");
const root_build_container = @This();
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
@@ -8,31 +10,6 @@ pub fn build(b: *std.Build) !void {
const coretext_enabled = b.option(bool, "enable-coretext", "Build coretext") orelse false;
const freetype_enabled = b.option(bool, "enable-freetype", "Build freetype") orelse true;
const freetype = b.dependency("freetype", .{
.target = target,
.optimize = optimize,
.@"enable-libpng" = true,
});
const macos = b.dependency("macos", .{ .target = target, .optimize = optimize });
const module = harfbuzz: {
const module = b.addModule("harfbuzz", .{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "freetype", .module = freetype.module("freetype") },
.{ .name = "macos", .module = macos.module("macos") },
},
});
const options = b.addOptions();
options.addOption(bool, "coretext", coretext_enabled);
options.addOption(bool, "freetype", freetype_enabled);
module.addOptions("build_options", options);
break :harfbuzz module;
};
// 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.
@@ -41,33 +18,63 @@ pub fn build(b: *std.Build) !void {
.search_strategy = .mode_first,
};
const test_exe = b.addTest(.{
.name = "test",
.root_module = b.createModule(.{
const freetype = b.dependency("freetype", .{
.target = target,
.optimize = optimize,
.@"enable-libpng" = true,
});
const module = harfbuzz: {
const module = b.addModule("harfbuzz", .{
.root_source_file = b.path("main.zig"),
.target = target,
.optimize = optimize,
}),
.imports = if (target.result.os.tag.isDarwin())
&.{
.{ .name = "freetype", .module = freetype.module("freetype") },
.{
.name = "macos",
.module = b.dependency("macos", .{ .target = target, .optimize = optimize })
.module("macos"),
},
}
else
&.{
.{ .name = "freetype", .module = freetype.module("freetype") },
},
});
try HarfBuzzC.addImportToModule(b, module, .{
.target = target,
.optimize = optimize,
.harfbuzz = if (b.systemIntegrationOption("harfbuzz", .{})) .{ .dynamic = dynamic_link_opts } else .static,
.coretext = coretext_enabled,
.freetype = if (freetype_enabled) ft: {
break :ft if (b.systemIntegrationOption("freetype", .{}))
.{ .dynamic = dynamic_link_opts }
else
.static;
} else null,
});
const options = b.addOptions();
options.addOption(bool, "coretext", coretext_enabled);
options.addOption(bool, "freetype", freetype_enabled);
module.addOptions("build_options", options);
break :harfbuzz module;
};
const test_exe = b.addTest(.{
.name = "test",
.root_module = module,
});
{
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);
} else {
test_exe.linkLibrary(freetype.artifact("freetype"));
}
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);
}
const tests_run = b.addRunArtifact(test_exe);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&tests_run.step);
if (b.systemIntegrationOption("harfbuzz", .{})) {
module.linkSystemLibrary("harfbuzz", dynamic_link_opts);
test_exe.linkSystemLibrary2("harfbuzz", dynamic_link_opts);
} else {
const lib = try buildLib(b, module, .{
if (!b.systemIntegrationOption("harfbuzz", .{})) {
const lib = try buildLib(b, .{
.target = target,
.optimize = optimize,
@@ -77,11 +84,11 @@ pub fn build(b: *std.Build) !void {
.dynamic_link_opts = dynamic_link_opts,
});
test_exe.linkLibrary(lib);
test_exe.root_module.linkLibrary(lib);
}
}
fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Build.Step.Compile {
fn buildLib(b: *std.Build, options: anytype) !*std.Build.Step.Compile {
const target = options.target;
const optimize = options.optimize;
@@ -99,18 +106,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,30 +159,20 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
});
if (b.systemIntegrationOption("freetype", .{})) {
lib.linkSystemLibrary2("freetype2", dynamic_link_opts);
module.linkSystemLibrary("freetype2", dynamic_link_opts);
lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts);
} else {
lib.linkLibrary(freetype.artifact("freetype"));
if (freetype.builder.lazyDependency(
"freetype",
.{},
)) |freetype_dep| {
module.addIncludePath(freetype_dep.path("include"));
}
lib.root_module.linkLibrary(freetype.artifact("freetype"));
}
}
if (coretext_enabled) {
try flags.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"});
lib.linkFramework("CoreText");
module.linkFramework("CoreText", .{});
lib.root_module.linkFramework("CoreText", .{});
}
if (b.lazyDependency("harfbuzz", .{})) |upstream| {
lib.addIncludePath(upstream.path("src"));
module.addIncludePath(upstream.path("src"));
lib.addCSourceFile(.{
lib.root_module.addIncludePath(upstream.path("src"));
lib.root_module.addCSourceFile(.{
.file = upstream.path("src/harfbuzz.cc"),
.flags = flags.items,
});
@@ -192,3 +187,173 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu
return lib;
}
const HarfBuzzC = struct {
const AddImportToModuleOptions = struct {
const LinkMode = union(enum) {
static,
dynamic: std.Build.Module.LinkSystemLibraryOptions,
};
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
harfbuzz: LinkMode,
coretext: bool,
freetype: ?LinkMode,
};
fn fmtInclude(w: *std.Io.Writer, name: []const u8, mode: AddImportToModuleOptions.LinkMode) !void {
if (mode == .dynamic) {
try w.print("#include <{s}>\n", .{name});
} else {
try w.print("#include \"{s}\"\n", .{name});
}
}
fn addImportToModule(
b: *std.Build,
module: *std.Build.Module,
options: AddImportToModuleOptions,
) !void {
// TODO: There's a decent amount of duplication here right now.
// Basically we want to mirror what we're passing in buildLib to make
// sure that the translation is generated as correct as possible.
// Eventually, we want to try and unravel this as much as we can, to
// the point that ultimately all C flags and even link options are
// self-contained in the translation artifact.
//
// This is a bit tricky right now as there are situations where we
// provide a static library built straight off of the C file, hence the
// duplication.
//
// NOTE: This function de-allocates nothing as b.allocator is an arena
// (unfortunately not documented, but a cursory search in various
// communities or the issue trackers should turn up confirmation).
const translate_c = b.lazyImport(root_build_container, "translate_c") orelse return;
const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse return;
const c_source = c_source: {
var source_builder: std.Io.Writer.Allocating = .init(b.allocator);
try fmtInclude(&source_builder.writer, "hb.h", options.harfbuzz);
if (options.coretext) {
try fmtInclude(&source_builder.writer, "hb-coretext.h", options.harfbuzz);
}
if (options.freetype != null) {
try fmtInclude(&source_builder.writer, "hb-ft.h", options.harfbuzz);
}
break :c_source source_builder.written();
};
// Assemble system libs
const system_libs = libs: {
var libs_builder: std.ArrayList(translate_c.Translator.LinkSystemLib) = .empty;
if (options.harfbuzz == .dynamic)
try libs_builder.append(b.allocator, .{ .name = "harfbuzz", .options = options.harfbuzz.dynamic });
if (options.freetype) |ft| {
if (ft == .dynamic)
try libs_builder.append(b.allocator, .{ .name = "freetype2", .options = ft.dynamic });
}
break :libs libs_builder.items;
};
// Assemble flags
const flags = flags: {
var flag_builder: std.ArrayList([]const u8) = .empty;
try flag_builder.appendSlice(b.allocator, &.{
"-DHAVE_STDBOOL_H",
});
// 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.
if (options.target.result.abi == .msvc) {
try flag_builder.appendSlice(b.allocator, &.{
"-fno-sanitize=undefined",
"-fno-sanitize-trap=undefined",
});
}
if (options.target.result.os.tag != .windows) {
try flag_builder.appendSlice(b.allocator, &.{
"-DHAVE_UNISTD_H",
"-DHAVE_SYS_MMAN_H",
"-DHAVE_PTHREAD=1",
});
}
// Freetype flags/non-system include paths
if (options.freetype != null) {
try flag_builder.appendSlice(b.allocator, &.{
"-DHAVE_FREETYPE=1",
// Let's just assume a new freetype
"-DHAVE_FT_GET_VAR_BLEND_COORDINATES=1",
"-DHAVE_FT_SET_VAR_BLEND_COORDINATES=1",
"-DHAVE_FT_DONE_MM_VAR=1",
"-DHAVE_FT_GET_TRANSFORM=1",
});
}
// Coretext
if (options.coretext) {
try flag_builder.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"});
try flag_builder.appendSlice(b.allocator, &.{"-fblocks"});
}
break :flags flag_builder.items;
};
const hb_c: translate_c.Translator = .init(translate_c_dep, .{
.c_source_file = b.addWriteFiles().add(
"hb_c.h",
c_source,
),
.target = options.target,
.optimize = options.optimize,
.link_libc = true,
.link_system_libs = system_libs,
.libc_file = if (options.target.result.os.tag.isDarwin()) libc_file: {
switch (try @import("apple_sdk").pathsForTarget(b, options.target.result)) {
inline else => |paths| break :libc_file paths.libc,
}
} else null,
.extra_args = flags,
});
if (options.harfbuzz == .static) {
if (b.lazyDependency("harfbuzz", .{})) |upstream| {
hb_c.addIncludePath(upstream.path("src"));
}
}
// Freetype non-system include paths
if (options.freetype) |freetype_enabled| {
if (freetype_enabled == .static) {
const ft_dep = b.dependency("freetype", .{
.target = options.target,
.optimize = options.optimize,
.@"enable-libpng" = true,
});
if (ft_dep.builder.lazyDependency(
"freetype",
.{},
)) |freetype_lazy_dep| {
hb_c.addIncludePath(freetype_lazy_dep.path("include"));
}
}
}
// Coretext
if (options.coretext) {
// NOTE: We should not necessarily need to add this directly to C
// translation, so we just add it to the module.
hb_c.mod.linkFramework("CoreText", .{});
}
module.addImport("hb_c", hb_c.mod);
}
};

View File

@@ -4,6 +4,12 @@
.fingerprint = 0xbd60917cd18865d8,
.paths = .{""},
.dependencies = .{
.translate_c = .{
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
.lazy = true,
},
// harfbuzz/harfbuzz
.harfbuzz = .{
.url = "https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz",

View File

@@ -1,8 +0,0 @@
const builtin = @import("builtin");
const build_options = @import("build_options");
pub const c = @cImport({
@cInclude("hb.h");
if (build_options.freetype) @cInclude("hb-ft.h");
if (build_options.coretext) @cInclude("hb-coretext.h");
});

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
/// The direction of a text segment or buffer.
///

View File

@@ -1,6 +1,6 @@
const macos = @import("macos");
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
const Face = @import("face.zig").Face;
const Font = @import("font.zig").Font;
const Error = @import("errors.zig").Error;

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
/// A font face is an object that represents a single face from within a font family.
///

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
const Face = @import("face.zig").Face;
const Error = @import("errors.zig").Error;

View File

@@ -1,6 +1,6 @@
const freetype = @import("freetype");
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
const Face = @import("face.zig").Face;
const Font = @import("font.zig").Font;
const Error = @import("errors.zig").Error;

View File

@@ -7,7 +7,7 @@ const font = @import("font.zig");
const shapepkg = @import("shape.zig");
const versionpkg = @import("version.zig");
pub const c = @import("c.zig").c;
pub const c = @import("hb_c");
pub const freetype = @import("freetype.zig");
pub const coretext = @import("coretext.zig");
pub const MemoryMode = blob.MemoryMode;

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
const Font = @import("font.zig").Font;
const Buffer = @import("buffer.zig").Buffer;
const Feature = @import("common.zig").Feature;

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const c = @import("c.zig").c;
const c = @import("hb_c");
pub const Version = struct {
major: u32,

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(""));
}
@@ -102,7 +101,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",
@@ -127,7 +126,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

@@ -2,6 +2,66 @@ const std = @import("std");
const builtin = @import("builtin");
const apple_sdk = @import("apple_sdk");
const Framework = struct {
const Tag = enum { all, macos };
tag: Tag,
name: []const u8,
headers: []const []const u8,
};
const frameworks = [_]Framework{
.{ .tag = .all, .name = "CoreFoundation", .headers = &.{"CoreFoundation.h"} },
.{ .tag = .all, .name = "CoreGraphics", .headers = &.{"CoreGraphics.h"} },
.{ .tag = .all, .name = "CoreText", .headers = &.{"CoreText.h"} },
.{ .tag = .all, .name = "CoreVideo", .headers = &.{ "CoreVideo.h", "CVPixelBuffer.h" } },
.{ .tag = .all, .name = "QuartzCore", .headers = &.{"CALayer.h"} },
.{ .tag = .all, .name = "IOSurface", .headers = &.{"IOSurfaceRef.h"} },
.{ .tag = .macos, .name = "Carbon", .headers = &.{"Carbon.h"} },
};
const extra_headers = [_][]const u8{
"dispatch/dispatch.h",
"os/log.h",
"os/signpost.h",
};
const framework_header_fmt = "#include <{s}/{s}>\n";
const extra_header_fmt = "#include <{s}>\n";
fn cSourceLen(tag: Framework.Tag) usize {
var len: usize = 0;
for (frameworks) |framework| {
if (tag != .macos and framework.tag == .macos) continue;
for (framework.headers) |h| len += std.fmt.count(framework_header_fmt, .{ framework.name, h });
}
for (extra_headers) |h| len += std.fmt.count(extra_header_fmt, .{h});
return len;
}
fn genCSource(comptime tag: Framework.Tag) [cSourceLen(tag):0]u8 {
const len = cSourceLen(tag);
var buf: [len:0]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
for (frameworks) |framework| {
if (tag != .macos and framework.tag == .macos) continue;
for (framework.headers) |h| writer.print(framework_header_fmt, .{ framework.name, h }) catch unreachable;
}
for (extra_headers) |h| writer.print(extra_header_fmt, .{h}) catch unreachable;
buf[len] = 0;
return buf;
}
const c_source_macos = genCSource(.macos);
const c_source_other = genCSource(.all);
fn linkFrameworks(tag: Framework.Tag, module: *std.Build.Module) !void {
for (frameworks) |framework| {
if (tag != .macos and framework.tag == .macos) continue;
module.linkFramework(framework.name, .{});
}
}
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
@@ -12,6 +72,29 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
});
translate: {
const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate;
const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate;
const macos_c: translate_c.Translator = .init(translate_c_dep, .{
.c_source_file = b.addWriteFiles().add(
"macos_c.h",
if (target.result.os.tag == .macos) &c_source_macos else &c_source_other,
),
.target = target,
.optimize = optimize,
.libc_file = if (target.result.os.tag.isDarwin()) libc_file: {
switch (try apple_sdk.pathsForTarget(b, target.result)) {
inline else => |paths| break :libc_file paths.libc,
}
} else null,
});
// Blocks need to be enabled to use MacOS headers
macos_c.run.addArg("-fblocks");
module.addImport("macos_c", macos_c.mod);
}
const lib = b.addLibrary(.{
.name = "macos",
.root_module = b.createModule(.{
@@ -21,34 +104,18 @@ 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");
if (target.result.os.tag == .macos) {
lib.linkFramework("Carbon");
module.linkFramework("Carbon", .{});
}
if (target.result.os.tag.isDarwin()) {
module.linkFramework("CoreFoundation", .{});
module.linkFramework("CoreGraphics", .{});
module.linkFramework("CoreText", .{});
module.linkFramework("CoreVideo", .{});
module.linkFramework("QuartzCore", .{});
module.linkFramework("IOSurface", .{});
try apple_sdk.addPaths(b, lib);
inline for (.{ lib.root_module, module }) |mod| {
try linkFrameworks(if (target.result.os.tag == .macos) .macos else .all, mod);
}
try apple_sdk.addPaths(b, lib);
b.installArtifact(lib);
{
@@ -63,7 +130,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

@@ -5,5 +5,10 @@
.paths = .{""},
.dependencies = .{
.apple_sdk = .{ .path = "../apple-sdk" },
.translate_c = .{
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
.lazy = true,
},
},
}

View File

@@ -10,25 +10,7 @@ pub const text = @import("text.zig");
pub const video = @import("video.zig");
pub const iosurface = @import("iosurface.zig");
// All of our C imports consolidated into one place. We used to
// import them one by one in each package but Zig 0.14 has some
// kind of issue with that I wasn't able to minimize.
pub const c = @cImport({
@cInclude("CoreFoundation/CoreFoundation.h");
@cInclude("CoreGraphics/CoreGraphics.h");
@cInclude("CoreText/CoreText.h");
@cInclude("CoreVideo/CoreVideo.h");
@cInclude("CoreVideo/CVPixelBuffer.h");
@cInclude("QuartzCore/CALayer.h");
@cInclude("IOSurface/IOSurfaceRef.h");
@cInclude("dispatch/dispatch.h");
@cInclude("os/log.h");
@cInclude("os/signpost.h");
if (builtin.os.tag == .macos) {
@cInclude("Carbon/Carbon.h");
}
});
pub const c = @import("macos_c");
test {
@import("std").testing.refAllDecls(@This());

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");
@@ -70,11 +65,15 @@ pub fn build(b: *std.Build) !void {
try flags.append(b.allocator, "-std=c++17");
}
if (target.result.os.tag == .freebsd or target.result.abi == .musl) {
if (target.result.os.tag == .freebsd or
target.result.abi == .musl or
target.result.abi == .android or
target.result.abi == .androideabi)
{
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,5 +1,38 @@
const std = @import("std");
// 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(.{});
const optimize = b.standardOptimizeOption(.{});
@@ -15,25 +48,41 @@ 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,
translate: {
const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate;
const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate;
const wuffs_c: translate_c.Translator = .init(translate_c_dep, .{
.c_source_file = b.addWriteFiles().add("wuffs_c.h", &wuffs_c_source),
.target = target,
.optimize = optimize,
.libc_file = if (target.result.os.tag.isDarwin()) libc_file: {
switch (try @import("apple_sdk").pathsForTarget(b, target.result)) {
inline else => |paths| break :libc_file paths.libc,
}
} else null,
});
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,12 @@
.version = "0.0.0",
.fingerprint = 0x67c0c059de921c4f,
.dependencies = .{
.translate_c = .{
.lazy = true,
.url = "https://codeberg.org/vancluever/translate-c/archive/a916baf225a3cfc93a982225462db612604ae290.tar.gz",
.hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB",
},
// 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,3 +1,4 @@
const builtin = @import("builtin");
const std = @import("std");
pub const png = @import("png.zig");
@@ -16,5 +17,19 @@ pub const ImageData = struct {
};
test {
std.testing.refAllDeclsRecursive(@This());
refAllDeclsRecursive(@This());
}
/// Copied from 0.15.2 stdlib (MIT license).
fn refAllDeclsRecursive(comptime T: type) void {
if (!builtin.is_test) return;
inline for (comptime std.meta.declarations(T)) |decl| {
if (@TypeOf(@field(T, decl.name)) == type) {
switch (@typeInfo(@field(T, decl.name))) {
.@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(@field(T, decl.name)),
else => {},
}
}
_ = &@field(T, decl.name);
}
}

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

@@ -15,6 +15,7 @@ const Config = configpkg.Config;
const BlockingQueue = @import("datastruct/main.zig").BlockingQueue;
const renderer = @import("renderer.zig");
const font = @import("font/main.zig");
const global = @import("global.zig");
const log = std.log.scoped(.app);
@@ -56,7 +57,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 +96,7 @@ pub fn init(
self.* = .{
.alloc = alloc,
.surfaces = .{},
.surfaces = .empty,
.mailbox = .{},
.font_grid_set = font_grid_set,
.config_conditional_state = .{},
@@ -236,7 +237,7 @@ pub fn needsConfirmQuit(self: *const App) bool {
/// Drain the mailbox.
fn drainMailbox(self: *App, rt_app: *apprt.App) !void {
while (self.mailbox.pop()) |message| {
while (self.mailbox.pop(global.io())) |message| {
if (comptime std.log.logEnabled(.debug, .app)) {
switch (message) {
// these tend to be way too verbose for normal debugging
@@ -566,7 +567,7 @@ pub const Mailbox = struct {
/// Send a message to the surface.
pub fn push(self: Mailbox, msg: Message, timeout: Queue.Timeout) Queue.Size {
const result = self.mailbox.push(msg, timeout);
const result = self.mailbox.push(global.io(), msg, timeout);
// Wake up our app loop
self.rt_app.wakeup();

View File

@@ -14,12 +14,15 @@
//! * posix_spawn is used for Mac, but doesn't support the necessary
//! features for tty setup.
//!
//!
//! TODO: This may have changed a lot now with the new I/O implementations in
//! >= 0.16.0, so this might warrant a recheck.
const Command = @This();
const std = @import("std");
const builtin = @import("builtin");
const configpkg = @import("config.zig");
const global_state = &@import("global.zig").state;
const global = @import("global.zig");
const internal_os = @import("os/main.zig");
const windows = internal_os.windows;
const TempDir = internal_os.TempDir;
@@ -29,8 +32,8 @@ const posix = std.posix;
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.
@@ -98,7 +101,7 @@ rt_post_fork_info: RtPostForkInfo,
/// If set, then the process will be created attached to this pseudo console.
/// `stdin`, `stdout`, and `stderr` will be ignored if set.
pseudo_console: if (builtin.os.tag == .windows) ?windows.exp.HPCON else void =
pseudo_console: if (builtin.os.tag == .windows) ?windows.HPCON else void =
if (builtin.os.tag == .windows) null else {},
/// User data that is sent to the callback. Set with setData and getData
@@ -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 = posix.system.fork();
if (pid != 0) {
// Parent, return immediately.
@@ -205,37 +208,68 @@ fn startPosix(self: *Command, arena: Allocator) !void {
if (self.stderr) |f| setupFd(f.handle, posix.STDERR_FILENO) catch
return error.ExecFailedInChild;
// Setup our working directory
if (self.cwd) |cwd| posix.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
// crash the entire process if this fails so we ignore it.
// We don't log because that'll show up in the output.
};
// Setup our working directory.
//
// NOTE: 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 crash the entire process if this fails so we
// ignore it. We don't log because that'll show up in the output.
if (self.cwd) |cwd| _ = posix.system.chdir(cwd);
// Restore any rlimits that were set by Ghostty. This might fail but
// any failures are ignored (its best effort).
global_state.rlimits.restore();
global.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: posix.E = execve: {
// This functionality has been taken from Zig stdlib, a simplified
// version of the exec bits with PATH search so that we can just
// offload to execve below.
const file_slice = std.mem.sliceTo(self.path, 0);
if (std.mem.findScalar(u8, file_slice, '/') != null) {
break :execve posix.errno(posix.system.execve(self.path, argsZ, envp));
}
var path_expanded_buf: [std.fs.max_path_bytes]u8 = undefined;
const PATH = global.environ().getPosix("PATH") orelse "/usr/local/bin:/bin/:/usr/bin";
var it = std.mem.tokenizeScalar(u8, PATH, ':');
var err: posix.system.E = undefined;
var seen_eacces = false;
while (it.next()) |search_path| {
const path_len = search_path.len + file_slice.len + 1;
if (path_expanded_buf.len < path_len + 1) return error.NameTooLong;
@memcpy(path_expanded_buf[0..search_path.len], search_path);
path_expanded_buf[search_path.len] = '/';
@memcpy(path_expanded_buf[search_path.len + 1 ..][0..file_slice.len], file_slice);
path_expanded_buf[path_len] = 0;
const full_path = path_expanded_buf[0..path_len :0].ptr;
// Replace here, switch on error (any error means that replace
// failed, but we might need to retry).
err = posix.errno(posix.system.execve(full_path, argsZ, envp));
switch (err) {
.ACCES => seen_eacces = true,
.NOENT, .NOTDIR => {},
else => break :execve err,
}
}
if (seen_eacces) break :execve .ACCES;
break :execve err;
};
// 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(global.io(), &stderr_buf);
const stderr = &stderr_writer.interface;
switch (err) {
error.FileNotFound => stderr.print(
posix.system.E.NOENT => stderr.print(
\\Requested executable not found. Please verify the command is on
\\the PATH and try again.
\\
@@ -243,11 +277,11 @@ fn startPosix(self: *Command, arena: Allocator) !void {
.{},
) catch {},
else => stderr.print(
\\exec syscall failed with unexpected error: {}
else => |e| stderr.print(
\\exec syscall failed with unexpected error: E{s}
\\
,
.{err},
.{@tagName(e)},
) catch {},
}
stderr.flush() catch {};
@@ -275,15 +309,37 @@ fn startWindows(self: *Command, arena: Allocator) !void {
const env_w = if (self.env) |env_map| try createWindowsEnvBlock(arena, env_map) else null;
const any_null_fd = self.stdin == null or self.stdout == null or self.stderr == null;
const null_fd = if (any_null_fd) try windows.OpenFile(
&[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' },
.{
.access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE,
.share_access = windows.FILE_SHARE_READ,
.creation = windows.OPEN_EXISTING,
},
) else null;
defer if (null_fd) |fd| posix.close(fd);
const null_fd = if (any_null_fd) null_fd: {
// path = "\Device\Null"
const path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' };
var path_unicode_string: windows.UNICODE_STRING = .init(&path);
var attrs: windows.OBJECT_ATTRIBUTES = .{ .ObjectName = &path_unicode_string };
var fd: windows.HANDLE = undefined;
var io_status: windows.IO_STATUS_BLOCK = undefined; // unused
const result = windows.exp.ntdll.NtCreateFile(
&fd,
.{ .GENERIC = .{ .READ = true }, .STANDARD = .{ .SYNCHRONIZE = true } },
&attrs,
&io_status,
null,
windows.FILE_ATTRIBUTE_NORMAL,
windows.FILE_SHARE_READ,
windows.OPEN_EXISTING,
windows.FILE_NON_DIRECTORY_FILE,
null,
0,
);
if (result != .SUCCESS) {
return windows.unexpectedStatus(result);
}
break :null_fd fd;
} else null;
defer {
if (null_fd) |fd| _ = windows.exp.kernel32.CloseHandle(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,
@@ -304,17 +360,17 @@ fn startWindows(self: *Command, arena: Allocator) !void {
1,
0,
&attribute_list_size,
) == 0) return windows.unexpectedError(windows.kernel32.GetLastError());
) == windows.FALSE) return windows.unexpectedError(windows.GetLastError());
if (windows.exp.kernel32.UpdateProcThreadAttribute(
attribute_list_buf.ptr,
0,
windows.exp.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
windows.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
pseudo_console,
@sizeOf(windows.exp.HPCON),
@sizeOf(windows.HPCON),
null,
null,
) == 0) return windows.unexpectedError(windows.kernel32.GetLastError());
) == windows.FALSE) return windows.unexpectedError(windows.GetLastError());
break :b .{ attribute_list_buf.ptr, null, null, null };
} else b: {
@@ -324,9 +380,9 @@ fn startWindows(self: *Command, arena: Allocator) !void {
break :b .{ null, stdin, stdout, stderr };
};
var startup_info_ex = windows.exp.STARTUPINFOEX{
var startup_info_ex = windows.STARTUPINFOEX{
.StartupInfo = .{
.cb = if (attribute_list != null) @sizeOf(windows.exp.STARTUPINFOEX) else @sizeOf(windows.STARTUPINFOW),
.cb = if (attribute_list != null) @sizeOf(windows.STARTUPINFOEX) else @sizeOf(windows.STARTUPINFOW),
.hStdError = stderr,
.hStdOutput = stdout,
.hStdInput = stdin,
@@ -348,8 +404,8 @@ fn startWindows(self: *Command, arena: Allocator) !void {
.lpAttributeList = attribute_list,
};
var flags: windows.DWORD = windows.exp.CREATE_UNICODE_ENVIRONMENT;
if (attribute_list != null) flags |= windows.exp.EXTENDED_STARTUPINFO_PRESENT;
var flags: windows.DWORD = windows.CREATE_UNICODE_ENVIRONMENT;
if (attribute_list != null) flags |= windows.EXTENDED_STARTUPINFO_PRESENT;
var process_information: windows.PROCESS_INFORMATION = undefined;
if (windows.exp.kernel32.CreateProcessW(
@@ -363,21 +419,18 @@ fn startWindows(self: *Command, arena: Allocator) !void {
if (cwd_w) |w| w.ptr else null,
@ptrCast(&startup_info_ex.StartupInfo),
&process_information,
) == 0) return windows.unexpectedError(windows.kernel32.GetLastError());
) == windows.FALSE) return windows.unexpectedError(windows.GetLastError());
self.pid = process_information.hProcess;
}
fn setupFd(src: File.Handle, target: i32) !void {
switch (builtin.os.tag) {
.linux => {
// We use dup3 so that we can clear CLO_ON_EXEC. We do NOT want this
// file descriptor to be closed on exec since we're exactly exec-ing after
// this.
const PosixCall = struct {
fn f(func: anytype, args: anytype) !usize {
while (true) {
const rc = linux.dup3(src, target, 0);
const rc = @call(.auto, func, args);
switch (posix.errno(rc)) {
.SUCCESS => break,
.SUCCESS => return @intCast(rc),
.INTR => continue,
.AGAIN, .ACCES => return error.Locked,
.BADF => unreachable,
@@ -391,16 +444,28 @@ fn setupFd(src: File.Handle, target: i32) !void {
else => |err| return posix.unexpectedErrno(err),
}
}
}
};
switch (builtin.os.tag) {
.linux => {
// We use dup3 so that we can clear CLO_ON_EXEC. We do NOT want this
// file descriptor to be closed on exec since we're exactly exec-ing after
// this.
_ = try PosixCall.f(linux.dup3, .{ src, target, 0 });
},
.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 PosixCall.f(posix.system.fcntl, .{ src, posix.F.GETFD });
if (flags & posix.FD_CLOEXEC != 0) {
_ = try posix.fcntl(src, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC));
_ = try PosixCall.f(
posix.system.fcntl,
.{ src, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC) },
);
}
try posix.dup2(src, target);
_ = try PosixCall.f(posix.system.dup2, .{ src, target });
},
else => @compileError("unsupported platform"),
}
@@ -411,21 +476,29 @@ pub fn wait(self: Command, block: bool) !Exit {
if (comptime builtin.os.tag == .windows) {
// Block until the process exits. This returns immediately if the
// process already exited.
const result = windows.kernel32.WaitForSingleObject(self.pid.?, windows.INFINITE);
//
// NOTE: We can use the pid directly as posix.system.pid_t is still an
// alias for a handle under Windows. We might want to keep an eye on if
// this changes, though.
const result = windows.exp.kernel32.WaitForSingleObject(self.pid.?, windows.INFINITE);
if (result == windows.WAIT_FAILED) {
return windows.unexpectedError(windows.kernel32.GetLastError());
return windows.unexpectedError(windows.GetLastError());
}
var exit_code: windows.DWORD = undefined;
const has_code = windows.kernel32.GetExitCodeProcess(self.pid.?, &exit_code) != 0;
const has_code = windows.exp.kernel32.GetExitCodeProcess(self.pid.?, &exit_code) != windows.FALSE;
if (!has_code) {
return windows.unexpectedError(windows.kernel32.GetLastError());
return windows.unexpectedError(windows.GetLastError());
}
return .{ .Exited = exit_code };
}
const res = if (block) posix.waitpid(self.pid.?, 0) else res: {
const status = if (block) wait_block: {
var status: c_int = undefined;
_ = posix.system.waitpid(self.pid.?, &status, 0);
break :wait_block status;
} else wait_nohang: {
// 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,12 +507,13 @@ 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);
if (res.pid != 0) break :res res;
var status: c_int = undefined;
const pid = posix.system.waitpid(self.pid.?, &status, posix.system.W.NOHANG);
if (pid != 0) break :wait_nohang status;
}
};
return .init(res.status);
return .init(@bitCast(status));
}
/// Sets command->data to data.
@@ -587,7 +661,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 +712,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,27 +771,31 @@ 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(
if (windows.exp.kernel32.SetHandleInformation(
file.handle,
windows.HANDLE_FLAG_INHERIT,
windows.HANDLE_FLAG_INHERIT,
);
) == windows.FALSE) {
return windows.unexpectedError(windows.GetLastError());
}
}
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(
if (windows.exp.kernel32.SetHandleInformation(
file.handle,
windows.HANDLE_FLAG_INHERIT,
windows.HANDLE_FLAG_INHERIT,
);
) == windows.FALSE) {
return windows.unexpectedError(windows.GetLastError());
}
}
return file;
@@ -726,8 +804,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 +834,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 +848,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 +884,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 +903,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 +935,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 +965,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 +997,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

@@ -20,7 +20,7 @@ const builtin = @import("builtin");
const assert = @import("quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const global_state = &@import("global.zig").state;
const global = @import("global.zig");
const oni = @import("oniguruma");
const crash = @import("crash/main.zig");
const unicode = @import("unicode/main.zig");
@@ -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
@@ -559,8 +559,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
@@ -581,7 +581,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 = global.io() };
const rng = rng_impl.interface();
break :candidate rng.int(u64);
};
if (candidate == 0) continue;
break :id candidate;
}
@@ -631,13 +635,12 @@ pub fn init(
var env = rt_surface.defaultTermioEnv() catch |err| env: {
// 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);
break :env global.environMap() catch 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(
@@ -654,7 +657,7 @@ pub fn init(
.shell_integration_features = config.@"shell-integration-features",
.cursor_blink = config.@"cursor-style-blink",
.working_directory = if (config.@"working-directory") |wd| wd.value() else null,
.resources_dir = global_state.resources_dir.host(),
.resources_dir = global.resourcesDir().host(),
.term = config.term,
.rt_pre_exec_info = .init(config),
.rt_post_fork_info = .init(config),
@@ -717,7 +720,7 @@ pub fn init(
rendererpkg.Thread.threadMain,
.{&self.renderer_thread},
);
self.renderer_thr.setName("renderer") catch {};
self.renderer_thr.setName(global.io(), "renderer") catch {};
// Start our IO thread
self.io_thr = try std.Thread.spawn(
@@ -725,7 +728,7 @@ pub fn init(
termio.Thread.threadMain,
.{ &self.io_thread, &self.io },
);
self.io_thr.setName("io") catch {};
self.io_thr.setName(global.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,
@@ -900,14 +903,14 @@ 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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
assert(self.renderer_state.inspector == null);
self.renderer_state.inspector = self.inspector;
}
// Notify our components we have an inspector active
_ = self.renderer_thread.mailbox.push(.{ .inspector = true }, .{ .forever = {} });
_ = self.renderer_thread.mailbox.push(global.io(), .{ .inspector = true }, .{ .forever = {} });
self.queueIo(.{ .inspector = true }, .unlocked);
}
@@ -917,14 +920,14 @@ 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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
assert(self.renderer_state.inspector != null);
self.renderer_state.inspector = null;
}
// Notify our components we have deactivated inspector
_ = self.renderer_thread.mailbox.push(.{ .inspector = false }, .{ .forever = {} });
_ = self.renderer_thread.mailbox.push(global.io(), .{ .inspector = false }, .{ .forever = {} });
self.queueIo(.{ .inspector = false }, .unlocked);
// Deinit the inspector
@@ -948,8 +951,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
break :true !self.io.terminal.cursorIsAtPrompt();
},
};
@@ -1096,9 +1099,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(global.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(
@@ -1126,15 +1129,22 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
},
.start_command => {
self.command_timer = try .now();
self.command_timer = .now(global.io(), .awake);
},
.stop_command => |v| timer: {
const end: std.time.Instant = try .now();
const end: std.Io.Timestamp = .now(global.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(
@@ -1185,8 +1195,8 @@ fn selectionScrollTick(self: *Surface) !void {
const pos_vp = self.posToViewport(pos.x, pos.y);
// We need our locked state for the remainder
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
const selection = self.mouse.selection_gesture.autoscrollTick(t, .{
@@ -1276,8 +1286,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
t.carriageReturn();
t.linefeed() catch break :terminal;
@@ -1316,8 +1326,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
// No matter what move the cursor back to the column 0.
@@ -1383,8 +1393,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
// If our password input state is unchanged then we don't
// waste time doing anything more.
@@ -1436,6 +1446,7 @@ fn searchCallback_(
for (matches) |*m| m.* = try m.clone(alloc);
_ = self.renderer_thread.mailbox.push(
global.io(),
.{ .search_viewport_matches = .{
.arena = arena,
.matches = matches,
@@ -1454,6 +1465,7 @@ fn searchCallback_(
const match = try sel.highlight.clone(alloc);
_ = self.renderer_thread.mailbox.push(
global.io(),
.{ .search_selected_match = .{
.arena = arena,
.match = match,
@@ -1469,6 +1481,7 @@ fn searchCallback_(
} else {
// Reset our selected match
_ = self.renderer_thread.mailbox.push(
global.io(),
.{ .search_selected_match = null },
.forever,
);
@@ -1493,10 +1506,12 @@ fn searchCallback_(
// When we quit, tell our renderer to reset any search state.
.quit => {
_ = self.renderer_thread.mailbox.push(
global.io(),
.{ .search_selected_match = null },
.forever,
);
_ = self.renderer_thread.mailbox.push(
global.io(),
.{ .search_viewport_matches = .{
.arena = .init(self.alloc),
.matches = &.{},
@@ -1539,8 +1554,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
self.renderer_state.mouse.mods = self.mouseModsWithCapture(self.mouse.mods);
// We use the clear screen dirty flag to force a rebuild of all
@@ -1786,7 +1801,7 @@ pub fn updateConfig(
termio_config_ptr.* = try termio.Termio.DerivedConfig.init(self.alloc, config);
errdefer termio_config_ptr.deinit();
_ = self.renderer_thread.mailbox.push(renderer_message, .{ .forever = {} });
_ = self.renderer_thread.mailbox.push(global.io(), renderer_message, .{ .forever = {} });
self.queueIo(.{
.change_config = .{
.alloc = self.alloc,
@@ -1907,8 +1922,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
return try self.dumpTextLocked(alloc, sel);
}
@@ -2033,15 +2048,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(global.io());
defer self.renderer_state.mutex.unlock(global.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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const sel = self.io.terminal.screens.active.selection orelse return null;
return try self.io.terminal.screens.active.selectionString(alloc, .{
.sel = sel,
@@ -2056,8 +2071,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const terminal_pwd = self.io.terminal.getPwd() orelse return null;
return try alloc.dupe(u8, terminal_pwd);
}
@@ -2074,7 +2089,7 @@ fn resolvePathForOpening(
const resolved = try std.fs.path.resolve(self.alloc, &.{ terminal_pwd, path });
std.fs.accessAbsolute(resolved, .{}) catch {
std.Io.Dir.accessAbsolute(global.io(), resolved, .{}) catch {
self.alloc.free(resolved);
return null;
};
@@ -2088,10 +2103,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(global.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(global.io());
// TODO: need to handle when scrolling and the cursor is not
// in the visible portion of the screen.
@@ -2440,7 +2455,7 @@ pub fn setFontSize(self: *Surface, size: font.face.DesiredSize) !void {
// Notify our render thread of the new font stack. The renderer
// MUST accept the new font grid and deref the old.
_ = self.renderer_thread.mailbox.push(.{
_ = self.renderer_thread.mailbox.push(global.io(), .{
.font_grid = .{
.grid = font_grid,
.set = &self.app.font_grid_set,
@@ -2530,8 +2545,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
// We clear our selection when ANY OF:
// 1. We have an existing preedit
@@ -2566,7 +2581,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);
@@ -2697,8 +2712,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
if (self.io.terminal.modes.get(.disable_keyboard)) return .consumed;
}
@@ -2728,8 +2743,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
self.mouseRefreshLinks(
pos,
self.posToViewport(pos.x, pos.y),
@@ -2821,8 +2836,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
if (self.config.selection_clear_on_typing or
event.key == .escape)
@@ -3253,8 +3268,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const t = &self.io.terminal;
var opts: input.key_encode.Options = .fromTerminal(t);
@@ -3295,7 +3310,7 @@ pub fn occlusionCallback(self: *Surface, visible: bool) !void {
crash.sentry.thread_state = self.crashThreadState();
defer crash.sentry.thread_state = null;
_ = self.renderer_thread.mailbox.push(.{
_ = self.renderer_thread.mailbox.push(global.io(), .{
.visible = visible,
}, .{ .forever = {} });
try self.queueRender();
@@ -3315,7 +3330,7 @@ pub fn focusCallback(self: *Surface, focused: bool) !void {
self.focused = focused;
// Notify our render thread of the new state
_ = self.renderer_thread.mailbox.push(.{
_ = self.renderer_thread.mailbox.push(global.io(), .{
.focus = focused,
}, .{ .forever = {} });
@@ -3382,9 +3397,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(global.io());
self.io.terminal.flags.focused = focused;
self.renderer_state.mutex.unlock();
self.renderer_state.mutex.unlock(global.io());
self.queueIo(.{ .focused = focused }, .unlocked);
}
}
@@ -3516,8 +3531,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(global.io());
defer self.renderer_state.mutex.unlock(global.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
@@ -3717,8 +3732,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(global.io());
defer if (lock) self.renderer_state.mutex.unlock(global.io());
// If the terminal explicitly requests it then we always allow it
// since we processed never/always at this point.
@@ -3739,8 +3754,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
return self.io.terminal.flags.mouse_event != .none;
}
@@ -3796,21 +3811,12 @@ 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 click_time = self.mouse.selection_gesture.left_click_time orelse
break :extend_selection;
const since = now.since(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 click_time = self.mouse.selection_gesture.left_click_time orelse
break :extend_selection;
const since = click_time.untilNow(global.io(), .awake);
if (since.toNanoseconds() <= self.config.mouse_interval) {
// Click interval very short, we may be increasing
// click counts so we don't extend the selection.
break :extend_selection;
}
@@ -3821,8 +3827,8 @@ pub fn mouseButtonCallback(
}
if (button == .left and action == .release) {
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(global.io());
defer self.renderer_state.mutex.unlock(global.io());
// The selection gesture tracks whether a press became a drag by
// comparing the release cell to the original press cell. Resolve the
@@ -3897,8 +3903,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(global.io());
defer self.renderer_state.mutex.unlock(global.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.
@@ -3937,8 +3943,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
const screen: *terminal.Screen = self.renderer_state.terminal.screens.active;
@@ -3962,12 +3968,8 @@ pub fn mouseButtonCallback(
break :pin pin;
};
const time = std.time.Instant.now() catch |err| time: {
log.err("error reading time, mouse multi-click won't work err={}", .{err});
break :time null;
};
var press_selection = try self.mouse.selection_gesture.press(t, .{
.time = time,
.time = std.Io.Timestamp.now(global.io(), .awake),
.pin = pin,
.xpos = pos.x,
.ypos = pos.y,
@@ -4047,8 +4049,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
// Get our viewport pin
const screen: *terminal.Screen = self.renderer_state.terminal.screens.active;
@@ -4119,8 +4121,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(global.io());
defer self.renderer_state.mutex.lockUncancelable(global.io());
_ = try self.startClipboardRequest(.standard, .paste);
// We don't need to clear selection because we didn't have
@@ -4134,8 +4136,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(global.io());
defer self.renderer_state.mutex.lockUncancelable(global.io());
_ = try self.startClipboardRequest(.standard, .paste);
},
}
@@ -4428,7 +4430,6 @@ fn openUrl(
// apprts to handle this themselves.
log.warn("apprt did not handle open URL action, falling back to default opener", .{});
try internal_os.open(
self.alloc,
action.kind,
action.url,
);
@@ -4474,8 +4475,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const sel = self.mouse.selection_gesture.deepPress(
self.renderer_state.terminal,
@@ -4540,8 +4541,8 @@ pub fn cursorPosCallback(
try self.queueRender();
}
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
self.renderer_state.mutex.lockUncancelable(global.io());
defer self.renderer_state.mutex.unlock(global.io());
// No mouse point so we don't highlight links
self.renderer_state.mouse.point = null;
@@ -4567,8 +4568,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
// Update our mouse state. We set this to null initially because we only
// want to set it when we're not selecting or doing any other mouse
@@ -4824,8 +4825,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
self.scrollToBottom() catch |err| {
log.warn("error scrolling to bottom err={}", .{err});
};
@@ -4851,8 +4852,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
self.scrollToBottom() catch |err| {
log.warn("error scrolling to bottom err={}", .{err});
};
@@ -4864,8 +4865,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
// With the lock held, we must scroll to the bottom.
// We always scroll to the bottom for these inputs.
@@ -4884,8 +4885,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
self.renderer_state.terminal.fullReset();
},
@@ -4955,7 +4956,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(global.io(), "search") catch {};
break :init s;
};
@@ -4968,6 +4969,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
}
_ = s.state.mailbox.push(
global.io(),
.{ .change_needle = try .init(
self.alloc,
text,
@@ -4980,6 +4982,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.navigate_search => |nav| {
const s: *Search = if (self.search) |*s| s else return false;
_ = s.state.mailbox.push(
global.io(),
.{ .select = switch (nav) {
.next => .next,
.previous => .prev,
@@ -4990,8 +4993,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
if (self.io.terminal.screens.active.selection) |sel| {
try self.copySelectionToClipboards(
@@ -5022,8 +5025,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
if (try self.linkAtPos(pos)) |link_info| {
const url_text = switch (link_info.action) {
.open => url_text: {
@@ -5168,8 +5171,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
if (self.io.terminal.screens.active_key == .alternate) return false;
}
@@ -5192,8 +5195,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const t: *terminal.Terminal = self.renderer_state.terminal;
t.screens.active.scroll(.{ .row = n });
}
@@ -5203,8 +5206,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(global.io());
defer self.renderer_state.mutex.unlock(global.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 });
@@ -5442,8 +5445,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const sel = self.io.terminal.screens.active.selectAll();
if (sel) |s| {
@@ -5564,7 +5567,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.main => @panic("crash binding action, crashing intentionally"),
.render => {
_ = self.renderer_thread.mailbox.push(.{ .crash = {} }, .{ .forever = {} });
_ = self.renderer_thread.mailbox.push(global.io(), .{ .crash = {} }, .{ .forever = {} });
self.queueRender() catch |err| {
// Not a big deal if this fails.
log.warn("failed to notify renderer of crash message err={}", .{err});
@@ -5575,8 +5578,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const screen: *terminal.Screen = self.io.terminal.screens.active;
const sel = if (screen.selection) |*sel| sel else {
@@ -5672,23 +5675,24 @@ fn writeScreenFile(
// Open our scrollback file
var file = try tmp_dir.dir.createFile(
global.io(),
filename,
switch (builtin.os.tag) {
.windows => .{},
else => .{ .mode = 0o600 },
else => .{ .permissions = .fromMode(0o600) },
},
);
defer file.close();
defer file.close(global.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(global.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(global.io());
defer self.renderer_state.mutex.unlock(global.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
@@ -5751,7 +5755,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(
global.io(),
filename,
&path_buf,
)];
switch (write_screen.action) {
.copy => {
@@ -5849,8 +5857,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(global.io());
defer self.renderer_state.mutex.unlock(global.io());
const opts: input.paste.Options = .fromTerminal(&self.io.terminal);
// If we have paste protection enabled, we detect unsafe pastes and return
@@ -5970,12 +5978,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(global.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;
}
@@ -5992,7 +6000,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

@@ -8,6 +8,7 @@ const renderer = @import("../renderer.zig");
const terminal = @import("../terminal/main.zig");
const CoreSurface = @import("../Surface.zig");
const lib = @import("../lib/main.zig");
const compat_testing = @import("../lib/compat/testing.zig");
/// The target for an action. This is generally the thing that had focus
/// while the action was made but the concept of "focus" is not guaranteed
@@ -424,8 +425,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));
@@ -434,19 +438,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
@@ -713,7 +710,7 @@ pub const SetTitle = struct {
pub fn format(
value: @This(),
comptime _: []const u8,
_: std.fmt.FormatOptions,
_: std.fmt.Options,
writer: *std.Io.Writer,
) !void {
try writer.print("{s}{{ {s} }}", .{ @typeName(@This()), value.title });
@@ -737,7 +734,7 @@ pub const Pwd = struct {
pub fn format(
value: @This(),
comptime _: []const u8,
_: std.fmt.FormatOptions,
_: std.fmt.Options,
writer: *std.Io.Writer,
) !void {
try writer.print("{s}{{ {s} }}", .{ @typeName(@This()), value.pwd });
@@ -765,7 +762,7 @@ pub const DesktopNotification = struct {
pub fn format(
value: @This(),
comptime _: []const u8,
_: std.fmt.FormatOptions,
_: std.fmt.Options,
writer: *std.Io.Writer,
) !void {
try writer.print("{s}{{ title: {s}, body: {s} }}", .{
@@ -1008,5 +1005,5 @@ pub const SearchSelected = struct {
};
test {
_ = std.testing.refAllDeclsRecursive(@This());
_ = compat_testing.refAllDeclsRecursive(@This());
}

View File

@@ -11,6 +11,7 @@ const Allocator = std.mem.Allocator;
const objc = @import("objc");
const apprt = @import("../apprt.zig");
const font = @import("../font/main.zig");
const global = @import("../global.zig");
const input = @import("../input.zig");
const internal_os = @import("../os/main.zig");
const renderer = @import("../renderer.zig");
@@ -333,7 +334,7 @@ pub const App = struct {
_: apprt.ipc.Target,
comptime action: apprt.ipc.Action.Key,
_: apprt.ipc.Action.Value(action),
) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!bool {
) (Allocator.Error || apprt.ipc.Errors)!bool {
switch (action) {
.new_window => return false,
.toggle_quick_terminal => return false,
@@ -372,7 +373,7 @@ pub const Platform = union(PlatformTag) {
/// Initialize a Platform a tag and configuration from the C ABI.
pub fn init(tag_int: c_int, c_platform: C) !Platform {
const tag = try std.meta.intToEnum(PlatformTag, tag_int);
const tag = std.enums.fromInt(PlatformTag, tag_int) orelse return error.InvalidEnumTag;
return switch (tag) {
.macos => if (MacOS != void) macos: {
const config = c_platform.macos;
@@ -490,16 +491,16 @@ pub const Surface = struct {
if (opts.working_directory) |c_wd| {
const wd = std.mem.sliceTo(c_wd, 0);
if (wd.len > 0) wd: {
var dir = std.fs.openDirAbsolute(wd, .{}) catch |err| {
var dir = std.Io.Dir.openDirAbsolute(global.io(), wd, .{}) catch |err| {
log.warn(
"error opening requested working directory dir={s} err={}",
.{ wd, err },
);
break :wd;
};
defer dir.close();
defer dir.close(global.io());
const stat = dir.stat() catch |err| {
const stat = dir.stat(global.io()) catch |err| {
log.warn(
"failed to stat requested working directory dir={s} err={}",
.{ wd, err },
@@ -950,32 +951,32 @@ pub const Surface = struct {
};
}
pub fn defaultTermioEnv(self: *const Surface) !std.process.EnvMap {
const alloc = self.app.core_app.alloc;
var env = try internal_os.getEnvMap(alloc);
pub fn defaultTermioEnv(self: *const Surface) !std.process.Environ.Map {
_ = self;
var env = try global.environMap();
errdefer env.deinit();
if (comptime builtin.target.os.tag.isDarwin()) {
if (env.get("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != null) {
env.remove("__XCODE_BUILT_PRODUCTS_DIR_PATHS");
env.remove("__XPC_DYLD_LIBRARY_PATH");
env.remove("DYLD_FRAMEWORK_PATH");
env.remove("DYLD_INSERT_LIBRARIES");
env.remove("DYLD_LIBRARY_PATH");
env.remove("LD_LIBRARY_PATH");
env.remove("SECURITYSESSIONID");
env.remove("XPC_SERVICE_NAME");
_ = env.orderedRemove("__XCODE_BUILT_PRODUCTS_DIR_PATHS");
_ = env.orderedRemove("__XPC_DYLD_LIBRARY_PATH");
_ = env.orderedRemove("DYLD_FRAMEWORK_PATH");
_ = env.orderedRemove("DYLD_INSERT_LIBRARIES");
_ = env.orderedRemove("DYLD_LIBRARY_PATH");
_ = env.orderedRemove("LD_LIBRARY_PATH");
_ = env.orderedRemove("SECURITYSESSIONID");
_ = env.orderedRemove("XPC_SERVICE_NAME");
}
// Remove this so that running `ghostty` within Ghostty works.
env.remove("GHOSTTY_MAC_LAUNCH_SOURCE");
_ = env.orderedRemove("GHOSTTY_MAC_LAUNCH_SOURCE");
// If we were launched from the desktop then we want to
// remove the LANGUAGE env var so that we don't inherit
// our translation settings for Ghostty. If we aren't from
// the desktop then we didn't set our LANGUAGE var so we
// don't need to remove it.
if (internal_os.launchedFromDesktop()) env.remove("LANGUAGE");
if (internal_os.launchedFromDesktop()) _ = env.orderedRemove("LANGUAGE");
}
return env;
@@ -1000,7 +1001,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,
@@ -1229,9 +1230,9 @@ 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(global.io(), .awake);
io.DeltaTime = if (self.instant) |prev| delta: {
const since_ns: f64 = @floatFromInt(now.since(prev));
const since_ns: f64 = @floatFromInt(prev.durationTo(now).toNanoseconds());
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);
@@ -1242,8 +1243,6 @@ pub const Inspector = struct {
// C API
pub const CAPI = struct {
const global = &@import("../global.zig").state;
/// This is the same as Surface.KeyEvent but this is the raw C API version.
const KeyEvent = extern struct {
action: input.Action,
@@ -1300,7 +1299,7 @@ pub const CAPI = struct {
pub fn deinit(self: *Text) void {
if (self.text) |ptr| {
global.alloc.free(ptr[0..self.text_len :0]);
global.alloc().free(ptr[0..self.text_len :0]);
}
}
};
@@ -1410,12 +1409,12 @@ pub const CAPI = struct {
opts: *const apprt.runtime.App.Options,
config: *const Config,
) !*App {
const core_app = try CoreApp.create(global.alloc);
const core_app = try CoreApp.create(global.alloc());
errdefer core_app.destroy();
// Create our runtime app
var app = try global.alloc.create(App);
errdefer global.alloc.destroy(app);
var app = try global.alloc().create(App);
errdefer global.alloc().destroy(app);
try app.init(core_app, config, opts.*);
errdefer app.terminate();
@@ -1438,7 +1437,7 @@ pub const CAPI = struct {
export fn ghostty_app_free(v: *App) void {
const core_app = v.core_app;
v.terminate();
global.alloc.destroy(v);
global.alloc().destroy(v);
core_app.destroy();
}
@@ -1520,13 +1519,7 @@ pub const CAPI = struct {
/// Update the color scheme of the app.
export fn ghostty_app_set_color_scheme(v: *App, scheme_raw: c_int) void {
const scheme = std.meta.intToEnum(apprt.ColorScheme, scheme_raw) catch {
log.warn(
"invalid color scheme to ghostty_surface_set_color_scheme value={}",
.{scheme_raw},
);
return;
};
const scheme = std.enums.fromInt(apprt.ColorScheme, scheme_raw) orelse return;
v.core_app.colorSchemeEvent(v, scheme) catch |err| {
log.err("error setting color scheme err={}", .{err});
@@ -1612,8 +1605,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(global.io());
defer core_surface.renderer_state.mutex.unlock(global.io());
// If we don't have a selection, do nothing.
const core_sel = core_surface.io.terminal.screens.active.selection orelse return false;
@@ -1632,8 +1625,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(global.io());
defer surface.core_surface.renderer_state.mutex.unlock(global.io());
const core_sel = sel.core(
surface.core_surface.renderer_state.terminal.screens.active,
@@ -1651,7 +1644,7 @@ pub const CAPI = struct {
// Get our text directly from the core surface.
const text = core_surface.dumpTextLocked(
global.alloc,
global.alloc(),
core_sel,
) catch |err| {
log.warn("error reading text err={}", .{err});
@@ -1730,14 +1723,7 @@ pub const CAPI = struct {
/// Update the color scheme of the surface.
export fn ghostty_surface_set_color_scheme(surface: *Surface, scheme_raw: c_int) void {
const scheme = std.meta.intToEnum(apprt.ColorScheme, scheme_raw) catch {
log.warn(
"invalid color scheme to ghostty_surface_set_color_scheme value={}",
.{scheme_raw},
);
return;
};
const scheme = std.enums.fromInt(apprt.ColorScheme, scheme_raw) orelse return;
surface.colorSchemeCallback(scheme);
}
@@ -1891,17 +1877,7 @@ pub const CAPI = struct {
stage_raw: u32,
pressure: f64,
) void {
const stage = std.meta.intToEnum(
input.MousePressureStage,
stage_raw,
) catch {
log.warn(
"invalid mouse pressure stage value={}",
.{stage_raw},
);
return;
};
const stage = std.enums.fromInt(input.MousePressureStage, stage_raw) orelse return;
surface.mousePressureCallback(stage, pressure);
}
@@ -2134,6 +2110,7 @@ pub const CAPI = struct {
export fn ghostty_surface_set_display_id(ptr: *Surface, display_id: u32) void {
const surface = &ptr.core_surface;
_ = surface.renderer_thread.mailbox.push(
global.io(),
.{ .macos_display_id = display_id },
.{ .forever = {} },
);
@@ -2157,8 +2134,8 @@ pub const CAPI = struct {
// read the font face. It should not be deferred since
// we're loading the primary face.
const grid = ptr.core_surface.renderer.font_grid;
grid.lock.lockShared();
defer grid.lock.unlockShared();
grid.lock.lockSharedUncancelable(global.io());
defer grid.lock.unlockShared(global.io());
const collection = &grid.resolver.collection;
const face = collection.getFace(.{}) catch return null;
@@ -2195,8 +2172,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(global.io());
defer surface.renderer_state.mutex.unlock(global.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

@@ -1,3 +1,4 @@
const builtin = @import("builtin");
const std = @import("std");
const assert = @import("../../../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
@@ -10,16 +11,17 @@ const gtk = @import("gtk");
const build_config = @import("../../../build_config.zig");
const build_info = @import("../build/info.zig");
const state = &@import("../../../global.zig").state;
const global = @import("../../../global.zig");
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");
const systemd = @import("../../../os/systemd.zig");
const terminal = @import("../../../terminal/main.zig");
const xev = @import("../../../global.zig").xev;
const xev = global.xev;
const Binding = @import("../../../input.zig").Binding;
const CoreConfig = configpkg.Config;
const CoreSurface = @import("../../../Surface.zig");
@@ -45,6 +47,8 @@ const OpenURI = @import("../portal.zig").OpenURI;
const log = std.log.scoped(.gtk_ghostty_application);
extern "c" fn setenv(name: ?[*]const u8, value: ?[*]const u8, overwrite: c_int) c_int;
/// Function used to funnel GLib/GObject/GTK log messages into Zig's logging
/// system rather than just getting dumped directly to stderr.
fn glibLogWriterFunction(
@@ -271,15 +275,17 @@ pub const Application = extern struct {
};
defer config.deinit();
// Set the old language,
const saved_language: ?[:0]const u8 = saved_language: {
const old_language = old_language: {
const result = (internal_os.getenv(alloc, "LANG") catch break :old_language null) orelse break :old_language null;
defer result.deinit(alloc);
break :old_language alloc.dupeZ(u8, result.value) catch break :old_language null;
const lang = global.environ().getPosix("LANG") orelse break :old_language null;
break :old_language alloc.dupeSentinel(u8, @ptrCast(lang), 0) catch null;
};
if (config.language) |language| _ = internal_os.setenv("LANG", language);
if (config.language) |language| {
// Override LANG if we need to (sync global environs if so)
_ = setenv("LANG", @ptrCast(language), 1);
global.syncEnviron();
}
break :saved_language old_language;
};
@@ -294,7 +300,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 +344,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
@@ -1063,7 +1069,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 {
@@ -1087,7 +1097,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(
global.io(),
path,
.{},
) catch |err| {
if (err != error.FileNotFound or !optional) {
log.warn(
"error opening gtk-custom-css file {s}: {}",
@@ -1096,12 +1110,14 @@ pub const Application = extern struct {
}
continue;
};
defer file.close();
defer file.close(global.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) {
@@ -1111,6 +1127,7 @@ pub const Application = extern struct {
},
else => |e| return e,
};
defer alloc.free(contents);
const bytes = glib.Bytes.new(contents.ptr, contents.len);
@@ -1401,7 +1418,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,
);
@@ -1461,7 +1478,7 @@ pub const Application = extern struct {
// Queue a new window
const priv = self.private();
_ = priv.core_app.mailbox.push(.{
_ = priv.core_app.mailbox.push(global.io(), .{
.new_window = .{},
}, .{ .forever = {} });
@@ -1813,7 +1830,7 @@ pub const Application = extern struct {
_: ?*glib.Variant,
self: *Self,
) callconv(.c) void {
_ = self.core().mailbox.push(.open_config, .forever);
_ = self.core().mailbox.push(global.io(), .open_config, .forever);
}
fn actionPresentSurface(
@@ -1839,6 +1856,7 @@ pub const Application = extern struct {
const surface = self.core().findSurfaceByID(surface_id) orelse return;
_ = self.core().mailbox.push(
global.io(),
.{
.surface_message = .{
.surface = surface,
@@ -1866,11 +1884,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
@@ -1894,11 +1909,11 @@ pub const Application = extern struct {
};
pub fn openUrlFallback(self: *Application, kind: apprt.action.OpenUrl.Kind, url: []const u8) void {
_ = self;
// Fallback to the minimal cross-platform way of opening a URL.
// This is always a safe fallback and enables for example Windows
// to open URLs (GTK on Windows via WSL is a thing).
internal_os.open(
self.allocator(),
kind,
url,
) catch |err| log.warn("unable to open url: {}", .{err});
@@ -2846,7 +2861,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 {
@@ -2863,7 +2878,7 @@ fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void {
} = .{
// `gtk-opengl-debug` dumps logs directly to stderr so both must be true
// to enable OpenGL debugging.
.opengl = state.logging.stderr and config.@"gtk-opengl-debug",
.opengl = global.logging().stderr and config.@"gtk-opengl-debug",
};
var gdk_disable: struct {
@@ -2915,8 +2930,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)) {
@@ -2926,15 +2940,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]);
_ = setenv("GDK_DEBUG", @ptrCast(value[0 .. value.len - 1 :0]), 1);
}
{
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)) {
@@ -2944,10 +2957,13 @@ 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]);
_ = setenv("GDK_DISABLE", @ptrCast(value[0 .. value.len - 1 :0]), 1);
}
// Sync environ after altering system env
global.syncEnviron();
}
fn findActiveWindow(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.c) c_int {

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

@@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const gio = @import("gio");
const glib = @import("glib");
const gobject = @import("gobject");
const global = @import("../../../global.zig");
const Binding = @import("../../../input.zig").Binding;
const key = @import("../key.zig");
@@ -630,6 +631,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 = global.io() };
break :rand_int rng_impl.interface().int(u28);
}},
) catch unreachable;
}

View File

@@ -7,6 +7,7 @@ const adw = @import("adw");
const gdk = @import("gdk");
const gobject = @import("gobject");
const gtk = @import("gtk");
const global = @import("../../../global.zig");
const input = @import("../../../input.zig");
const gresource = @import("../build/gresource.zig");
@@ -61,13 +62,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 +141,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(global.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 +300,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(global.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 +459,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(global.io(), .awake).nanoseconds >= frame_time_ns
else
true;

View File

@@ -36,6 +36,7 @@ const Window = @import("window.zig").Window;
const InspectorWindow = @import("inspector_window.zig").InspectorWindow;
const i18n = @import("../../../os/i18n.zig");
const media = @import("../media.zig");
const global = @import("../../../global.zig");
const log = std.log.scoped(.gtk_ghostty_surface);
@@ -1589,32 +1590,32 @@ 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);
var env = try global.environMap();
errdefer env.deinit();
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.
@@ -1641,7 +1642,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{
@@ -1708,7 +1709,7 @@ pub const Surface = extern struct {
item.key,
item.value,
);
for (env_to_remove.items) |key| _ = env_map.remove(key);
for (env_to_remove.items) |key| _ = env_map.orderedRemove(key);
}
pub fn clipboardRequest(

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

@@ -6,6 +6,7 @@ const Allocator = std.mem.Allocator;
const gio = @import("gio");
const glib = @import("glib");
const global = @import("../../../global.zig");
const apprt = @import("../../../apprt.zig");
const ApprtApp = @import("../App.zig");
@@ -28,10 +29,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(global.io(), &buf);
const stderr = &stderr_writer.interface;
// Get the appropriate bus name and object path for contacting the
@@ -133,7 +136,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(global.io(), &buf);
const stderr = &stderr_writer.interface;
// finish building the parameters

View File

@@ -7,10 +7,15 @@ const gio = @import("gio");
const glib = @import("glib");
const gobject = @import("gobject");
const gtk = @import("gtk");
const global = @import("../../global.zig");
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(
global.io(),
path,
.{ .read = true },
) catch |err| {
log.warn("unable to access {s}: {t}", .{ path, err });
return null;
};

View File

@@ -1,6 +1,7 @@
const std = @import("std");
const gio = @import("gio");
const global = @import("../../global.zig");
const Allocator = std.mem.Allocator;
@@ -11,7 +12,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 = global.io() };
return rng_impl.interface().int(usize);
}
/// Format a request token consistently for use in portal object paths and payloads.

View File

@@ -9,6 +9,7 @@ const assert = std.debug.assert;
const gio = @import("gio");
const glib = @import("glib");
const gobject = @import("gobject");
const global = @import("../../../global.zig");
const App = @import("../App.zig");
const portal = @import("../portal.zig");
@@ -23,7 +24,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 +79,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 +124,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(global.io());
defer self.mutex.unlock(global.io());
if (!self.alive) return;
self.alive = false;
@@ -157,8 +158,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(global.io());
defer self.mutex.unlock(global.io());
// Create an entry that is used to track the results of the D-Bus method
// call.
@@ -166,7 +167,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(global.io(), .awake),
.token = token,
.kind = value.kind,
.uri = try alloc.dupeZ(u8, value.url),
@@ -235,8 +236,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(global.io());
defer self.mutex.unlock(global.io());
if (!self.alive) return null;
@@ -422,8 +423,8 @@ fn requestCallback(
return;
}
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(global.io());
defer self.mutex.unlock(global.io());
if (!self.alive) return;
@@ -461,8 +462,8 @@ fn responseReceived(
return;
};
self.mutex.lock();
defer self.mutex.unlock();
self.mutex.lockUncancelable(global.io());
defer self.mutex.unlock(global.io());
if (!self.alive) return;
@@ -534,22 +535,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(global.io());
defer self.mutex.unlock(global.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(
global.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

@@ -2,6 +2,7 @@ const std = @import("std");
const gio = @import("gio");
const glib = @import("glib");
const global = @import("../../global.zig");
const log = std.log.scoped(.gtk_post_fork);
@@ -85,12 +86,10 @@ pub fn postFork(cmd: *Command) Command.PostForkError!void {
return;
};
const start = std.time.Instant.now() catch unreachable;
const start: std.Io.Timestamp = .now(global.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(global.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 +115,6 @@ pub fn postFork(cmd: *Command) Command.PostForkError!void {
}
}
std.Thread.sleep(25 * std.time.ns_per_ms);
std.Io.sleep(global.io(), .fromMilliseconds(25), .awake) catch unreachable;
}
}

View File

@@ -3,6 +3,7 @@ const std = @import("std");
const log = std.log.scoped(.gtk_pre_exec);
const configpkg = @import("../../config.zig");
const global = @import("../../global.zig");
const internal_os = @import("../../os/main.zig");
const Command = @import("../../Command.zig");
@@ -47,12 +48,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(global.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(global.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 +73,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(global.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

@@ -126,8 +126,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));
@@ -135,20 +139,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

@@ -12,6 +12,7 @@ const Allocator = std.mem.Allocator;
const terminalpkg = @import("../terminal/main.zig");
const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig");
const global = @import("../global.zig");
const log = std.log.scoped(.@"apc-parser-bench");
@@ -19,7 +20,7 @@ opts: Options,
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 data to read as a filepath. If this is "-" then
@@ -103,7 +104,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void {
fn teardown(ptr: *anyopaque) void {
const self: *ApcParser = @ptrCast(@alignCast(ptr));
if (self.data_f) |f| {
f.close();
f.close(global.io());
self.data_f = null;
}
}
@@ -114,7 +115,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
const f = self.data_f orelse return;
var read_buf: [64 * 1024]u8 align(std.atomic.cache_line) = undefined;
var f_reader = f.reader(&read_buf);
var f_reader = f.reader(global.io(), &read_buf);
const r = &f_reader.interface;
// This buffer size matches the read buffer size used by the

View File

@@ -6,6 +6,7 @@ const builtin = @import("builtin");
const assert = std.debug.assert;
const macos = @import("macos");
const build_config = @import("../build_config.zig");
const global = @import("../global.zig");
ptr: *anyopaque,
vtable: VTable,
@@ -64,21 +65,23 @@ pub fn run(
signpost.log.release();
};
const start = std.time.Instant.now() catch return error.BenchmarkFailed;
const start: std.Io.Timestamp = .now(global.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(global.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

@@ -1,6 +1,6 @@
const std = @import("std");
const cli = @import("cli.zig");
const state = &@import("../global.zig").state;
const global = @import("../global.zig");
const log = std.log.scoped(.benchmark);
@@ -19,7 +19,7 @@ export fn ghostty_benchmark_cli(
};
cli.mainAction(
state.alloc,
global.alloc(),
action,
.{ .string = std.mem.sliceTo(args, 0) },
) catch |err| {

View File

@@ -14,13 +14,14 @@ const options = @import("options.zig");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const simd = @import("../simd/main.zig");
const table = @import("../unicode/main.zig").table;
const global = @import("../global.zig");
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 +94,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(global.io());
self.data_f = null;
}
}
@@ -114,7 +115,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(global.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
@@ -141,7 +142,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(global.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
@@ -173,7 +174,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(global.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};

View File

@@ -11,13 +11,14 @@ const options = @import("options.zig");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const unicode = @import("../unicode/main.zig");
const uucode = @import("uucode");
const global = @import("../global.zig");
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 +83,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(global.io());
self.data_f = null;
}
}
@@ -92,7 +93,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(global.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
@@ -115,7 +116,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(global.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};

View File

@@ -11,13 +11,14 @@ const options = @import("options.zig");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const uucode = @import("uucode");
const symbols_table = @import("../unicode/symbols_table.zig").table;
const global = @import("../global.zig");
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 +81,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(global.io());
self.data_f = null;
}
}
@@ -90,7 +91,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(global.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};
@@ -117,7 +118,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(global.io(), &read_buf);
var r = &f_reader.interface;
var d: UTF8Decoder = .{};

View File

@@ -9,11 +9,12 @@ const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig");
const Parser = @import("../terminal/osc.zig").Parser;
const log = std.log.scoped(.@"osc-parser-bench");
const global = @import("../global.zig");
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 +71,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(global.io());
self.data_f = null;
}
}
@@ -80,7 +81,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(global.io(), &read_buf);
var osc_buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) {

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