diff --git a/build.zig b/build.zig
index 38d2bca6d..0f7c010e0 100644
--- a/build.zig
+++ b/build.zig
@@ -15,6 +15,8 @@ pub fn build(b: *std.Build) !void {
// Ghostty dependencies used by many artifacts.
const deps = try buildpkg.SharedDeps.init(b, &config);
const exe = try buildpkg.GhosttyExe.init(b, &config, &deps);
+ const wasm = try buildpkg.GhosttyWasm.init(b, &config, &deps);
+ _ = wasm;
if (config.emit_helpgen) deps.help_strings.install();
// Ghostty docs
diff --git a/example/app.ts b/example/app.ts
index 5b426a333..af0b3fcf7 100644
--- a/example/app.ts
+++ b/example/app.ts
@@ -1,11 +1,11 @@
-import { ZigJS } from "zig-js";
+import { ZigJS } from "zig-js/src/index.ts";
const zjs = new ZigJS();
const importObject = {
module: {},
env: {
memory: new WebAssembly.Memory({
- initial: 25,
+ initial: 512,
maximum: 65536,
shared: true,
}),
@@ -27,38 +27,36 @@ fetch(url.href)
.then((results) => {
const memory = importObject.env.memory;
const {
- malloc,
- free,
- config_new,
+ atlas_clear,
+ atlas_debug_canvas,
+ atlas_free,
+ atlas_grow,
+ atlas_new,
+ atlas_reserve,
+ atlas_set,
+ config_finalize,
config_free,
config_load_string,
- config_finalize,
- face_new,
- face_free,
- face_render_glyph,
- face_debug_canvas,
- deferred_face_new,
+ config_new,
deferred_face_free,
deferred_face_load,
- deferred_face_face,
- group_new,
- group_free,
- group_add_face,
- group_init_sprite_face,
- group_index_for_codepoint,
- group_render_glyph,
- group_cache_new,
- group_cache_free,
- group_cache_index_for_codepoint,
- group_cache_render_glyph,
- group_cache_atlas_grayscale,
- group_cache_atlas_color,
- atlas_new,
- atlas_free,
- atlas_debug_canvas,
- shaper_new,
+ deferred_face_new,
+ face_debug_canvas,
+ face_free,
+ face_new,
+ face_render_glyph,
+ free,
+ malloc,
shaper_free,
+ shaper_new,
shaper_test,
+ collection_new,
+ collection_add_deferred_face,
+ shared_grid_new,
+ shared_grid_atlas_grayscale,
+ shared_grid_atlas_color,
+ shared_grid_index_for_codepoint,
+ shared_grid_render_glyph,
} = results.instance.exports;
// Give us access to the zjs value for debugging.
globalThis.zjs = zjs;
@@ -98,57 +96,63 @@ fetch(url.href)
//free(font_ptr);
// Create our group
- const group = group_new(32 /* size */);
- group_add_face(
- group,
+ const collection = collection_new(24);
+ collection_add_deferred_face(
+ collection,
0 /* regular */,
deferred_face_new(font_name.ptr, font_name.len, 0 /* text */),
);
- group_add_face(
- group,
+ collection_add_deferred_face(
+ collection,
0 /* regular */,
deferred_face_new(font_name.ptr, font_name.len, 1 /* emoji */),
);
+ const grid = shared_grid_new(collection);
// Initialize our sprite font, without this we just use the browser.
- group_init_sprite_face(group);
+ // group_init_sprite_face(group);
- // Create our group cache
- const group_cache = group_cache_new(group);
+ // // Create our group cache
+ // const group_cache = group_cache_new(group);
// Render a glyph
- // for (let i = 33; i <= 126; i++) {
- // const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
- // group_cache_render_glyph(group_cache, font_idx, i, 0);
- // //face_render_glyph(face, atlas, i);
- // }
+ for (let i = 33; i <= 126; i++) {
+ const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
+ shared_grid_render_glyph(grid, font_idx, i, 0);
+ //face_render_glyph(face, atlas, i);
+ }
//
- // const emoji = ["π","π","π","π±","πΏ","π","π","π","π‘","π"];
- // for (let i = 0; i < emoji.length; i++) {
- // const cp = emoji[i].codePointAt(0);
- // const font_idx = group_cache_index_for_codepoint(group_cache, cp, 0, -1 /* best choice */);
- // group_cache_render_glyph(group_cache, font_idx, cp, 0);
- // }
+ const emoji = ["π", "π", "π", "π±", "πΏ", "π", "π", "π", "π‘", "π"];
+ for (let i = 0; i < emoji.length; i++) {
+ const cp = emoji[i].codePointAt(0);
+ const font_idx = shared_grid_index_for_codepoint(
+ grid,
+ cp,
+ 0,
+ -1 /* best choice */,
+ );
+ shared_grid_render_glyph(grid, font_idx, cp, 0);
+ }
for (let i = 0x2500; i <= 0x257f; i++) {
- const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
- group_cache_render_glyph(group_cache, font_idx, i, 0);
+ const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
+ shared_grid_render_glyph(grid, font_idx, i, 0);
}
for (let i = 0x2580; i <= 0x259f; i++) {
- const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
- group_cache_render_glyph(group_cache, font_idx, i, 0);
+ const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
+ shared_grid_render_glyph(grid, font_idx, i, 0);
}
for (let i = 0x2800; i <= 0x28ff; i++) {
- const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
- group_cache_render_glyph(group_cache, font_idx, i, 0);
+ const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
+ shared_grid_render_glyph(grid, font_idx, i, 0);
}
for (let i = 0x1fb00; i <= 0x1fb3b; i++) {
- const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
- group_cache_render_glyph(group_cache, font_idx, i, 0);
+ const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
+ shared_grid_render_glyph(grid, font_idx, i, 0);
}
for (let i = 0x1fb3c; i <= 0x1fb6b; i++) {
- const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1);
- group_cache_render_glyph(group_cache, font_idx, i, 0);
+ const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
+ shared_grid_render_glyph(grid, font_idx, i, 0);
}
//face_render_glyph(face, atlas, "ζ©".codePointAt(0));
@@ -161,26 +165,26 @@ fetch(url.href)
const shaper = shaper_new(120);
//const input = makeStr("helloπ");
const input = makeStr("helloπππ½");
- shaper_test(shaper, group_cache, input.ptr, input.len);
+ shaper_test(shaper, grid, input.ptr, input.len);
const cp = 1114112;
- const font_idx = group_cache_index_for_codepoint(
- group_cache,
+ const font_idx = shared_grid_index_for_codepoint(
+ grid,
cp,
0,
-1 /* best choice */,
);
- group_cache_render_glyph(group_cache, font_idx, cp, -1);
+ shared_grid_render_glyph(grid, font_idx, cp, -1);
// Debug our atlas canvas
{
- const atlas = group_cache_atlas_grayscale(group_cache);
+ const atlas = shared_grid_atlas_grayscale(grid);
const id = atlas_debug_canvas(atlas);
document.getElementById("atlas-canvas").append(zjs.deleteValue(id));
}
{
- const atlas = group_cache_atlas_color(group_cache);
+ const atlas = shared_grid_atlas_color(grid);
const id = atlas_debug_canvas(atlas);
document.getElementById("atlas-color-canvas").append(zjs.deleteValue(id));
}
diff --git a/example/index.html b/example/index.html
index 2e66f92d1..02cd65183 100644
--- a/example/index.html
+++ b/example/index.html
@@ -8,6 +8,7 @@
Open your console, we are just debugging here.
The current grayscale font atlas is rendered below.
+
The current color font atlas is rendered below.
diff --git a/example/package-lock.json b/example/package-lock.json
index 3cb4de6f0..c18a6b878 100644
--- a/example/package-lock.json
+++ b/example/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.1.0",
"license": "MIT",
"dependencies": {
- "zig-js": "file:../vendor/zig-js/js"
+ "zig-js": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main"
},
"devDependencies": {
"@parcel/transformer-inline-string": "^2.8.0",
@@ -20,20 +20,6 @@
"../js": {
"extraneous": true
},
- "../vendor/zig-js/js": {
- "name": "zig-js-glue",
- "version": "0.1.2",
- "license": "MIT",
- "devDependencies": {
- "@parcel/packager-ts": "^2.8.0",
- "@parcel/transformer-typescript-types": "^2.8.0",
- "@types/jest": "^29.2.3",
- "jest": "^29.3.1",
- "parcel": "^2.8.0",
- "ts-jest": "^29.0.3",
- "typescript": "^4.9.3"
- }
- },
"node_modules/@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@@ -2693,8 +2679,11 @@
}
},
"node_modules/zig-js": {
- "resolved": "../vendor/zig-js/js",
- "link": true
+ "name": "zig-js-glue",
+ "version": "0.1.3",
+ "resolved": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main",
+ "integrity": "sha512-CdM4TmAINU1fsZMm0S3dH4XzQgCIC4AWfztA2eGRD9Tfk/2LfCZ7RgOED7i26P1Jf2+BCQIBMbxXocV7oxR3Ig==",
+ "license": "MIT"
}
},
"dependencies": {
@@ -4421,16 +4410,8 @@
"dev": true
},
"zig-js": {
- "version": "file:../vendor/zig-js/js",
- "requires": {
- "@parcel/packager-ts": "^2.8.0",
- "@parcel/transformer-typescript-types": "^2.8.0",
- "@types/jest": "^29.2.3",
- "jest": "^29.3.1",
- "parcel": "^2.8.0",
- "ts-jest": "^29.0.3",
- "typescript": "^4.9.3"
- }
+ "version": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main",
+ "integrity": "sha512-CdM4TmAINU1fsZMm0S3dH4XzQgCIC4AWfztA2eGRD9Tfk/2LfCZ7RgOED7i26P1Jf2+BCQIBMbxXocV7oxR3Ig=="
}
}
}
diff --git a/example/package.json b/example/package.json
index e884e5c6c..b031a6cc9 100644
--- a/example/package.json
+++ b/example/package.json
@@ -17,6 +17,6 @@
"typescript": "^4.9.3"
},
"dependencies": {
- "zig-js": "file:../vendor/zig-js/js"
+ "zig-js": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main"
}
}
diff --git a/src/App.zig b/src/App.zig
index 15859d115..ea489509a 100644
--- a/src/App.zig
+++ b/src/App.zig
@@ -61,7 +61,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: ?internal_os.Instant = null,
last_notification_digest: u64 = 0,
/// The conditional state of the configuration. See the equivalent field
diff --git a/src/Surface.zig b/src/Surface.zig
index b81a45ecb..bafd22576 100644
--- a/src/Surface.zig
+++ b/src/Surface.zig
@@ -174,7 +174,7 @@ const Mouse = struct {
/// The left click time was the last time the left click was done. This
/// is always set on the first left click.
left_click_count: u8 = 0,
- left_click_time: std.time.Instant = undefined,
+ left_click_time: internal_os.Instant = undefined,
/// The last x/y sent for mouse reports.
event_point: ?terminal.point.Coordinate = null,
@@ -2857,7 +2857,7 @@ 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| {
+ if (internal_os.Instant.now()) |now| {
const since = now.since(self.mouse.left_click_time);
if (since <= self.config.mouse_interval) {
// Click interval very short, we may be increasing
@@ -3003,7 +3003,7 @@ pub fn mouseButtonCallback(
self.mouse.left_click_ypos = pos.y;
// Setup our click counter and timer
- if (std.time.Instant.now()) |now| {
+ if (internal_os.Instant.now()) |now| {
// If we have mouse clicks, then we check if the time elapsed
// is less than and our interval and if so, increase the count.
if (self.mouse.left_click_count > 0) {
@@ -4653,7 +4653,7 @@ 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 = try internal_os.Instant.now();
// Set a limit of one desktop notification per second so that the OS
// doesn't kill us when we run out of resources.
diff --git a/src/build/GhosttyWasm.zig b/src/build/GhosttyWasm.zig
new file mode 100644
index 000000000..ec0a3e8f4
--- /dev/null
+++ b/src/build/GhosttyWasm.zig
@@ -0,0 +1,79 @@
+const Ghostty = @This();
+
+const std = @import("std");
+const Config = @import("Config.zig");
+const SharedDeps = @import("SharedDeps.zig");
+
+/// The primary Ghostty executable.
+exe: *std.Build.Step.Compile,
+
+/// The install step for the executable.
+install_step: *std.Build.Step.InstallArtifact,
+
+pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty {
+ // Build our Wasm target.
+ const wasm_crosstarget: std.Target.Query = .{
+ .cpu_arch = .wasm32,
+ .os_tag = .freestanding,
+ .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
+ .cpu_features_add = std.Target.wasm.featureSet(&.{
+ // We use this to explicitly request shared memory.
+ .atomics,
+
+ // Not explicitly used but compiler could use them if they want.
+ .bulk_memory,
+ .reference_types,
+ .sign_ext,
+ }),
+ };
+
+ // Whether we're using wasm shared memory. Some behaviors change.
+ // For now we require this but I wanted to make the code handle both
+ // up front.
+ const wasm_shared: bool = true;
+
+ const wasm = b.addExecutable(.{
+ .name = "ghostty-wasm",
+ .root_source_file = b.path("src/main_wasm.zig"),
+ .target = b.resolveTargetQuery(wasm_crosstarget),
+ .optimize = cfg.optimize,
+ });
+
+ // So that we can use web workers with our wasm binary
+ wasm.import_memory = true;
+ wasm.initial_memory = 65536 * 512;
+ wasm.entry = .disabled;
+ // wasm.wasi_exec_model = .reactor;
+ wasm.rdynamic = true;
+ wasm.max_memory = 65536 * 65536; // Maximum number of pages in wasm32
+ wasm.shared_memory = wasm_shared;
+
+ // Stack protector adds extern requirements that we don't satisfy.
+ wasm.root_module.stack_protector = false;
+
+ // Add the shared dependencies
+ _ = try deps.addWasm(wasm);
+
+ // Install
+ const wasm_install = b.addInstallArtifact(wasm, .{});
+ const install = b.addInstallFile(wasm.getEmittedBin(), "../example/ghostty-wasm.wasm");
+ wasm_install.step.dependOn(&install.step);
+
+ const step = b.step("wasm", "Build the wasm library");
+ step.dependOn(&install.step);
+
+ const test_step = b.step("test-wasm", "Run all tests for wasm");
+ const main_test = b.addTest(.{
+ .name = "wasm-test",
+ .root_source_file = b.path("src/main_wasm.zig"),
+ .target = b.resolveTargetQuery(wasm_crosstarget),
+ });
+
+ _ = try deps.addWasm(main_test);
+ test_step.dependOn(&main_test.step);
+
+ return .{
+ .exe = wasm,
+ .install_step = wasm_install,
+ };
+}
diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig
index 7d0b64c5b..e2f2c533b 100644
--- a/src/build/SharedDeps.zig
+++ b/src/build/SharedDeps.zig
@@ -84,6 +84,44 @@ fn initTarget(
try self.config.addOptions(self.options);
}
+pub fn addWasm(
+ self: *const SharedDeps,
+ step: *std.Build.Step.Compile,
+) !LazyPathList {
+ const b = step.step.owner;
+
+ // We could use our config.target/optimize fields here but its more
+ // correct to always match our step.
+ const target = step.root_module.resolved_target.?;
+ const optimize = step.root_module.optimize.?;
+
+ // We maintain a list of our static libraries and return it so that
+ // we can build a single fat static library for the final app.
+ var static_libs = LazyPathList.init(b.allocator);
+ errdefer static_libs.deinit();
+
+ // Every exe gets build options populated
+ step.root_module.addOptions("build_options", self.options);
+
+ const js_dep = b.dependency("zig_js", .{
+ .target = target,
+ .optimize = optimize,
+ });
+ step.root_module.addImport("zig-js", js_dep.module("zig-js"));
+ step.root_module.addImport("z2d", b.addModule("z2d", .{
+ .root_source_file = b.dependency("z2d", .{}).path("src/z2d.zig"),
+ .target = target,
+ .optimize = optimize,
+ }));
+ step.root_module.addImport("ziglyph", b.dependency("ziglyph", .{
+ .target = target,
+ .optimize = optimize,
+ }).module("ziglyph"));
+
+ self.unicode_tables.addImport(step);
+ return static_libs;
+}
+
pub fn add(
self: *const SharedDeps,
step: *std.Build.Step.Compile,
@@ -261,17 +299,6 @@ pub fn add(
try static_libs.append(breakpad_dep.artifact("breakpad").getEmittedBin());
}
- // Wasm we do manually since it is such a different build.
- if (step.rootModuleTarget().cpu.arch == .wasm32) {
- const js_dep = b.dependency("zig_js", .{
- .target = target,
- .optimize = optimize,
- });
- step.root_module.addImport("zig-js", js_dep.module("zig-js"));
-
- return static_libs;
- }
-
// On Linux, we need to add a couple common library paths that aren't
// on the standard search list. i.e. GTK is often in /usr/lib/x86_64-linux-gnu
// on x86_64.
diff --git a/src/build/main.zig b/src/build/main.zig
index a0e67543f..fbe7cda28 100644
--- a/src/build/main.zig
+++ b/src/build/main.zig
@@ -10,6 +10,7 @@ pub const GitVersion = @import("GitVersion.zig");
pub const GhosttyBench = @import("GhosttyBench.zig");
pub const GhosttyDocs = @import("GhosttyDocs.zig");
pub const GhosttyExe = @import("GhosttyExe.zig");
+pub const GhosttyWasm = @import("GhosttyWasm.zig");
pub const GhosttyFrameData = @import("GhosttyFrameData.zig");
pub const GhosttyLib = @import("GhosttyLib.zig");
pub const GhosttyResources = @import("GhosttyResources.zig");
diff --git a/src/build_config.zig b/src/build_config.zig
index b80247aab..8f7b828e6 100644
--- a/src/build_config.zig
+++ b/src/build_config.zig
@@ -81,8 +81,8 @@ pub const Artifact = enum {
pub fn detect() Artifact {
if (builtin.target.isWasm()) {
- assert(builtin.output_mode == .Obj);
- assert(builtin.link_mode == .Static);
+ // assert(builtin.output_mode == .Obj);
+ // assert(builtin.link_mode == .Static);
return .wasm_module;
}
diff --git a/src/cli/args.zig b/src/cli/args.zig
index 7385e6a3e..7b4d40028 100644
--- a/src/cli/args.zig
+++ b/src/cli/args.zig
@@ -1335,7 +1335,7 @@ pub fn LineIterator(comptime ReaderType: type) type {
}
// Constructs a LineIterator (see docs for that).
-fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) {
+pub fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) {
return .{ .r = reader };
}
diff --git a/src/config/Config.zig b/src/config/Config.zig
index d191de53a..e9554dbd6 100644
--- a/src/config/Config.zig
+++ b/src/config/Config.zig
@@ -3475,22 +3475,24 @@ fn loadTheme(self: *Config, theme: Theme) !void {
pub fn finalize(self: *Config) !void {
// We always load the theme first because it may set other fields
// in our config.
- if (self.theme) |theme| {
- const different = !std.mem.eql(u8, theme.light, theme.dark);
+ if (builtin.cpu.arch != .wasm32) {
+ if (self.theme) |theme| {
+ const different = !std.mem.eql(u8, theme.light, theme.dark);
- // Warning: loadTheme will deinit our existing config and replace
- // it so all memory from self prior to this point will be freed.
- try self.loadTheme(theme);
+ // Warning: loadTheme will deinit our existing config and replace
+ // it so all memory from self prior to this point will be freed.
+ try self.loadTheme(theme);
- // If we have different light vs dark mode themes, disable
- // window-theme = auto since that breaks it.
- if (different) {
- // This setting doesn't make sense with different light/dark themes
- // because it'll force the theme based on the Ghostty theme.
- if (self.@"window-theme" == .auto) self.@"window-theme" = .system;
+ // If we have different light vs dark mode themes, disable
+ // window-theme = auto since that breaks it.
+ if (different) {
+ // This setting doesn't make sense with different light/dark themes
+ // because it'll force the theme based on the Ghostty theme.
+ if (self.@"window-theme" == .auto) self.@"window-theme" = .system;
- // Mark that we use a conditional theme
- self._conditional_set.insert(.theme);
+ // Mark that we use a conditional theme
+ self._conditional_set.insert(.theme);
+ }
}
}
diff --git a/src/config/theme.zig b/src/config/theme.zig
index 2d206e1f6..53b179274 100644
--- a/src/config/theme.zig
+++ b/src/config/theme.zig
@@ -39,7 +39,7 @@ pub const Location = enum {
// error set since some platforms don't support some
// error types.
const Error = @TypeOf(err) || switch (builtin.os.tag) {
- .ios => error{BufferTooSmall},
+ .ios, .wasi => error{BufferTooSmall},
else => error{},
};
diff --git a/src/font/Collection.zig b/src/font/Collection.zig
index cb16528aa..7d4d2b2aa 100644
--- a/src/font/Collection.zig
+++ b/src/font/Collection.zig
@@ -687,6 +687,27 @@ pub const Index = packed struct(Index.Backing) {
}
};
+/// The wasm-compatible API.
+pub const Wasm = struct {
+ const wasm = @import("../os/wasm.zig");
+ const alloc = wasm.alloc;
+ export fn collection_new(points: f32) ?*Collection {
+ const result = alloc.create(Collection) catch |err| {
+ log.warn("error creating collection err={}", .{err});
+ return null;
+ };
+ result.* = Collection.init();
+ result.load_options = .{ .library = Library.init() catch unreachable, .size = .{ .points = points } };
+ return result;
+ }
+ export fn collection_add_deferred_face(self: *Collection, style: u8, face: *DeferredFace) u16 {
+ return @bitCast(self.add(alloc, @enumFromInt(style), .{ .deferred = face.* }) catch |err| {
+ log.warn("error adding deferred face to collection err={}", .{err});
+ return 0;
+ });
+ }
+};
+
test init {
const testing = std.testing;
const alloc = testing.allocator;
diff --git a/src/font/DeferredFace.zig b/src/font/DeferredFace.zig
index 3ee104386..f47904e95 100644
--- a/src/font/DeferredFace.zig
+++ b/src/font/DeferredFace.zig
@@ -254,7 +254,7 @@ fn loadWebCanvas(
opts: font.face.Options,
) !Face {
const wc = self.wc.?;
- return try Face.initNamed(wc.alloc, wc.font_str, opts, wc.presentation);
+ return try Face.initNamed(wc.alloc, wc.font_str, opts.size, wc.presentation);
}
/// Returns true if this face can satisfy the given codepoint and
@@ -392,7 +392,7 @@ pub const Wasm = struct {
}
export fn deferred_face_load(self: *DeferredFace, pts: f32) void {
- self.load(.{}, .{ .points = pts }) catch |err| {
+ _ = self.load(.{}, .{ .size = .{ .points = pts } }) catch |err| {
log.warn("error loading deferred face err={}", .{err});
return;
};
diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig
index 65c7ecd87..4ee965abb 100644
--- a/src/font/SharedGrid.zig
+++ b/src/font/SharedGrid.zig
@@ -311,6 +311,54 @@ const GlyphKey = struct {
const TestMode = enum { normal };
+/// The wasm-compatible API.
+pub const Wasm = struct {
+ const wasm = @import("../os/wasm.zig");
+ const alloc = wasm.alloc;
+ export fn shared_grid_new(c: *Collection) ?*SharedGrid {
+ const result = alloc.create(SharedGrid) catch |err| {
+ log.warn("error creating SharedGrid err={}", .{err});
+ return null;
+ };
+ result.* = SharedGrid.init(wasm.alloc, .{ .collection = c.* }) catch |err| {
+ log.warn("error initializing SharedGrid err={}", .{err});
+ return null;
+ };
+ return result;
+ }
+ export fn shared_grid_atlas_grayscale(self: *SharedGrid) ?*Atlas {
+ return &self.atlas_grayscale;
+ }
+ export fn shared_grid_atlas_color(self: *SharedGrid) ?*Atlas {
+ return &self.atlas_color;
+ }
+ export fn shared_grid_index_for_codepoint(self: *SharedGrid, code: u32, style: u8, presentation: i8) ?*Collection.Index {
+ const get = self.getIndex(wasm.alloc, code, @enumFromInt(style), if (presentation < 0) null else @enumFromInt(presentation)) catch |err| {
+ log.warn("error getting SharedGrid index for codepoint err={}", .{err});
+ return null;
+ };
+ if (get) |thing| {
+ const index = wasm.alloc.create(Collection.Index) catch unreachable;
+ index.* = thing;
+ return index;
+ }
+ return null;
+ }
+
+ export fn shared_grid_render_glyph(self: *SharedGrid, font_idx: *Collection.Index, code: u32, _: u8) void {
+ const glyph_index = glyph_index: {
+ if (font_idx.special()) |special| break :glyph_index switch (special) {
+ .sprite => code,
+ };
+ self.lock.lockShared();
+ defer self.lock.unlockShared();
+ const face = self.resolver.collection.getFace(font_idx.*) catch unreachable;
+ break :glyph_index face.glyphIndex(code) orelse return;
+ };
+ _ = self.renderGlyph(wasm.alloc, font_idx.*, glyph_index, .{ .grid_metrics = self.metrics }) catch unreachable;
+ }
+};
+
fn testGrid(mode: TestMode, alloc: Allocator, lib: Library) !SharedGrid {
const testFont = font.embedded.regular;
diff --git a/src/font/face/web_canvas.zig b/src/font/face/web_canvas.zig
index 30540191d..6882768e3 100644
--- a/src/font/face/web_canvas.zig
+++ b/src/font/face/web_canvas.zig
@@ -26,9 +26,6 @@ pub const Face = struct {
/// The presentation for this font.
presentation: font.Presentation,
- /// Metrics for this font face. These are useful for renderers.
- metrics: font.Metrics,
-
/// The canvas element that we will reuse to render glyphs
canvas: js.Object,
@@ -58,24 +55,20 @@ pub const Face = struct {
const font_str = try alloc.dupe(u8, raw);
errdefer alloc.free(font_str);
- // Create our canvas that we're going to continue to reuse.
- const doc = try js.global.get(js.Object, "document");
- defer doc.deinit();
- const canvas = try doc.call(js.Object, "createElement", .{js.string("canvas")});
+ // Create our canvasxx that we're going to continue to reuse.
+ const OffscreenCanvas = try js.global.get(js.Object, "OffscreenCanvas");
+ defer OffscreenCanvas.deinit();
+ const canvas = try OffscreenCanvas.new(.{ 0, 0 });
errdefer canvas.deinit();
- var result = Face{
+ const result = Face{
.alloc = alloc,
.font_str = font_str,
.size = size,
.presentation = presentation,
.canvas = canvas,
-
- // We're going to calculate these right after initialization.
- .metrics = undefined,
};
- try result.calcMetrics();
log.debug("face initialized: {s}", .{raw});
return result;
@@ -192,8 +185,6 @@ pub const Face = struct {
glyph_index: u32,
opts: font.face.RenderOptions,
) !font.Glyph {
- _ = opts;
-
var render = try self.renderGlyphInternal(alloc, glyph_index);
defer render.deinit();
@@ -227,34 +218,41 @@ pub const Face = struct {
atlas.set(region, bitmap_formatted);
}
- return font.Glyph{
+ const glyph = font.Glyph{
.width = render.width,
.height = render.height,
- // TODO: this can't be right
- .offset_x = 0,
- .offset_y = 0,
+ .offset_x = render.x_offset,
+ .offset_y = render.y_offset + @as(i32, @intCast(opts.grid_metrics.cell_height)),
.atlas_x = region.x,
.atlas_y = region.y,
.advance_x = 0,
};
+ std.log.err("glyph: {}", .{glyph});
+ return glyph;
}
+ pub const GetMetricsError = error{
+ OutOfMemory,
+ InvalidType,
+ };
+
/// Calculate the metrics associated with a given face.
- fn calcMetrics(self: *Face) !void {
+ pub fn getMetrics(self: *Face) GetMetricsError!font.Metrics.FaceMetrics {
const ctx = try self.context();
defer ctx.deinit();
+ const x_metric = try ctx.call(js.Object, "measureText", .{js.string("x")});
+ defer x_metric.deinit();
+ const M_metric = try ctx.call(js.Object, "measureText", .{js.string("M")});
+ defer M_metric.deinit();
// Cell width is the width of our M text
const cell_width: f32 = cell_width: {
- const metrics = try ctx.call(js.Object, "measureText", .{js.string("M")});
- defer metrics.deinit();
-
// We prefer the bounding box since it is tighter but certain
// text such as emoji do not have a bounding box set so we use
// the full run width instead.
- const bounding_right = try metrics.get(f32, "actualBoundingBoxRight");
+ const bounding_right = try M_metric.get(f32, "actualBoundingBoxRight");
if (bounding_right > 0) break :cell_width bounding_right;
- break :cell_width try metrics.get(f32, "width");
+ break :cell_width try M_metric.get(f32, "width");
};
// To get the cell height we render a high and low character and get
@@ -262,29 +260,15 @@ pub const Face = struct {
// pixel height but this is a more surefire way to get it.
const height_metrics = try ctx.call(js.Object, "measureText", .{js.string("M_")});
defer height_metrics.deinit();
- const asc = try height_metrics.get(f32, "actualBoundingBoxAscent");
- const desc = try height_metrics.get(f32, "actualBoundingBoxDescent");
- const cell_height = asc + desc;
- const cell_baseline = desc;
+ const asc = try height_metrics.get(f32, "fontBoundingBoxAscent");
+ const desc = try height_metrics.get(f32, "fontBoundingBoxDescent");
- // There isn't a declared underline position for canvas measurements
- // so we just go 1 under the cell height to match freetype logic
- // at this time (our freetype logic).
- const underline_position = cell_height - 1;
- const underline_thickness: f32 = 1;
-
- const result = font.Metrics{
- .cell_width = @intFromFloat(cell_width),
- .cell_height = @intFromFloat(cell_height),
- .cell_baseline = @intFromFloat(cell_baseline),
- .underline_position = @intFromFloat(underline_position),
- .underline_thickness = @intFromFloat(underline_thickness),
- .strikethrough_position = @intFromFloat(underline_position),
- .strikethrough_thickness = @intFromFloat(underline_thickness),
+ return .{
+ .ascent = asc,
+ .descent = -desc,
+ .cell_width = cell_width,
+ .line_gap = 0,
};
-
- self.metrics = result;
- log.debug("metrics font={s} value={}", .{ self.font_str, self.metrics });
}
/// Returns the 2d context configured for drawing
@@ -325,6 +309,27 @@ pub const Face = struct {
return ctx;
}
+ pub fn hasColor(_: *const Face) bool {
+ return true;
+ }
+
+ pub fn isColorGlyph(self: *const Face, cp: u32) bool {
+ // Render the glyph
+ var render = self.renderGlyphInternal(self.alloc, cp) catch unreachable;
+ defer render.deinit();
+
+ // Inspect the image data for any non-zeros in the RGB value.
+ // NOTE(perf): this is an easy candidate for SIMD.
+ var i: usize = 0;
+ while (i < render.bitmap.len) : (i += 4) {
+ if (render.bitmap[i] > 0 or
+ render.bitmap[i + 1] > 0 or
+ render.bitmap[i + 2] > 0) return true;
+ }
+
+ return false;
+ }
+
/// An internal (web-canvas-only) format for rendered glyphs
/// since we do render passes in multiple different situations.
const RenderedGlyph = struct {
@@ -332,6 +337,8 @@ pub const Face = struct {
metrics: js.Object,
width: u32,
height: u32,
+ y_offset: i32,
+ x_offset: i32,
bitmap: []u8,
pub fn deinit(self: *RenderedGlyph) void {
@@ -392,6 +399,15 @@ pub const Face = struct {
// Height is our ascender + descender for this char
const height = if (!broken_bbox) @as(u32, @intFromFloat(@ceil(asc + desc))) + 1 else width;
+ const ctx_temp = try self.context();
+ try ctx_temp.set("textBaseline", js.string("top"));
+ // Get the width and height of the render
+ const metrics_2 = try measure_ctx.call(js.Object, "measureText", .{glyph_str});
+ const top_desc = try metrics_2.get(f32, "actualBoundingBoxDescent") + 1;
+ const y_offset = @as(i32, @intCast(height)) - @as(i32, @intFromFloat(top_desc));
+ const x_offset: i32 = @intFromFloat(-left);
+ ctx_temp.deinit();
+
// Note: width and height both get "+ 1" added to them above. This
// is important so that there is a 1px border around the glyph to avoid
// any clipping in the atlas.
@@ -401,15 +417,15 @@ pub const Face = struct {
try self.canvas.set("width", width);
try self.canvas.set("height", height);
- const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width});
- defer alloc.free(width_str);
- const height_str = try std.fmt.allocPrint(alloc, "{d}px", .{height});
- defer alloc.free(height_str);
+ // const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width});
+ // defer alloc.free(width_str);
+ // const height_str = try std.fmt.allocPrint(alloc, "{d}px", .{height});
+ // defer alloc.free(height_str);
- const style = try self.canvas.get(js.Object, "style");
- defer style.deinit();
- try style.set("width", js.string(width_str));
- try style.set("height", js.string(height_str));
+ // const style = try self.canvas.get(js.Object, "style");
+ // defer style.deinit();
+ // try style.set("width", js.string(width_str));
+ // try style.set("height", js.string(height_str));
}
// Reload our context since we resized the canvas
@@ -484,6 +500,8 @@ pub const Face = struct {
.width = width,
.height = height,
.bitmap = bitmap,
+ .y_offset = y_offset,
+ .x_offset = x_offset,
};
}
};
@@ -494,7 +512,7 @@ pub const Wasm = struct {
const alloc = wasm.alloc;
export fn face_new(ptr: [*]const u8, len: usize, pts: u16, p: u16) ?*Face {
- return face_new_(ptr, len, pts, p) catch null;
+ return face_new_(ptr, len, @floatFromInt(pts), p) catch null;
}
fn face_new_(ptr: [*]const u8, len: usize, pts: f32, presentation: u16) !*Face {
@@ -552,7 +570,7 @@ pub const Wasm = struct {
}
fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph {
- const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{});
+ const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) });
const result = try alloc.create(font.Glyph);
errdefer alloc.destroy(result);
diff --git a/src/font/main.zig b/src/font/main.zig
index ffeb42f7a..ead14460b 100644
--- a/src/font/main.zig
+++ b/src/font/main.zig
@@ -33,6 +33,8 @@ comptime {
if (builtin.target.isWasm()) {
_ = Atlas.Wasm;
_ = DeferredFace.Wasm;
+ _ = SharedGrid.Wasm;
+ _ = @import("Collection.zig").Wasm;
_ = face.web_canvas.Wasm;
_ = shape.web_canvas.Wasm;
}
diff --git a/src/font/shaper/web_canvas.zig b/src/font/shaper/web_canvas.zig
index f38ab885a..0864ce048 100644
--- a/src/font/shaper/web_canvas.zig
+++ b/src/font/shaper/web_canvas.zig
@@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const ziglyph = @import("ziglyph");
const font = @import("../main.zig");
const terminal = @import("../../terminal/main.zig");
+const SharedGrid = font.SharedGrid;
const log = std.log.scoped(.font_shaper);
@@ -30,19 +31,19 @@ pub const Shaper = struct {
alloc: Allocator,
/// The shared memory used for shaping results.
- cell_buf: []font.shape.Cell,
+ cell_buf: std.ArrayListUnmanaged(font.shape.Cell),
/// The shared memory used for storing information about a run.
run_buf: RunBuf,
/// The cell_buf argument is the buffer to use for storing shaped results.
/// This should be at least the number of columns in the terminal.
- pub fn init(alloc: Allocator, opts: font.shape.Options) !Shaper {
+ pub fn init(alloc: Allocator, _: font.shape.Options) !Shaper {
// Note: we do not support opts.font_features
return Shaper{
.alloc = alloc,
- .cell_buf = opts.cell_buf,
+ .cell_buf = .{},
.run_buf = .{},
};
}
@@ -61,14 +62,16 @@ pub const Shaper = struct {
/// for a Shaper struct since they share state.
pub fn runIterator(
self: *Shaper,
- group: *font.GroupCache,
- row: terminal.Screen.Row,
+ grid: *SharedGrid,
+ screen: *const terminal.Screen,
+ row: terminal.Pin,
selection: ?terminal.Selection,
cursor_x: ?usize,
) font.shape.RunIterator {
return .{
.hooks = .{ .shaper = self },
- .group = group,
+ .grid = grid,
+ .screen = screen,
.row = row,
.selection = selection,
.cursor_x = cursor_x,
@@ -90,21 +93,22 @@ pub const Shaper = struct {
const clusters = self.run_buf.items(.cluster);
assert(codepoints.len == clusters.len);
+ self.cell_buf.clearRetainingCapacity();
switch (codepoints.len) {
// Special cases: if we have no codepoints (is this possible?)
// then our result is also an empty cell run.
- 0 => return self.cell_buf[0..0],
+ 0 => return self.cell_buf.items[0..0],
// If we have only 1 codepoint, then we assume that it is
// a single grapheme and just let it through. At this point,
// we can't have any more information to do anything else.
1 => {
- self.cell_buf[0] = .{
+ try self.cell_buf.append(self.alloc, .{
.x = @intCast(clusters[0]),
.glyph_index = codepoints[0],
- };
+ });
- return self.cell_buf[0..1];
+ return self.cell_buf.items[0..1];
},
else => {},
@@ -151,10 +155,10 @@ pub const Shaper = struct {
switch (len) {
// If we have only a single codepoint then just render it
// as-is.
- 1 => self.cell_buf[cur] = .{
+ 1 => try self.cell_buf.append(self.alloc, .{
.x = @intCast(clusters[start]),
.glyph_index = codepoints[start],
- },
+ }),
// We must have multiple codepoints (see assert above). In
// this case we UTF-8 encode the codepoints and send them
@@ -190,13 +194,13 @@ pub const Shaper = struct {
};
defer self.alloc.free(cluster);
- var face = try run.group.group.faceFromIndex(run.font_index);
+ var face = try run.grid.resolver.collection.getFace(run.font_index);
const index = try face.graphemeGlyphIndex(cluster);
- self.cell_buf[cur] = .{
+ try self.cell_buf.append(self.alloc, .{
.x = @intCast(clusters[start]),
.glyph_index = index,
- };
+ });
},
}
@@ -204,7 +208,7 @@ pub const Shaper = struct {
cur += 1;
}
- return self.cell_buf[0..cur];
+ return self.cell_buf.items[0..cur];
}
/// The hooks for RunIterator.
@@ -238,15 +242,12 @@ pub const Wasm = struct {
const wasm = @import("../../os/wasm.zig");
const alloc = wasm.alloc;
- export fn shaper_new(cap: usize) ?*Shaper {
- return shaper_new_(cap) catch null;
+ export fn shaper_new() ?*Shaper {
+ return shaper_new_() catch null;
}
- fn shaper_new_(cap: usize) !*Shaper {
- const cell_buf = try alloc.alloc(font.shape.Cell, cap);
- errdefer alloc.free(cell_buf);
-
- var shaper = try Shaper.init(alloc, .{ .cell_buf = cell_buf });
+ fn shaper_new_() !*Shaper {
+ var shaper = try Shaper.init(alloc, .{});
errdefer shaper.deinit();
const result = try alloc.create(Shaper);
@@ -257,7 +258,6 @@ pub const Wasm = struct {
export fn shaper_free(ptr: ?*Shaper) void {
if (ptr) |v| {
- alloc.free(v.cell_buf);
v.deinit();
alloc.destroy(v);
}
@@ -266,45 +266,130 @@ pub const Wasm = struct {
/// Runs a test to verify shaping works properly.
export fn shaper_test(
self: *Shaper,
- group: *font.GroupCache,
+ grid: *SharedGrid,
str: [*]const u8,
len: usize,
) void {
- shaper_test_(self, group, str[0..len]) catch |err| {
+ shaper_test_(self, grid, str[0..len]) catch |err| {
log.warn("error during shaper test err={}", .{err});
};
}
+ const js = @import("zig-js");
- fn shaper_test_(self: *Shaper, group: *font.GroupCache, str: []const u8) !void {
- // Create a terminal and print all our characters into it.
- var term = try terminal.Terminal.init(alloc, self.cell_buf.len, 80);
+ fn createImageData(self: *font.Atlas) !js.Object {
+ // We need to draw pixels so this is format dependent.
+ const buf: []u8 = switch (self.format) {
+ // RGBA is the native ImageData format
+ .rgba => self.data,
+
+ .grayscale => buf: {
+ // Convert from A8 to RGBA so every 4th byte is set to a value.
+ var buf: []u8 = try alloc.alloc(u8, self.data.len * 4);
+ errdefer alloc.free(buf);
+ @memset(buf, 0);
+ for (self.data, 0..) |value, i| {
+ buf[(i * 4) + 3] = value;
+ }
+ break :buf buf;
+ },
+
+ else => return error.UnsupportedAtlasFormat,
+ };
+ defer if (buf.ptr != self.data.ptr) alloc.free(buf);
+
+ // Create an ImageData from our buffer and then write it to the canvas
+ const image_data: js.Object = data: {
+ // Get our runtime memory
+ const mem = try js.runtime.get(js.Object, "memory");
+ defer mem.deinit();
+ const mem_buf = try mem.get(js.Object, "buffer");
+ defer mem_buf.deinit();
+
+ // Create an array that points to our buffer
+ const arr = arr: {
+ const Uint8ClampedArray = try js.global.get(js.Object, "Uint8ClampedArray");
+ defer Uint8ClampedArray.deinit();
+ const arr = try Uint8ClampedArray.new(.{ mem_buf, buf.ptr, buf.len });
+ if (!wasm.shared_mem) break :arr arr;
+
+ // If we're sharing memory then we have to copy the data since
+ // we can't set ImageData directly using a SharedArrayBuffer.
+ defer arr.deinit();
+ break :arr try arr.call(js.Object, "slice", .{});
+ };
+ defer arr.deinit();
+
+ // Create the image data from our array
+ const ImageData = try js.global.get(js.Object, "ImageData");
+ defer ImageData.deinit();
+ const data = try ImageData.new(.{ arr, self.size, self.size });
+ errdefer data.deinit();
+
+ break :data data;
+ };
+
+ return image_data;
+ }
+
+ fn shaper_test_(self: *Shaper, grid: *SharedGrid, str: []const u8) !void {
+ // Make a screen with some data
+ var term = try terminal.Terminal.init(alloc, .{ .cols = 20, .rows = 5 });
defer term.deinit(alloc);
+ try term.printString(str);
- // Iterate over unicode codepoints and add to terminal
- {
- const view = try std.unicode.Utf8View.init(str);
- var iter = view.iterator();
- while (iter.nextCodepoint()) |c| {
- try term.print(c);
+ // Get our run iterator
+
+ var row_it = term.screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
+ var y: usize = 0;
+ const Render = struct {
+ render: SharedGrid.Render,
+ y: usize,
+ x: usize,
+ };
+ var cell_list: std.ArrayListUnmanaged(Render) = .{};
+ defer cell_list.deinit(wasm.alloc);
+ while (row_it.next()) |row| {
+ defer y += 1;
+ var it = self.runIterator(grid, &term.screen, row, null, null);
+ while (try it.next(alloc)) |run| {
+ const cells = try self.shape(run);
+ for (cells) |cell| {
+ const render = try grid.renderGlyph(wasm.alloc, run.font_index, cell.glyph_index, .{ .grid_metrics = grid.metrics });
+ try cell_list.append(wasm.alloc, .{ .render = render, .x = cell.x, .y = y });
+
+ log.info("y={} x={} width={} height={} ax={} ay={} base={}", .{
+ y * grid.metrics.cell_height,
+ cell.x * grid.metrics.cell_width,
+ render.glyph.width,
+ render.glyph.height,
+ render.glyph.atlas_x,
+ render.glyph.atlas_y,
+ grid.metrics.cell_baseline,
+ });
+ }
}
}
+ const colour_data = try createImageData(&grid.atlas_color);
+ const gray_data = try createImageData(&grid.atlas_grayscale);
- // Iterate over the rows and print out all the runs we get.
- var rowIter = term.screen.rowIterator(.viewport);
- var y: usize = 0;
- while (rowIter.next()) |row| {
- defer y += 1;
-
- var iter = self.runIterator(group, row, null, null);
- while (try iter.next(alloc)) |run| {
- const cells = try self.shape(run);
- log.info("y={} run={d} shape={any} idx={}", .{
- y,
- run.cells,
- cells,
- run.font_index,
- });
- }
+ const doc = try js.global.get(js.Object, "document");
+ defer doc.deinit();
+ const canvas = try doc.call(js.Object, "getElementById", .{js.string("shaper-canvas")});
+ errdefer canvas.deinit();
+ const ctx = try canvas.call(js.Object, "getContext", .{js.string("2d")});
+ defer ctx.deinit();
+ for (cell_list.items) |cell| {
+ const x_start = -@as(isize, @intCast(cell.render.glyph.atlas_x));
+ const y_start = -@as(isize, @intCast(cell.render.glyph.atlas_y));
+ try ctx.call(void, "putImageData", .{
+ if (cell.render.presentation == .emoji) colour_data else gray_data,
+ x_start + @as(isize, @intCast(cell.x * grid.metrics.cell_width)) + cell.render.glyph.offset_x,
+ y_start + @as(isize, @intCast((cell.y + 1) * grid.metrics.cell_height)) - cell.render.glyph.offset_y,
+ cell.render.glyph.atlas_x,
+ cell.render.glyph.atlas_y,
+ cell.render.glyph.width,
+ cell.render.glyph.height,
+ });
}
}
};
diff --git a/src/main_wasm.zig b/src/main_wasm.zig
index bffe5e4b7..d4b7d63e5 100644
--- a/src/main_wasm.zig
+++ b/src/main_wasm.zig
@@ -11,16 +11,17 @@ comptime {
_ = @import("App.zig").Wasm;
}
-pub const std_options = struct {
+pub const std_options: std.Options = .{
// Set our log level. We try to get as much logging as possible but in
// ReleaseSmall mode where we're optimizing for space, we elevate the
// log level.
- pub const log_level: std.log.Level = switch (builtin.mode) {
- .Debug => .debug,
- .ReleaseSmall => .warn,
- else => .info,
- };
+ // .log_level = switch (builtin.mode) {
+ // .Debug => .debug,
+ // .ReleaseSmall => .warn,
+ // else => .info,
+ // },
+ .log_level = .info,
// Set our log function
- pub const logFn = @import("os/wasm/log.zig").log;
+ .logFn = @import("os/wasm/log.zig").log,
};
diff --git a/src/os/desktop.zig b/src/os/desktop.zig
index c73f150e0..465d4e751 100644
--- a/src/os/desktop.zig
+++ b/src/os/desktop.zig
@@ -56,6 +56,8 @@ pub fn launchedFromDesktop() bool {
// iPhone/iPad is always launched from the "desktop"
.ios => true,
+ .freestanding => false,
+
else => @compileError("unsupported platform"),
};
}
diff --git a/src/os/homedir.zig b/src/os/homedir.zig
index b5629fd65..e2733e6ce 100644
--- a/src/os/homedir.zig
+++ b/src/os/homedir.zig
@@ -20,6 +20,8 @@ pub inline fn home(buf: []u8) !?[]const u8 {
// iOS doesn't have a user-writable home directory
.ios => null,
+ .wasi => null,
+
else => @compileError("unimplemented"),
};
}
diff --git a/src/os/main.zig b/src/os/main.zig
index cb9355931..1d7aaa575 100644
--- a/src/os/main.zig
+++ b/src/os/main.zig
@@ -12,6 +12,8 @@ const mouse = @import("mouse.zig");
const openpkg = @import("open.zig");
const pipepkg = @import("pipe.zig");
const resourcesdir = @import("resourcesdir.zig");
+const builtin = @import("builtin");
+const std = @import("std");
// Namespaces
pub const args = @import("args.zig");
@@ -51,3 +53,8 @@ pub const OpenType = openpkg.Type;
pub const pipe = pipepkg.pipe;
pub const resourcesDir = resourcesdir.resourcesDir;
pub const ShellEscapeWriter = shell.ShellEscapeWriter;
+pub const Instant = if (builtin.cpu.arch != .wasm32) std.time.Instant else struct {
+ fn now() !@This() {
+ return .{};
+ }
+};
diff --git a/src/terminal/kitty/graphics_image.zig b/src/terminal/kitty/graphics_image.zig
index 094e1622b..ae207f9e7 100644
--- a/src/terminal/kitty/graphics_image.zig
+++ b/src/terminal/kitty/graphics_image.zig
@@ -349,7 +349,7 @@ pub const LoadingImage = struct {
}
// Set our time
- self.image.transmit_time = std.time.Instant.now() catch |err| {
+ self.image.transmit_time = internal_os.Instant.now() catch |err| {
log.warn("failed to get time: {}", .{err});
return error.InternalError;
};
@@ -456,7 +456,7 @@ pub const Image = struct {
format: command.Transmission.Format = .rgb,
compression: command.Transmission.Compression = .none,
data: []const u8 = "",
- transmit_time: std.time.Instant = undefined,
+ transmit_time: internal_os.Instant = undefined,
/// Set this to true if this image was loaded by a command that
/// doesn't specify an ID or number, since such commands should
diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig
index ffd3aa580..97a2839f0 100644
--- a/src/terminal/kitty/graphics_storage.zig
+++ b/src/terminal/kitty/graphics_storage.zig
@@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const terminal = @import("../main.zig");
+const internal_os = @import("../../os/main.zig");
const point = @import("../point.zig");
const size = @import("../size.zig");
const command = @import("graphics_command.zig");
@@ -498,7 +499,7 @@ pub const ImageStorage = struct {
// bit is fine compared to the megabytes we're looking to save.
const Candidate = struct {
id: u32,
- time: std.time.Instant,
+ time: internal_os.Instant,
used: bool,
};
diff --git a/src/terminal/page.zig b/src/terminal/page.zig
index 30f6658aa..5b225cf64 100644
--- a/src/terminal/page.zig
+++ b/src/terminal/page.zig
@@ -320,7 +320,7 @@ pub const Page = struct {
/// when runtime safety is enabled. This is a no-op when runtime
/// safety is disabled. This uses the libc allocator.
pub fn assertIntegrity(self: *const Page) void {
- if (comptime build_config.slow_runtime_safety) {
+ if (comptime build_config.slow_runtime_safety and builtin.cpu.arch != .wasm32) {
self.verifyIntegrity(std.heap.c_allocator) catch |err| {
log.err("page integrity violation, crashing. err={}", .{err});
@panic("page integrity violation");
diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig
index 864f2e21c..934e3edfd 100644
--- a/src/termio/Exec.zig
+++ b/src/termio/Exec.zig
@@ -102,7 +102,7 @@ pub fn threadEnter(
};
// Track our process start time for abnormal exits
- const process_start = try std.time.Instant.now();
+ const process_start = try internal_os.Instant.now();
// Create our pipe that we'll use to kill our read thread.
// pipe[0] is the read end, pipe[1] is the write end.
@@ -354,7 +354,7 @@ fn processExit(
// Determine how long the process was running for.
const runtime_ms: ?u64 = runtime: {
- const process_end = std.time.Instant.now() catch break :runtime null;
+ const process_end = internal_os.Instant.now() catch break :runtime null;
const runtime_ns = process_end.since(execdata.start);
const runtime_ms = runtime_ns / std.time.ns_per_ms;
break :runtime runtime_ms;
@@ -612,7 +612,7 @@ pub const ThreadData = struct {
const WRITE_REQ_PREALLOC = std.math.pow(usize, 2, 5);
/// Process start time and boolean of whether its already exited.
- start: std.time.Instant,
+ start: internal_os.Instant,
exited: bool = false,
/// The number of milliseconds below which we consider a process
diff --git a/src/termio/Termio.zig b/src/termio/Termio.zig
index ab61ae4ca..ec8d20e0f 100644
--- a/src/termio/Termio.zig
+++ b/src/termio/Termio.zig
@@ -68,7 +68,7 @@ terminal_stream: terminal.Stream(StreamHandler),
/// Last time the cursor was reset. This is used to prevent message
/// flooding with cursor resets.
-last_cursor_reset: ?std.time.Instant = null,
+last_cursor_reset: ?internal_os.Instant = null,
/// The configuration for this IO that is derived from the main
/// configuration. This must be exported so that we don't need to
@@ -572,7 +572,7 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
// non-blink state so it is rendered if visible. If we're under
// HEAVY read load, we don't want to send a ton of these so we
// use a timer under the covers
- if (std.time.Instant.now()) |now| cursor_reset: {
+ if (internal_os.Instant.now()) |now| cursor_reset: {
if (self.last_cursor_reset) |last| {
if (now.since(last) <= (500 * std.time.ns_per_ms)) {
break :cursor_reset;