wasm shaping and atlas rendering again

This commit is contained in:
Gabriel Dinner-David
2024-11-20 23:58:45 -05:00
parent 873ebc368c
commit f8e201c5c4
26 changed files with 358 additions and 264 deletions

152
build.zig
View File

@ -748,7 +748,7 @@ pub fn build(b: *std.Build) !void {
break :config copy; break :config copy;
}; };
const wasm = b.addSharedLibrary(.{ const wasm = b.addExecutable(.{
.name = "ghostty-wasm", .name = "ghostty-wasm",
.root_source_file = b.path("src/main_wasm.zig"), .root_source_file = b.path("src/main_wasm.zig"),
.target = b.resolveTargetQuery(wasm_crosstarget), .target = b.resolveTargetQuery(wasm_crosstarget),
@ -757,7 +757,10 @@ pub fn build(b: *std.Build) !void {
// So that we can use web workers with our wasm binary // So that we can use web workers with our wasm binary
wasm.import_memory = true; wasm.import_memory = true;
wasm.initial_memory = 65536 * 25; 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.max_memory = 65536 * 65536; // Maximum number of pages in wasm32
wasm.shared_memory = wasm_shared; wasm.shared_memory = wasm_shared;
@ -769,10 +772,11 @@ pub fn build(b: *std.Build) !void {
// Install // Install
const wasm_install = b.addInstallArtifact(wasm, .{}); const wasm_install = b.addInstallArtifact(wasm, .{});
wasm_install.dest_dir = .{ .prefix = {} }; 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"); const step = b.step("wasm", "Build the wasm library");
step.dependOn(&wasm_install.step); step.dependOn(&install.step);
// We support tests via wasmtime. wasmtime uses WASI so this // We support tests via wasmtime. wasmtime uses WASI so this
// isn't an exact match to our freestanding target above but // isn't an exact match to our freestanding target above but
@ -1073,81 +1077,82 @@ fn addDeps(
try static_libs.append(fontconfig_dep.artifact("fontconfig").getEmittedBin()); try static_libs.append(fontconfig_dep.artifact("fontconfig").getEmittedBin());
} }
} }
if (step.rootModuleTarget().cpu.arch != .wasm32) {
// Libpng - Ghostty doesn't actually use this directly, its only used
// through dependencies, so we only need to add it to our static
// libs list if we're not using system integration. The dependencies
// will handle linking it.
if (!b.systemIntegrationOption("libpng", .{})) {
const libpng_dep = b.dependency("libpng", .{
.target = target,
.optimize = optimize,
});
step.linkLibrary(libpng_dep.artifact("png"));
try static_libs.append(libpng_dep.artifact("png").getEmittedBin());
}
// Libpng - Ghostty doesn't actually use this directly, its only used // Zlib - same as libpng, only used through dependencies.
// through dependencies, so we only need to add it to our static if (!b.systemIntegrationOption("zlib", .{})) {
// libs list if we're not using system integration. The dependencies const zlib_dep = b.dependency("zlib", .{
// will handle linking it. .target = target,
if (!b.systemIntegrationOption("libpng", .{})) { .optimize = optimize,
const libpng_dep = b.dependency("libpng", .{ });
step.linkLibrary(zlib_dep.artifact("z"));
try static_libs.append(zlib_dep.artifact("z").getEmittedBin());
}
// Oniguruma
const oniguruma_dep = b.dependency("oniguruma", .{
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
step.linkLibrary(libpng_dep.artifact("png")); step.root_module.addImport("oniguruma", oniguruma_dep.module("oniguruma"));
try static_libs.append(libpng_dep.artifact("png").getEmittedBin()); if (b.systemIntegrationOption("oniguruma", .{})) {
} // Oniguruma is compiled and distributed as libonig.so
step.linkSystemLibrary2("onig", dynamic_link_opts);
} else {
step.linkLibrary(oniguruma_dep.artifact("oniguruma"));
try static_libs.append(oniguruma_dep.artifact("oniguruma").getEmittedBin());
}
// Zlib - same as libpng, only used through dependencies. // Glslang
if (!b.systemIntegrationOption("zlib", .{})) { const glslang_dep = b.dependency("glslang", .{
const zlib_dep = b.dependency("zlib", .{
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
step.linkLibrary(zlib_dep.artifact("z")); step.root_module.addImport("glslang", glslang_dep.module("glslang"));
try static_libs.append(zlib_dep.artifact("z").getEmittedBin()); if (b.systemIntegrationOption("glslang", .{})) {
} step.linkSystemLibrary2("glslang", dynamic_link_opts);
step.linkSystemLibrary2("glslang-default-resource-limits", dynamic_link_opts);
} else {
step.linkLibrary(glslang_dep.artifact("glslang"));
try static_libs.append(glslang_dep.artifact("glslang").getEmittedBin());
}
// Oniguruma // Spirv-cross
const oniguruma_dep = b.dependency("oniguruma", .{ const spirv_cross_dep = b.dependency("spirv_cross", .{
.target = target,
.optimize = optimize,
});
step.root_module.addImport("oniguruma", oniguruma_dep.module("oniguruma"));
if (b.systemIntegrationOption("oniguruma", .{})) {
// Oniguruma is compiled and distributed as libonig.so
step.linkSystemLibrary2("onig", dynamic_link_opts);
} else {
step.linkLibrary(oniguruma_dep.artifact("oniguruma"));
try static_libs.append(oniguruma_dep.artifact("oniguruma").getEmittedBin());
}
// Glslang
const glslang_dep = b.dependency("glslang", .{
.target = target,
.optimize = optimize,
});
step.root_module.addImport("glslang", glslang_dep.module("glslang"));
if (b.systemIntegrationOption("glslang", .{})) {
step.linkSystemLibrary2("glslang", dynamic_link_opts);
step.linkSystemLibrary2("glslang-default-resource-limits", dynamic_link_opts);
} else {
step.linkLibrary(glslang_dep.artifact("glslang"));
try static_libs.append(glslang_dep.artifact("glslang").getEmittedBin());
}
// Spirv-cross
const spirv_cross_dep = b.dependency("spirv_cross", .{
.target = target,
.optimize = optimize,
});
step.root_module.addImport("spirv_cross", spirv_cross_dep.module("spirv_cross"));
if (b.systemIntegrationOption("spirv-cross", .{})) {
step.linkSystemLibrary2("spirv-cross", dynamic_link_opts);
} else {
step.linkLibrary(spirv_cross_dep.artifact("spirv_cross"));
try static_libs.append(spirv_cross_dep.artifact("spirv_cross").getEmittedBin());
}
// Simdutf
if (b.systemIntegrationOption("simdutf", .{})) {
step.linkSystemLibrary2("simdutf", dynamic_link_opts);
} else {
const simdutf_dep = b.dependency("simdutf", .{
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
}); });
step.linkLibrary(simdutf_dep.artifact("simdutf")); step.root_module.addImport("spirv_cross", spirv_cross_dep.module("spirv_cross"));
try static_libs.append(simdutf_dep.artifact("simdutf").getEmittedBin()); if (b.systemIntegrationOption("spirv-cross", .{})) {
step.linkSystemLibrary2("spirv-cross", dynamic_link_opts);
} else {
step.linkLibrary(spirv_cross_dep.artifact("spirv_cross"));
try static_libs.append(spirv_cross_dep.artifact("spirv_cross").getEmittedBin());
}
// Simdutf
if (b.systemIntegrationOption("simdutf", .{})) {
step.linkSystemLibrary2("simdutf", dynamic_link_opts);
} else {
const simdutf_dep = b.dependency("simdutf", .{
.target = target,
.optimize = optimize,
});
step.linkLibrary(simdutf_dep.artifact("simdutf"));
try static_libs.append(simdutf_dep.artifact("simdutf").getEmittedBin());
}
} }
// Sentry // Sentry
@ -1157,7 +1162,7 @@ fn addDeps(
.backend = .breakpad, .backend = .breakpad,
}); });
step.root_module.addImport("sentry", sentry_dep.module("sentry")); step.root_module.addImport("sentry", sentry_dep.module("sentry"));
if (target.result.os.tag != .windows) { if (target.result.os.tag != .windows and target.result.cpu.arch != .wasm32) {
// Sentry // Sentry
step.linkLibrary(sentry_dep.artifact("sentry")); step.linkLibrary(sentry_dep.artifact("sentry"));
try static_libs.append(sentry_dep.artifact("sentry").getEmittedBin()); try static_libs.append(sentry_dep.artifact("sentry").getEmittedBin());
@ -1177,6 +1182,17 @@ fn addDeps(
.optimize = optimize, .optimize = optimize,
}); });
step.root_module.addImport("zig-js", js_dep.module("zig-js")); step.root_module.addImport("zig-js", js_dep.module("zig-js"));
step.root_module.addImport("ziglyph", b.dependency("ziglyph", .{
.target = target,
.optimize = optimize,
}).module("ziglyph"));
step.root_module.addImport("z2d", b.addModule("z2d", .{
.root_source_file = b.dependency("z2d", .{}).path("src/z2d.zig"),
.target = target,
.optimize = optimize,
}));
// step.linkLibC();
try addUnicodeTables(b, step);
return static_libs; return static_libs;
} }

View File

@ -1,11 +1,11 @@
import { ZigJS } from "zig-js"; import { ZigJS } from "zig-js/src/index.ts";
const zjs = new ZigJS(); const zjs = new ZigJS();
const importObject = { const importObject = {
module: {}, module: {},
env: { env: {
memory: new WebAssembly.Memory({ memory: new WebAssembly.Memory({
initial: 25, initial: 512,
maximum: 65536, maximum: 65536,
shared: true, shared: true,
}), }),
@ -27,38 +27,36 @@ fetch(url.href)
.then((results) => { .then((results) => {
const memory = importObject.env.memory; const memory = importObject.env.memory;
const { const {
malloc, atlas_clear,
free, atlas_debug_canvas,
config_new, atlas_free,
config_free, atlas_grow,
config_load_string, atlas_new,
config_finalize, atlas_reserve,
face_new, atlas_set,
face_free, config_finalize,
face_render_glyph, config_free,
face_debug_canvas, config_load_string,
deferred_face_new, config_new,
deferred_face_free, deferred_face_free,
deferred_face_load, deferred_face_load,
deferred_face_face, deferred_face_new,
group_new, face_debug_canvas,
group_free, face_free,
group_add_face, face_new,
group_init_sprite_face, face_render_glyph,
group_index_for_codepoint, free,
group_render_glyph, malloc,
group_cache_new, shaper_free,
group_cache_free, shaper_new,
group_cache_index_for_codepoint, shaper_test,
group_cache_render_glyph, collection_new,
group_cache_atlas_grayscale, collection_add_deferred_face,
group_cache_atlas_color, shared_grid_new,
atlas_new, shared_grid_atlas_grayscale,
atlas_free, shared_grid_atlas_color,
atlas_debug_canvas, shared_grid_index_for_codepoint,
shaper_new, shared_grid_render_glyph,
shaper_free,
shaper_test,
} = results.instance.exports; } = results.instance.exports;
// Give us access to the zjs value for debugging. // Give us access to the zjs value for debugging.
globalThis.zjs = zjs; globalThis.zjs = zjs;
@ -81,109 +79,110 @@ fetch(url.href)
config_load_string(config, config_str.ptr, config_str.len); config_load_string(config, config_str.ptr, config_str.len);
config_finalize(config); config_finalize(config);
free(config_str.ptr); free(config_str.ptr);
// Create our atlas // Create our atlas
// const atlas = atlas_new(512, 0 /* grayscale */); // const atlas = atlas_new(512, 0 /* grayscale */);
// Create some memory for our string // Create some memory for our string
const font_name = makeStr("monospace"); const font_name = makeStr("monospace");
// Initialize our deferred face // Initialize our deferred face
// const df = deferred_face_new(font_ptr, font.byteLength, 0 /* text */); // const df = deferred_face_new(font_ptr, font.byteLength, 0 /* text */);
//deferred_face_load(df, 72 /* size */); //deferred_face_load(df, 72 /* size */);
//const face = deferred_face_face(df); //const face = deferred_face_face(df);
// Initialize our font face // Initialize our font face
//const face = face_new(font_ptr, font.byteLength, 72 /* size in px */); //const face = face_new(font_ptr, font.byteLength, 72 /* size in px */);
//free(font_ptr); //free(font_ptr);
// Create our group // Create our group
const group = group_new(32 /* size */); const collection = collection_new(24);
group_add_face( collection_add_deferred_face(
group, collection,
0 /* regular */, 0 /* regular */,
deferred_face_new(font_name.ptr, font_name.len, 0 /* text */), deferred_face_new(font_name.ptr, font_name.len, 0 /* text */),
); );
group_add_face( collection_add_deferred_face(
group, collection,
0 /* regular */, 0 /* regular */,
deferred_face_new(font_name.ptr, font_name.len, 1 /* emoji */), 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. // 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 // // Create our group cache
const group_cache = group_cache_new(group); // const group_cache = group_cache_new(group);
// Render a glyph // Render a glyph
// for (let i = 33; i <= 126; i++) { for (let i = 33; i <= 126; i++) {
// const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
// group_cache_render_glyph(group_cache, font_idx, i, 0); shared_grid_render_glyph(grid, font_idx, i, 0);
// //face_render_glyph(face, atlas, i); //face_render_glyph(face, atlas, i);
// } }
// //
// const emoji = ["🐏","🌞","🌚","🍱","💿","🐈","📃","📀","🕡","🙃"]; const emoji = ["🐏","🌞","🌚","🍱","💿","🐈","📃","📀","🕡","🙃"];
// for (let i = 0; i < emoji.length; i++) { for (let i = 0; i < emoji.length; i++) {
// const cp = emoji[i].codePointAt(0); const cp = emoji[i].codePointAt(0);
// const font_idx = group_cache_index_for_codepoint(group_cache, cp, 0, -1 /* best choice */); const font_idx = shared_grid_index_for_codepoint(grid, cp, 0, -1 /* best choice */);
// group_cache_render_glyph(group_cache, font_idx, cp, 0); shared_grid_render_glyph(grid, font_idx, cp, 0);
// } }
for (let i = 0x2500; i <= 0x257f; i++) { for (let i = 0x2500; i <= 0x257f; i++) {
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
group_cache_render_glyph(group_cache, font_idx, i, 0); shared_grid_render_glyph(grid, font_idx, i, 0);
} }
for (let i = 0x2580; i <= 0x259f; i++) { for (let i = 0x2580; i <= 0x259f; i++) {
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
group_cache_render_glyph(group_cache, font_idx, i, 0); shared_grid_render_glyph(grid, font_idx, i, 0);
} }
for (let i = 0x2800; i <= 0x28ff; i++) { for (let i = 0x2800; i <= 0x28ff; i++) {
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
group_cache_render_glyph(group_cache, font_idx, i, 0); shared_grid_render_glyph(grid, font_idx, i, 0);
} }
for (let i = 0x1fb00; i <= 0x1fb3b; i++) { for (let i = 0x1fb00; i <= 0x1fb3b; i++) {
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
group_cache_render_glyph(group_cache, font_idx, i, 0); shared_grid_render_glyph(grid, font_idx, i, 0);
} }
for (let i = 0x1fb3c; i <= 0x1fb6b; i++) { for (let i = 0x1fb3c; i <= 0x1fb6b; i++) {
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); const font_idx = shared_grid_index_for_codepoint(grid, i, 0, -1);
group_cache_render_glyph(group_cache, font_idx, i, 0); shared_grid_render_glyph(grid, font_idx, i, 0);
} }
//face_render_glyph(face, atlas, "橋".codePointAt(0)); //face_render_glyph(face, atlas, "橋".codePointAt(0));
//face_render_glyph(face, atlas, "p".codePointAt(0)); //face_render_glyph(face, atlas, "p".codePointAt(0));
// Debug our canvas // Debug our canvas
//face_debug_canvas(face); //face_debug_canvas(face);
// Let's try shaping // Let's try shaping
const shaper = shaper_new(120); const shaper = shaper_new(120);
//const input = makeStr("hello🐏"); //const input = makeStr("hello🐏");
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 cp = 1114112;
const font_idx = group_cache_index_for_codepoint( const font_idx = shared_grid_index_for_codepoint(
group_cache, grid,
cp, cp,
0, 0,
-1 /* best choice */, -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 // 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); const id = atlas_debug_canvas(atlas);
document.getElementById("atlas-canvas").append(zjs.deleteValue(id)); 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); const id = atlas_debug_canvas(atlas);
document.getElementById("atlas-color-canvas").append(zjs.deleteValue(id)); document.getElementById("atlas-color-canvas").append(zjs.deleteValue(id));
} }
//face_free(face); //face_free(face);
}); });

View File

@ -9,7 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"zig-js": "file:../vendor/zig-js/js" "zig-js": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main"
}, },
"devDependencies": { "devDependencies": {
"@parcel/transformer-inline-string": "^2.8.0", "@parcel/transformer-inline-string": "^2.8.0",
@ -20,20 +20,6 @@
"../js": { "../js": {
"extraneous": true "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": { "node_modules/@babel/code-frame": {
"version": "7.18.6", "version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@ -2693,8 +2679,11 @@
} }
}, },
"node_modules/zig-js": { "node_modules/zig-js": {
"resolved": "../vendor/zig-js/js", "name": "zig-js-glue",
"link": true "version": "0.1.3",
"resolved": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main",
"integrity": "sha512-CdM4TmAINU1fsZMm0S3dH4XzQgCIC4AWfztA2eGRD9Tfk/2LfCZ7RgOED7i26P1Jf2+BCQIBMbxXocV7oxR3Ig==",
"license": "MIT"
} }
}, },
"dependencies": { "dependencies": {
@ -4421,16 +4410,8 @@
"dev": true "dev": true
}, },
"zig-js": { "zig-js": {
"version": "file:../vendor/zig-js/js", "version": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main",
"requires": { "integrity": "sha512-CdM4TmAINU1fsZMm0S3dH4XzQgCIC4AWfztA2eGRD9Tfk/2LfCZ7RgOED7i26P1Jf2+BCQIBMbxXocV7oxR3Ig=="
"@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"
}
} }
} }
} }

View File

@ -17,6 +17,6 @@
"typescript": "^4.9.3" "typescript": "^4.9.3"
}, },
"dependencies": { "dependencies": {
"zig-js": "file:../vendor/zig-js/js" "zig-js": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main"
} }
} }

View File

@ -116,7 +116,7 @@ pub fn build(b: *std.Build) !void {
.flags = flags.items, .flags = flags.items,
}), }),
.freestanding => {}, .freestanding, .wasi => {},
else => { else => {
std.log.warn("target={} not supported", .{target.result.os.tag}); std.log.warn("target={} not supported", .{target.result.os.tag});

View File

@ -64,7 +64,7 @@ font_grid_set: font.SharedGridSet,
// Used to rate limit desktop notifications. Some platforms (notably macOS) will // 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 // run out of resources if desktop notifications are sent too fast and the OS
// will kill Ghostty. // will kill Ghostty.
last_notification_time: ?std.time.Instant = null, last_notification_time: ?internal_os.Instant = null,
last_notification_digest: u64 = 0, last_notification_digest: u64 = 0,
/// The conditional state of the configuration. See the equivalent field /// The conditional state of the configuration. See the equivalent field

View File

@ -174,7 +174,7 @@ const Mouse = struct {
/// The left click time was the last time the left click was done. This /// The left click time was the last time the left click was done. This
/// is always set on the first left click. /// is always set on the first left click.
left_click_count: u8 = 0, 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. /// The last x/y sent for mouse reports.
event_point: ?terminal.point.Coordinate = null, event_point: ?terminal.point.Coordinate = null,
@ -2724,7 +2724,7 @@ pub fn mouseButtonCallback(
// If we are within the interval that the click would register // If we are within the interval that the click would register
// an increment then we do not extend the selection. // 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); const since = now.since(self.mouse.left_click_time);
if (since <= self.config.mouse_interval) { if (since <= self.config.mouse_interval) {
// Click interval very short, we may be increasing // Click interval very short, we may be increasing
@ -2870,7 +2870,7 @@ pub fn mouseButtonCallback(
self.mouse.left_click_ypos = pos.y; self.mouse.left_click_ypos = pos.y;
// Setup our click counter and timer // 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 // If we have mouse clicks, then we check if the time elapsed
// is less than and our interval and if so, increase the count. // is less than and our interval and if so, increase the count.
if (self.mouse.left_click_count > 0) { if (self.mouse.left_click_count > 0) {
@ -4478,7 +4478,7 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const
// how fast identical notifications can be sent sequentially. // how fast identical notifications can be sent sequentially.
const hash_algorithm = std.hash.Wyhash; 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 // Set a limit of one desktop notification per second so that the OS
// doesn't kill us when we run out of resources. // doesn't kill us when we run out of resources.

View File

@ -128,8 +128,8 @@ pub const Artifact = enum {
pub fn detect() Artifact { pub fn detect() Artifact {
if (builtin.target.isWasm()) { if (builtin.target.isWasm()) {
assert(builtin.output_mode == .Obj); // assert(builtin.output_mode == .Obj);
assert(builtin.link_mode == .Static); // assert(builtin.link_mode == .Static);
return .wasm_module; return .wasm_module;
} }

View File

@ -1275,7 +1275,7 @@ pub fn LineIterator(comptime ReaderType: type) type {
} }
// Constructs a LineIterator (see docs for that). // 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 }; return .{ .r = reader };
} }

View File

@ -2752,19 +2752,21 @@ fn loadTheme(self: *Config, theme: Theme) !void {
pub fn finalize(self: *Config) !void { pub fn finalize(self: *Config) !void {
// We always load the theme first because it may set other fields // We always load the theme first because it may set other fields
// in our config. // in our config.
if (self.theme) |theme| { if (builtin.cpu.arch != .wasm32) {
const different = !std.mem.eql(u8, theme.light, theme.dark); if (self.theme) |theme| {
const different = !std.mem.eql(u8, theme.light, theme.dark);
// Warning: loadTheme will deinit our existing config and replace // Warning: loadTheme will deinit our existing config and replace
// it so all memory from self prior to this point will be freed. // it so all memory from self prior to this point will be freed.
try self.loadTheme(theme); try self.loadTheme(theme);
// If we have different light vs dark mode themes, disable // If we have different light vs dark mode themes, disable
// window-theme = auto since that breaks it. // window-theme = auto since that breaks it.
if (different) { if (different) {
// This setting doesn't make sense with different light/dark themes // This setting doesn't make sense with different light/dark themes
// because it'll force the theme based on the Ghostty theme. // because it'll force the theme based on the Ghostty theme.
if (self.@"window-theme" == .auto) self.@"window-theme" = .system; if (self.@"window-theme" == .auto) self.@"window-theme" = .system;
}
} }
} }

View File

@ -39,7 +39,7 @@ pub const Location = enum {
// error set since some platforms don't support some // error set since some platforms don't support some
// error types. // error types.
const Error = @TypeOf(err) || switch (builtin.os.tag) { const Error = @TypeOf(err) || switch (builtin.os.tag) {
.ios => error{BufferTooSmall}, .ios, .wasi => error{BufferTooSmall},
else => error{}, else => error{},
}; };

View File

@ -659,6 +659,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 { test init {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;

View File

@ -254,7 +254,7 @@ fn loadWebCanvas(
opts: font.face.Options, opts: font.face.Options,
) !Face { ) !Face {
const wc = self.wc.?; 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 /// 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 { 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}); log.warn("error loading deferred face err={}", .{err});
return; return;
}; };

View File

@ -322,6 +322,54 @@ const GlyphKey = struct {
const TestMode = enum { normal }; 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, .{}) catch unreachable;
}
};
fn testGrid(mode: TestMode, alloc: Allocator, lib: Library) !SharedGrid { fn testGrid(mode: TestMode, alloc: Allocator, lib: Library) !SharedGrid {
const testFont = font.embedded.regular; const testFont = font.embedded.regular;

View File

@ -325,6 +325,23 @@ pub const Face = struct {
return ctx; return ctx;
} }
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 /// An internal (web-canvas-only) format for rendered glyphs
/// since we do render passes in multiple different situations. /// since we do render passes in multiple different situations.
const RenderedGlyph = struct { const RenderedGlyph = struct {
@ -494,7 +511,7 @@ pub const Wasm = struct {
const alloc = wasm.alloc; const alloc = wasm.alloc;
export fn face_new(ptr: [*]const u8, len: usize, pts: u16, p: u16) ?*Face { 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 { fn face_new_(ptr: [*]const u8, len: usize, pts: f32, presentation: u16) !*Face {

View File

@ -33,6 +33,8 @@ comptime {
if (builtin.target.isWasm()) { if (builtin.target.isWasm()) {
_ = Atlas.Wasm; _ = Atlas.Wasm;
_ = DeferredFace.Wasm; _ = DeferredFace.Wasm;
_ = SharedGrid.Wasm;
_ = @import("Collection.zig").Wasm;
_ = face.web_canvas.Wasm; _ = face.web_canvas.Wasm;
_ = shape.web_canvas.Wasm; _ = shape.web_canvas.Wasm;
} }

View File

@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const ziglyph = @import("ziglyph"); const ziglyph = @import("ziglyph");
const font = @import("../main.zig"); const font = @import("../main.zig");
const terminal = @import("../../terminal/main.zig"); const terminal = @import("../../terminal/main.zig");
const SharedGrid = font.SharedGrid;
const log = std.log.scoped(.font_shaper); const log = std.log.scoped(.font_shaper);
@ -30,19 +31,19 @@ pub const Shaper = struct {
alloc: Allocator, alloc: Allocator,
/// The shared memory used for shaping results. /// 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. /// The shared memory used for storing information about a run.
run_buf: RunBuf, run_buf: RunBuf,
/// The cell_buf argument is the buffer to use for storing shaped results. /// 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. /// 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 // Note: we do not support opts.font_features
return Shaper{ return Shaper{
.alloc = alloc, .alloc = alloc,
.cell_buf = opts.cell_buf, .cell_buf = .{},
.run_buf = .{}, .run_buf = .{},
}; };
} }
@ -61,14 +62,16 @@ pub const Shaper = struct {
/// for a Shaper struct since they share state. /// for a Shaper struct since they share state.
pub fn runIterator( pub fn runIterator(
self: *Shaper, self: *Shaper,
group: *font.GroupCache, grid: *SharedGrid,
row: terminal.Screen.Row, screen: *const terminal.Screen,
row: terminal.Pin,
selection: ?terminal.Selection, selection: ?terminal.Selection,
cursor_x: ?usize, cursor_x: ?usize,
) font.shape.RunIterator { ) font.shape.RunIterator {
return .{ return .{
.hooks = .{ .shaper = self }, .hooks = .{ .shaper = self },
.group = group, .grid = grid,
.screen = screen,
.row = row, .row = row,
.selection = selection, .selection = selection,
.cursor_x = cursor_x, .cursor_x = cursor_x,
@ -90,21 +93,22 @@ pub const Shaper = struct {
const clusters = self.run_buf.items(.cluster); const clusters = self.run_buf.items(.cluster);
assert(codepoints.len == clusters.len); assert(codepoints.len == clusters.len);
self.cell_buf.clearRetainingCapacity();
switch (codepoints.len) { switch (codepoints.len) {
// Special cases: if we have no codepoints (is this possible?) // Special cases: if we have no codepoints (is this possible?)
// then our result is also an empty cell run. // 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 // If we have only 1 codepoint, then we assume that it is
// a single grapheme and just let it through. At this point, // a single grapheme and just let it through. At this point,
// we can't have any more information to do anything else. // we can't have any more information to do anything else.
1 => { 1 => {
self.cell_buf[0] = .{ try self.cell_buf.append(self.alloc, .{
.x = @intCast(clusters[0]), .x = @intCast(clusters[0]),
.glyph_index = codepoints[0], .glyph_index = codepoints[0],
}; });
return self.cell_buf[0..1]; return self.cell_buf.items[0..1];
}, },
else => {}, else => {},
@ -151,10 +155,10 @@ pub const Shaper = struct {
switch (len) { switch (len) {
// If we have only a single codepoint then just render it // If we have only a single codepoint then just render it
// as-is. // as-is.
1 => self.cell_buf[cur] = .{ 1 => try self.cell_buf.append(self.alloc, .{
.x = @intCast(clusters[start]), .x = @intCast(clusters[start]),
.glyph_index = codepoints[start], .glyph_index = codepoints[start],
}, }),
// We must have multiple codepoints (see assert above). In // We must have multiple codepoints (see assert above). In
// this case we UTF-8 encode the codepoints and send them // this case we UTF-8 encode the codepoints and send them
@ -190,13 +194,13 @@ pub const Shaper = struct {
}; };
defer self.alloc.free(cluster); 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); const index = try face.graphemeGlyphIndex(cluster);
self.cell_buf[cur] = .{ try self.cell_buf.append(self.alloc, .{
.x = @intCast(clusters[start]), .x = @intCast(clusters[start]),
.glyph_index = index, .glyph_index = index,
}; });
}, },
} }
@ -204,7 +208,7 @@ pub const Shaper = struct {
cur += 1; cur += 1;
} }
return self.cell_buf[0..cur]; return self.cell_buf.items[0..cur];
} }
/// The hooks for RunIterator. /// The hooks for RunIterator.
@ -238,15 +242,12 @@ pub const Wasm = struct {
const wasm = @import("../../os/wasm.zig"); const wasm = @import("../../os/wasm.zig");
const alloc = wasm.alloc; const alloc = wasm.alloc;
export fn shaper_new(cap: usize) ?*Shaper { export fn shaper_new() ?*Shaper {
return shaper_new_(cap) catch null; return shaper_new_() catch null;
} }
fn shaper_new_(cap: usize) !*Shaper { fn shaper_new_() !*Shaper {
const cell_buf = try alloc.alloc(font.shape.Cell, cap); var shaper = try Shaper.init(alloc, .{});
errdefer alloc.free(cell_buf);
var shaper = try Shaper.init(alloc, .{ .cell_buf = cell_buf });
errdefer shaper.deinit(); errdefer shaper.deinit();
const result = try alloc.create(Shaper); const result = try alloc.create(Shaper);
@ -257,7 +258,6 @@ pub const Wasm = struct {
export fn shaper_free(ptr: ?*Shaper) void { export fn shaper_free(ptr: ?*Shaper) void {
if (ptr) |v| { if (ptr) |v| {
alloc.free(v.cell_buf);
v.deinit(); v.deinit();
alloc.destroy(v); alloc.destroy(v);
} }
@ -266,38 +266,33 @@ pub const Wasm = struct {
/// Runs a test to verify shaping works properly. /// Runs a test to verify shaping works properly.
export fn shaper_test( export fn shaper_test(
self: *Shaper, self: *Shaper,
group: *font.GroupCache, grid: *SharedGrid,
str: [*]const u8, str: [*]const u8,
len: usize, len: usize,
) void { ) 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}); log.warn("error during shaper test err={}", .{err});
}; };
} }
fn shaper_test_(self: *Shaper, group: *font.GroupCache, str: []const u8) !void { fn shaper_test_(self: *Shaper, grid: *SharedGrid, str: []const u8) !void {
// Create a terminal and print all our characters into it. // Make a screen with some data
var term = try terminal.Terminal.init(alloc, self.cell_buf.len, 80); var term = try terminal.Terminal.init(alloc, .{ .cols = 6, .rows = 5 });
defer term.deinit(alloc); defer term.deinit(alloc);
try term.printString(str);
// Iterate over unicode codepoints and add to terminal // Get our run iterator
{
const view = try std.unicode.Utf8View.init(str);
var iter = view.iterator();
while (iter.nextCodepoint()) |c| {
try term.print(c);
}
}
// Iterate over the rows and print out all the runs we get. var row_it = term.screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
var rowIter = term.screen.rowIterator(.viewport);
var y: usize = 0; var y: usize = 0;
while (rowIter.next()) |row| { while (row_it.next()) |row| {
defer y += 1; defer y += 1;
var it = self.runIterator(grid, &term.screen, row, null, null);
var iter = self.runIterator(group, row, null, null); while (try it.next(alloc)) |run| {
while (try iter.next(alloc)) |run| {
const cells = try self.shape(run); const cells = try self.shape(run);
for (cells) |cell| {
_ = try grid.renderGlyph(wasm.alloc, run.font_index, cell.glyph_index, .{});
}
log.info("y={} run={d} shape={any} idx={}", .{ log.info("y={} run={d} shape={any} idx={}", .{
y, y,
run.cells, run.cells,

View File

@ -11,16 +11,17 @@ comptime {
_ = @import("App.zig").Wasm; _ = @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 // 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 // ReleaseSmall mode where we're optimizing for space, we elevate the
// log level. // log level.
pub const log_level: std.log.Level = switch (builtin.mode) { // .log_level = switch (builtin.mode) {
.Debug => .debug, // .Debug => .debug,
.ReleaseSmall => .warn, // .ReleaseSmall => .warn,
else => .info, // else => .info,
}; // },
.log_level = .info,
// Set our log function // Set our log function
pub const logFn = @import("os/wasm/log.zig").log; .logFn = @import("os/wasm/log.zig").log,
}; };

View File

@ -56,6 +56,8 @@ pub fn launchedFromDesktop() bool {
// iPhone/iPad is always launched from the "desktop" // iPhone/iPad is always launched from the "desktop"
.ios => true, .ios => true,
.freestanding => false,
else => @compileError("unsupported platform"), else => @compileError("unsupported platform"),
}; };
} }

View File

@ -20,6 +20,8 @@ pub inline fn home(buf: []u8) !?[]u8 {
// iOS doesn't have a user-writable home directory // iOS doesn't have a user-writable home directory
.ios => null, .ios => null,
.wasi => null,
else => @compileError("unimplemented"), else => @compileError("unimplemented"),
}; };
} }

View File

@ -13,6 +13,8 @@ const mouse = @import("mouse.zig");
const openpkg = @import("open.zig"); const openpkg = @import("open.zig");
const pipepkg = @import("pipe.zig"); const pipepkg = @import("pipe.zig");
const resourcesdir = @import("resourcesdir.zig"); const resourcesdir = @import("resourcesdir.zig");
const builtin = @import("builtin");
const std = @import("std");
// Namespaces // Namespaces
pub const args = @import("args.zig"); pub const args = @import("args.zig");
@ -42,3 +44,8 @@ pub const clickInterval = mouse.clickInterval;
pub const open = openpkg.open; pub const open = openpkg.open;
pub const pipe = pipepkg.pipe; pub const pipe = pipepkg.pipe;
pub const resourcesDir = resourcesdir.resourcesDir; pub const resourcesDir = resourcesdir.resourcesDir;
pub const Instant = if (builtin.cpu.arch != .wasm32) std.time.Instant else struct {
fn now() !@This() {
return .{};
}
};

View File

@ -346,7 +346,7 @@ pub const LoadingImage = struct {
} }
// Set our time // 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}); log.warn("failed to get time: {}", .{err});
return error.InternalError; return error.InternalError;
}; };
@ -453,7 +453,7 @@ pub const Image = struct {
format: command.Transmission.Format = .rgb, format: command.Transmission.Format = .rgb,
compression: command.Transmission.Compression = .none, compression: command.Transmission.Compression = .none,
data: []const u8 = "", data: []const u8 = "",
transmit_time: std.time.Instant = undefined, transmit_time: internal_os.Instant = undefined,
pub const Error = error{ pub const Error = error{
InternalError, InternalError,

View File

@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
const terminal = @import("../main.zig"); const terminal = @import("../main.zig");
const internal_os = @import("../../os/main.zig");
const point = @import("../point.zig"); const point = @import("../point.zig");
const size = @import("../size.zig"); const size = @import("../size.zig");
const command = @import("graphics_command.zig"); const command = @import("graphics_command.zig");
@ -495,7 +496,7 @@ pub const ImageStorage = struct {
// bit is fine compared to the megabytes we're looking to save. // bit is fine compared to the megabytes we're looking to save.
const Candidate = struct { const Candidate = struct {
id: u32, id: u32,
time: std.time.Instant, time: internal_os.Instant,
used: bool, used: bool,
}; };

View File

@ -320,7 +320,7 @@ pub const Page = struct {
/// when runtime safety is enabled. This is a no-op when runtime /// when runtime safety is enabled. This is a no-op when runtime
/// safety is disabled. This uses the libc allocator. /// safety is disabled. This uses the libc allocator.
pub fn assertIntegrity(self: *const Page) void { 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| { self.verifyIntegrity(std.heap.c_allocator) catch |err| {
log.err("page integrity violation, crashing. err={}", .{err}); log.err("page integrity violation, crashing. err={}", .{err});
@panic("page integrity violation"); @panic("page integrity violation");

View File

@ -102,7 +102,7 @@ pub fn threadEnter(
}; };
// Track our process start time for abnormal exits // 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. // Create our pipe that we'll use to kill our read thread.
// pipe[0] is the read end, pipe[1] is the write end. // pipe[0] is the read end, pipe[1] is the write end.
@ -345,7 +345,7 @@ fn processExit(
// Determine how long the process was running for. // Determine how long the process was running for.
const runtime_ms: ?u64 = runtime: { const runtime_ms: ?u64 = runtime: {
const process_end = std.time.Instant.now() catch break :runtime null; const process_end = internal_os.now() catch break :runtime null;
const runtime_ns = process_end.since(execdata.start); const runtime_ns = process_end.since(execdata.start);
const runtime_ms = runtime_ns / std.time.ns_per_ms; const runtime_ms = runtime_ns / std.time.ns_per_ms;
break :runtime runtime_ms; break :runtime runtime_ms;
@ -603,7 +603,7 @@ pub const ThreadData = struct {
const WRITE_REQ_PREALLOC = std.math.pow(usize, 2, 5); const WRITE_REQ_PREALLOC = std.math.pow(usize, 2, 5);
/// Process start time and boolean of whether its already exited. /// Process start time and boolean of whether its already exited.
start: std.time.Instant, start: internal_os.Instant,
exited: bool = false, exited: bool = false,
/// The number of milliseconds below which we consider a process /// The number of milliseconds below which we consider a process

View File

@ -68,7 +68,7 @@ terminal_stream: terminal.Stream(StreamHandler),
/// Last time the cursor was reset. This is used to prevent message /// Last time the cursor was reset. This is used to prevent message
/// flooding with cursor resets. /// 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 /// The configuration for this IO that is derived from the main
/// configuration. This must be exported so that we don't need to /// configuration. This must be exported so that we don't need to
@ -552,7 +552,7 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
// non-blink state so it is rendered if visible. If we're under // 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 // HEAVY read load, we don't want to send a ton of these so we
// use a timer under the covers // 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 (self.last_cursor_reset) |last| {
if (now.since(last) <= (500 * std.time.ns_per_ms)) { if (now.since(last) <= (500 * std.time.ns_per_ms)) {
break :cursor_reset; break :cursor_reset;