Merge 1ece6f06b397c8853038b6fc3902529faeef04da into 4c2d4620007c4c774b7bc6fd282827c04c08686c

This commit is contained in:
Gabriel Dinner-David
2024-11-25 09:41:27 +00:00
committed by GitHub
54 changed files with 4152 additions and 502 deletions

223
build.zig
View File

@ -716,7 +716,8 @@ pub fn build(b: *std.Build) !void {
// Build our Wasm target. // Build our Wasm target.
const wasm_crosstarget: std.Target.Query = .{ const wasm_crosstarget: std.Target.Query = .{
.cpu_arch = .wasm32, .cpu_arch = .wasm32,
.os_tag = .freestanding, .os_tag = .wasi,
.abi = .musl,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp }, .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
.cpu_features_add = std.Target.wasm.featureSet(&.{ .cpu_features_add = std.Target.wasm.featureSet(&.{
// We use this to explicitly request shared memory. // We use this to explicitly request shared memory.
@ -748,7 +749,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,9 +758,14 @@ 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.import_symbols = 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;
wasm.root_module.single_threaded = false;
// Stack protector adds extern requirements that we don't satisfy. // Stack protector adds extern requirements that we don't satisfy.
wasm.root_module.stack_protector = false; wasm.root_module.stack_protector = false;
@ -769,10 +775,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 +1080,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 +1165,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 +1185,83 @@ 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.root_module.addImport("opengl", b.dependency(
"opengl",
.{},
).module("opengl"));
step.root_module.addImport("xev", b.dependency("libxev", .{
.target = target,
.optimize = optimize,
}).module("xev"));
step.root_module.addImport("wuffs", b.dependency("wuffs", .{
.target = target,
.optimize = optimize,
}).module("wuffs"));
const oniguruma_dep = b.dependency("oniguruma", .{
.target = target,
.optimize = optimize,
});
step.root_module.addImport("oniguruma", oniguruma_dep.module("oniguruma"));
step.linkLibrary(oniguruma_dep.artifact("oniguruma"));
try static_libs.append(oniguruma_dep.artifact("oniguruma").getEmittedBin());
const simdutf_dep = b.dependency("simdutf", .{
.target = target,
.optimize = optimize,
});
const utfcpp_dep = b.dependency("utfcpp", .{
.target = target,
.optimize = optimize,
});
step.linkLibrary(utfcpp_dep.artifact("utfcpp"));
try static_libs.append(utfcpp_dep.artifact("utfcpp").getEmittedBin());
step.linkLibrary(simdutf_dep.artifact("simdutf"));
try static_libs.append(simdutf_dep.artifact("simdutf").getEmittedBin());
step.linkLibC();
step.linkLibCpp();
step.addIncludePath(b.path("src"));
{
// From hwy/detect_targets.h
const HWY_AVX3_SPR: c_int = 1 << 4;
const HWY_AVX3_ZEN4: c_int = 1 << 6;
const HWY_AVX3_DL: c_int = 1 << 7;
const HWY_AVX3: c_int = 1 << 8;
// Zig 0.13 bug: https://github.com/ziglang/zig/issues/20414
// To workaround this we just disable AVX512 support completely.
// The performance difference between AVX2 and AVX512 is not
// significant for our use case and AVX512 is very rare on consumer
// hardware anyways.
const HWY_DISABLED_TARGETS: c_int = HWY_AVX3_SPR | HWY_AVX3_ZEN4 | HWY_AVX3_DL | HWY_AVX3;
step.addCSourceFiles(.{
.files = &.{
"src/simd/base64.cpp",
"src/simd/codepoint_width.cpp",
"src/simd/index_of.cpp",
"src/simd/vt.cpp",
},
.flags = if (step.rootModuleTarget().cpu.arch == .x86_64) &.{
b.fmt("-DHWY_DISABLED_TARGETS={}", .{HWY_DISABLED_TARGETS}),
} else &.{"-DSIMDUTF_NO_THREADS"},
});
}
const highway_dep = b.dependency("highway", .{
.target = target,
.optimize = optimize,
});
step.linkLibrary(highway_dep.artifact("highway"));
try static_libs.append(highway_dep.artifact("highway").getEmittedBin());
try addUnicodeTables(b, step);
return static_libs; return static_libs;
} }

View File

@ -4,10 +4,7 @@
.paths = .{""}, .paths = .{""},
.dependencies = .{ .dependencies = .{
// Zig libs // Zig libs
.libxev = .{ .libxev = .{ .path = "../xev" },
.url = "https://github.com/mitchellh/libxev/archive/b8d1d93e5c899b27abbaa7df23b496c3e6a178c7.tar.gz",
.hash = "1220612bc023c21d75234882ec9a8c6a1cbd9d642da3dfb899297f14bb5bd7b6cd78",
},
.mach_glfw = .{ .mach_glfw = .{
.url = "https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz", .url = "https://github.com/mitchellh/mach-glfw/archive/37c2995f31abcf7e8378fba68ddcf4a3faa02de0.tar.gz",
.hash = "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62", .hash = "12206ed982e709e565d536ce930701a8c07edfd2cfdce428683f3f2a601d37696a62",

10
example/.proxyrc.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = function (app) {
app.use(
(req, res, next) => {
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
next();
}
);
};

View File

@ -1,65 +1,47 @@
import { ZigJS } from "zig-js"; import { importObject, setFiles, setStdin, setWasmModule, zjs } from "./imports";
import { old } from "./old";
const zjs = new ZigJS();
const importObject = {
module: {},
env: {
memory: new WebAssembly.Memory({
initial: 25,
maximum: 65536,
shared: true,
}),
log: (ptr: number, len: number) => {
const arr = new Uint8ClampedArray(zjs.memory.buffer, ptr, len);
const data = arr.slice();
const str = new TextDecoder("utf-8").decode(data);
console.log(str);
},
},
...zjs.importObject(),
};
const url = new URL("ghostty-wasm.wasm", import.meta.url); const url = new URL("ghostty-wasm.wasm", import.meta.url);
fetch(url.href) fetch(url.href)
.then((response) => response.arrayBuffer()) .then((response) => response.arrayBuffer())
.then((bytes) => WebAssembly.instantiate(bytes, importObject)) .then((bytes) => WebAssembly.instantiate(bytes, importObject))
.then((results) => { .then(async (results) => {
const memory = importObject.env.memory; const memory = importObject.env.memory;
const { const {
malloc, atlas_clear,
free, atlas_debug_canvas,
config_new, atlas_free,
atlas_grow,
atlas_new,
atlas_reserve,
atlas_set,
config_finalize,
config_free, config_free,
config_load_string, config_load_string,
config_finalize, config_new,
face_new,
face_free,
face_render_glyph,
face_debug_canvas,
deferred_face_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,
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,
shaper_free, shaper_free,
shaper_new,
shaper_test, 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,
run,
draw,
} = 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;
console.log(zjs); console.log(zjs);
@ -76,114 +58,41 @@ fetch(url.href)
}; };
// Create our config // Create our config
const config = config_new(); const config_str = makeStr("font-family = monospace\nfont-size = 32\n");
const config_str = makeStr("font-family = monospace"); // old(results);
config_load_string(config, config_str.ptr, config_str.len); setWasmModule(results.module);
config_finalize(config); const stdin = new SharedArrayBuffer(1024);
free(config_str.ptr); const files = {
nextFd: new SharedArrayBuffer(4),
// Create our atlas polling: new SharedArrayBuffer(4),
// const atlas = atlas_new(512, 0 /* grayscale */); has: new SharedArrayBuffer(1024),
// Create some memory for our string
const font_name = makeStr("monospace");
// Initialize our deferred face
// const df = deferred_face_new(font_ptr, font.byteLength, 0 /* text */);
//deferred_face_load(df, 72 /* size */);
//const face = deferred_face_face(df);
// Initialize our font face
//const face = face_new(font_ptr, font.byteLength, 72 /* size in px */);
//free(font_ptr);
// Create our group
const group = group_new(32 /* size */);
group_add_face(
group,
0 /* regular */,
deferred_face_new(font_name.ptr, font_name.len, 0 /* text */),
);
group_add_face(
group,
0 /* regular */,
deferred_face_new(font_name.ptr, font_name.len, 1 /* emoji */),
);
// Initialize our sprite font, without this we just use the browser.
group_init_sprite_face(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);
// }
//
// 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);
// }
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);
} }
for (let i = 0x2580; i <= 0x259f; i++) { new Int32Array(files.nextFd)[0] = 4;
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); setFiles(files);
group_cache_render_glyph(group_cache, font_idx, i, 0); setStdin(stdin);
run(config_str.ptr, config_str.len);
await new Promise((resolve) => setTimeout(resolve, 500))
const io = new Uint8ClampedArray(stdin, 4);
const text = new TextEncoder().encode("hello world\r\n");
io.set(text);
const n = new Int32Array(stdin);
Atomics.store(n, 0, text.length)
Atomics.notify(n, 0);
function drawing() {
requestAnimationFrame(() => {
draw();
drawing();
});
} }
for (let i = 0x2800; i <= 0x28ff; i++) { drawing()
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); setInterval(() => {
group_cache_render_glyph(group_cache, font_idx, i, 0); // const text = new TextEncoder().encode("🐏\n\r👍🏽\n\rM_ghostty\033[2;2H\033[48;2;240;40;40m\033[38;2;23;255;80mhello");
} const text = new TextEncoder().encode("🐏\r\n👍🏽\r\nM_ghostty\033[48;2;240;40;40m\033[38;2;23;255;80mhello\r\n");
for (let i = 0x1fb00; i <= 0x1fb3b; i++) { const n = new Int32Array(stdin);
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); const place = Atomics.add(n, 0, text.length)
group_cache_render_glyph(group_cache, font_idx, i, 0); const io = new Uint8ClampedArray(stdin, 4 + place);
} io.set(text);
for (let i = 0x1fb3c; i <= 0x1fb6b; i++) { Atomics.notify(n, 0);
const font_idx = group_cache_index_for_codepoint(group_cache, i, 0, -1); }, 10000)
group_cache_render_glyph(group_cache, font_idx, i, 0); })
}
//face_render_glyph(face, atlas, "橋".codePointAt(0));
//face_render_glyph(face, atlas, "p".codePointAt(0));
// Debug our canvas
//face_debug_canvas(face);
// Let's try shaping
const shaper = shaper_new(120);
//const input = makeStr("hello🐏");
const input = makeStr("hello🐏👍🏽");
shaper_test(shaper, group_cache, input.ptr, input.len);
const cp = 1114112;
const font_idx = group_cache_index_for_codepoint(
group_cache,
cp,
0,
-1 /* best choice */,
);
group_cache_render_glyph(group_cache, font_idx, cp, -1);
// Debug our atlas canvas
{
const atlas = group_cache_atlas_grayscale(group_cache);
const id = atlas_debug_canvas(atlas);
document.getElementById("atlas-canvas").append(zjs.deleteValue(id));
}
{
const atlas = group_cache_atlas_color(group_cache);
const id = atlas_debug_canvas(atlas);
document.getElementById("atlas-color-canvas").append(zjs.deleteValue(id));
}
//face_free(face);
});

1076
example/imports.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,8 @@
</head> </head>
<body> <body>
<p>Open your console, we are just debugging here.</p> <p>Open your console, we are just debugging here.</p>
<div><div style="display: inline-block; border: 1px solid green;"><canvas id="main-canvas" width="500px" height="500px"></canvas></div></div>
<div><div style="display: inline-block; border: 1px solid green;"><canvas id="shaper-canvas" width="500px" height="500px"></canvas></div></div>
<p>The current <b>grayscale</b> font atlas is rendered below.</p> <p>The current <b>grayscale</b> font atlas is rendered below.</p>
<div><div id="atlas-canvas" style="display: inline-block; border: 1px solid green;"></div></div> <div><div id="atlas-canvas" style="display: inline-block; border: 1px solid green;"></div></div>
<p>The current <b>color</b> font atlas is rendered below.</p> <p>The current <b>color</b> font atlas is rendered below.</p>

158
example/old.ts Normal file
View File

@ -0,0 +1,158 @@
import { zjs } from "./imports";
export function old(results) {
const {
atlas_clear,
atlas_debug_canvas,
atlas_free,
atlas_grow,
atlas_new,
atlas_reserve,
atlas_set,
config_finalize,
config_free,
config_load_string,
config_new,
deferred_face_free,
deferred_face_load,
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,
run,
} = results.instance.exports;
// Helpers
const makeStr = (str) => {
const utf8 = new TextEncoder().encode(str);
const ptr = malloc(utf8.byteLength);
new Uint8Array(zjs.memory.buffer, ptr).set(utf8);
return { ptr: ptr, len: utf8.byteLength };
};
// Create our config
const config_str = makeStr("font-family = monospace");
const config = config_new();
config_load_string(config, config_str);
config_finalize(config);
free(config_str.ptr);
// Create our atlas
// const atlas = atlas_new(512, 0 /* grayscale */);
// Create some memory for our string
const font_name = makeStr("monospace");
// Initialize our deferred face
// const df = deferred_face_new(font_ptr, font.byteLength, 0 /* text */);
//deferred_face_load(df, 72 /* size */);
//const face = deferred_face_face(df);
// Initialize our font face
//const face = face_new(font_ptr, font.byteLength, 72 /* size in px */);
//free(font_ptr);
// Create our group
const collection = collection_new(32);
collection_add_deferred_face(
collection,
0 /* regular */,
deferred_face_new(font_name.ptr, font_name.len, 0 /* text */),
);
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);
// // Create our group cache
// const group_cache = group_cache_new(group);
// Render a glyph
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 = 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 = 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 = 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 = 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 = 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 = 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));
//face_render_glyph(face, atlas, "p".codePointAt(0));
// Debug our canvas
//face_debug_canvas(face);
// Let's try shaping
const shaper = shaper_new(120);
//const input = makeStr("hello🐏");
const input = makeStr("M_yhelloaaaaaaaaa\n🐏\n👍🏽\nM_ghostty");
shaper_test(shaper, grid, input.ptr, input.len);
const cp = 1114112;
const font_idx = shared_grid_index_for_codepoint(
grid,
cp,
0,
-1 /* best choice */,
);
shared_grid_render_glyph(grid, font_idx, cp, -1);
// Debug our atlas canvas
{
const atlas = shared_grid_atlas_grayscale(grid);
const id = atlas_debug_canvas(atlas);
document.getElementById("atlas-canvas").append(zjs.deleteValue(id));
}
{
const atlas = shared_grid_atlas_color(grid);
const id = atlas_debug_canvas(atlas);
document.getElementById("atlas-color-canvas").append(zjs.deleteValue(id));
}
//face_free(face);
}

1272
example/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,10 +13,20 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@parcel/transformer-inline-string": "^2.8.0", "@parcel/transformer-inline-string": "^2.8.0",
"buffer": "^5.7.1",
"crypto-browserify": "^3.12.1",
"events": "^3.3.0",
"os-browserify": "^0.3.0",
"parcel": "^2.8.0", "parcel": "^2.8.0",
"typescript": "^4.9.3" "path-browserify": "^1.0.1",
"process": "^0.11.10",
"stream-browserify": "^3.0.0",
"string_decoder": "^1.3.0",
"typescript": "^4.9.3",
"vm-browserify": "^1.1.2"
}, },
"dependencies": { "dependencies": {
"zig-js": "file:../vendor/zig-js/js" "glslog": "^0.0.10",
"zig-js": "https://gitpkg.vercel.app/mitchellh/zig-js/js?main"
} }
} }

431
example/wasi.ts Normal file
View File

@ -0,0 +1,431 @@
/*
This project is based from the Node implementation made by Gus Caplan
https://github.com/devsnek/node-wasi
However, JavaScript WASI is focused on:
* Bringing WASI to the Browsers
* Make easy to plug different filesystems
* Provide a type-safe api using Typescript
Copyright 2019 Gus Caplan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
export const WASI_ESUCCESS = 0;
export const WASI_E2BIG = 1;
export const WASI_EACCES = 2;
export const WASI_EADDRINUSE = 3;
export const WASI_EADDRNOTAVAIL = 4;
export const WASI_EAFNOSUPPORT = 5;
export const WASI_EAGAIN = 6;
export const WASI_EALREADY = 7;
export const WASI_EBADF = 8;
export const WASI_EBADMSG = 9;
export const WASI_EBUSY = 10;
export const WASI_ECANCELED = 11;
export const WASI_ECHILD = 12;
export const WASI_ECONNABORTED = 13;
export const WASI_ECONNREFUSED = 14;
export const WASI_ECONNRESET = 15;
export const WASI_EDEADLK = 16;
export const WASI_EDESTADDRREQ = 17;
export const WASI_EDOM = 18;
export const WASI_EDQUOT = 19;
export const WASI_EEXIST = 20;
export const WASI_EFAULT = 21;
export const WASI_EFBIG = 22;
export const WASI_EHOSTUNREACH = 23;
export const WASI_EIDRM = 24;
export const WASI_EILSEQ = 25;
export const WASI_EINPROGRESS = 26;
export const WASI_EINTR = 27;
export const WASI_EINVAL = 28;
export const WASI_EIO = 29;
export const WASI_EISCONN = 30;
export const WASI_EISDIR = 31;
export const WASI_ELOOP = 32;
export const WASI_EMFILE = 33;
export const WASI_EMLINK = 34;
export const WASI_EMSGSIZE = 35;
export const WASI_EMULTIHOP = 36;
export const WASI_ENAMETOOLONG = 37;
export const WASI_ENETDOWN = 38;
export const WASI_ENETRESET = 39;
export const WASI_ENETUNREACH = 40;
export const WASI_ENFILE = 41;
export const WASI_ENOBUFS = 42;
export const WASI_ENODEV = 43;
export const WASI_ENOENT = 44;
export const WASI_ENOEXEC = 45;
export const WASI_ENOLCK = 46;
export const WASI_ENOLINK = 47;
export const WASI_ENOMEM = 48;
export const WASI_ENOMSG = 49;
export const WASI_ENOPROTOOPT = 50;
export const WASI_ENOSPC = 51;
export const WASI_ENOSYS = 52;
export const WASI_ENOTCONN = 53;
export const WASI_ENOTDIR = 54;
export const WASI_ENOTEMPTY = 55;
export const WASI_ENOTRECOVERABLE = 56;
export const WASI_ENOTSOCK = 57;
export const WASI_ENOTSUP = 58;
export const WASI_ENOTTY = 59;
export const WASI_ENXIO = 60;
export const WASI_EOVERFLOW = 61;
export const WASI_EOWNERDEAD = 62;
export const WASI_EPERM = 63;
export const WASI_EPIPE = 64;
export const WASI_EPROTO = 65;
export const WASI_EPROTONOSUPPORT = 66;
export const WASI_EPROTOTYPE = 67;
export const WASI_ERANGE = 68;
export const WASI_EROFS = 69;
export const WASI_ESPIPE = 70;
export const WASI_ESRCH = 71;
export const WASI_ESTALE = 72;
export const WASI_ETIMEDOUT = 73;
export const WASI_ETXTBSY = 74;
export const WASI_EXDEV = 75;
export const WASI_ENOTCAPABLE = 76;
export const WASI_SIGABRT = 0;
export const WASI_SIGALRM = 1;
export const WASI_SIGBUS = 2;
export const WASI_SIGCHLD = 3;
export const WASI_SIGCONT = 4;
export const WASI_SIGFPE = 5;
export const WASI_SIGHUP = 6;
export const WASI_SIGILL = 7;
export const WASI_SIGINT = 8;
export const WASI_SIGKILL = 9;
export const WASI_SIGPIPE = 10;
export const WASI_SIGQUIT = 11;
export const WASI_SIGSEGV = 12;
export const WASI_SIGSTOP = 13;
export const WASI_SIGTERM = 14;
export const WASI_SIGTRAP = 15;
export const WASI_SIGTSTP = 16;
export const WASI_SIGTTIN = 17;
export const WASI_SIGTTOU = 18;
export const WASI_SIGURG = 19;
export const WASI_SIGUSR1 = 20;
export const WASI_SIGUSR2 = 21;
export const WASI_SIGVTALRM = 22;
export const WASI_SIGXCPU = 23;
export const WASI_SIGXFSZ = 24;
export const WASI_FILETYPE_UNKNOWN = 0;
export const WASI_FILETYPE_BLOCK_DEVICE = 1;
export const WASI_FILETYPE_CHARACTER_DEVICE = 2;
export const WASI_FILETYPE_DIRECTORY = 3;
export const WASI_FILETYPE_REGULAR_FILE = 4;
export const WASI_FILETYPE_SOCKET_DGRAM = 5;
export const WASI_FILETYPE_SOCKET_STREAM = 6;
export const WASI_FILETYPE_SYMBOLIC_LINK = 7;
export type WASI_FILETYPE =
| typeof WASI_FILETYPE_UNKNOWN
| typeof WASI_FILETYPE_BLOCK_DEVICE
| typeof WASI_FILETYPE_CHARACTER_DEVICE
| typeof WASI_FILETYPE_DIRECTORY
| typeof WASI_FILETYPE_REGULAR_FILE
| typeof WASI_FILETYPE_SOCKET_DGRAM
| typeof WASI_FILETYPE_SOCKET_STREAM
| typeof WASI_FILETYPE_SYMBOLIC_LINK;
export const WASI_FDFLAG_APPEND = 0x0001;
export const WASI_FDFLAG_DSYNC = 0x0002;
export const WASI_FDFLAG_NONBLOCK = 0x0004;
export const WASI_FDFLAG_RSYNC = 0x0008;
export const WASI_FDFLAG_SYNC = 0x0010;
export const WASI_RIGHT_FD_DATASYNC = BigInt(0x0000000000000001);
export const WASI_RIGHT_FD_READ = BigInt(0x0000000000000002);
export const WASI_RIGHT_FD_SEEK = BigInt(0x0000000000000004);
export const WASI_RIGHT_FD_FDSTAT_SET_FLAGS = BigInt(0x0000000000000008);
export const WASI_RIGHT_FD_SYNC = BigInt(0x0000000000000010);
export const WASI_RIGHT_FD_TELL = BigInt(0x0000000000000020);
export const WASI_RIGHT_FD_WRITE = BigInt(0x0000000000000040);
export const WASI_RIGHT_FD_ADVISE = BigInt(0x0000000000000080);
export const WASI_RIGHT_FD_ALLOCATE = BigInt(0x0000000000000100);
export const WASI_RIGHT_PATH_CREATE_DIRECTORY = BigInt(0x0000000000000200);
export const WASI_RIGHT_PATH_CREATE_FILE = BigInt(0x0000000000000400);
export const WASI_RIGHT_PATH_LINK_SOURCE = BigInt(0x0000000000000800);
export const WASI_RIGHT_PATH_LINK_TARGET = BigInt(0x0000000000001000);
export const WASI_RIGHT_PATH_OPEN = BigInt(0x0000000000002000);
export const WASI_RIGHT_FD_READDIR = BigInt(0x0000000000004000);
export const WASI_RIGHT_PATH_READLINK = BigInt(0x0000000000008000);
export const WASI_RIGHT_PATH_RENAME_SOURCE = BigInt(0x0000000000010000);
export const WASI_RIGHT_PATH_RENAME_TARGET = BigInt(0x0000000000020000);
export const WASI_RIGHT_PATH_FILESTAT_GET = BigInt(0x0000000000040000);
export const WASI_RIGHT_PATH_FILESTAT_SET_SIZE = BigInt(0x0000000000080000);
export const WASI_RIGHT_PATH_FILESTAT_SET_TIMES = BigInt(0x0000000000100000);
export const WASI_RIGHT_FD_FILESTAT_GET = BigInt(0x0000000000200000);
export const WASI_RIGHT_FD_FILESTAT_SET_SIZE = BigInt(0x0000000000400000);
export const WASI_RIGHT_FD_FILESTAT_SET_TIMES = BigInt(0x0000000000800000);
export const WASI_RIGHT_PATH_SYMLINK = BigInt(0x0000000001000000);
export const WASI_RIGHT_PATH_REMOVE_DIRECTORY = BigInt(0x0000000002000000);
export const WASI_RIGHT_PATH_UNLINK_FILE = BigInt(0x0000000004000000);
export const WASI_RIGHT_POLL_FD_READWRITE = BigInt(0x0000000008000000);
export const WASI_RIGHT_SOCK_SHUTDOWN = BigInt(0x0000000010000000);
export const RIGHTS_ALL =
WASI_RIGHT_FD_DATASYNC |
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_SEEK |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_SYNC |
WASI_RIGHT_FD_TELL |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_FD_ALLOCATE |
WASI_RIGHT_PATH_CREATE_DIRECTORY |
WASI_RIGHT_PATH_CREATE_FILE |
WASI_RIGHT_PATH_LINK_SOURCE |
WASI_RIGHT_PATH_LINK_TARGET |
WASI_RIGHT_PATH_OPEN |
WASI_RIGHT_FD_READDIR |
WASI_RIGHT_PATH_READLINK |
WASI_RIGHT_PATH_RENAME_SOURCE |
WASI_RIGHT_PATH_RENAME_TARGET |
WASI_RIGHT_PATH_FILESTAT_GET |
WASI_RIGHT_PATH_FILESTAT_SET_SIZE |
WASI_RIGHT_PATH_FILESTAT_SET_TIMES |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_FD_FILESTAT_SET_TIMES |
WASI_RIGHT_FD_FILESTAT_SET_SIZE |
WASI_RIGHT_PATH_SYMLINK |
WASI_RIGHT_PATH_UNLINK_FILE |
WASI_RIGHT_PATH_REMOVE_DIRECTORY |
WASI_RIGHT_POLL_FD_READWRITE |
WASI_RIGHT_SOCK_SHUTDOWN;
export const RIGHTS_BLOCK_DEVICE_BASE = RIGHTS_ALL;
export const RIGHTS_BLOCK_DEVICE_INHERITING = RIGHTS_ALL;
export const RIGHTS_CHARACTER_DEVICE_BASE = RIGHTS_ALL;
export const RIGHTS_CHARACTER_DEVICE_INHERITING = RIGHTS_ALL;
export const RIGHTS_REGULAR_FILE_BASE =
WASI_RIGHT_FD_DATASYNC |
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_SEEK |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_SYNC |
WASI_RIGHT_FD_TELL |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_FD_ALLOCATE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_FD_FILESTAT_SET_SIZE |
WASI_RIGHT_FD_FILESTAT_SET_TIMES |
WASI_RIGHT_POLL_FD_READWRITE;
export const RIGHTS_REGULAR_FILE_INHERITING = BigInt(0);
export const RIGHTS_DIRECTORY_BASE =
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_SYNC |
WASI_RIGHT_FD_ADVISE |
WASI_RIGHT_PATH_CREATE_DIRECTORY |
WASI_RIGHT_PATH_CREATE_FILE |
WASI_RIGHT_PATH_LINK_SOURCE |
WASI_RIGHT_PATH_LINK_TARGET |
WASI_RIGHT_PATH_OPEN |
WASI_RIGHT_FD_READDIR |
WASI_RIGHT_PATH_READLINK |
WASI_RIGHT_PATH_RENAME_SOURCE |
WASI_RIGHT_PATH_RENAME_TARGET |
WASI_RIGHT_PATH_FILESTAT_GET |
WASI_RIGHT_PATH_FILESTAT_SET_SIZE |
WASI_RIGHT_PATH_FILESTAT_SET_TIMES |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_FD_FILESTAT_SET_TIMES |
WASI_RIGHT_PATH_SYMLINK |
WASI_RIGHT_PATH_UNLINK_FILE |
WASI_RIGHT_PATH_REMOVE_DIRECTORY |
WASI_RIGHT_POLL_FD_READWRITE;
export const RIGHTS_DIRECTORY_INHERITING =
RIGHTS_DIRECTORY_BASE | RIGHTS_REGULAR_FILE_BASE;
export const RIGHTS_SOCKET_BASE =
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_POLL_FD_READWRITE |
WASI_RIGHT_SOCK_SHUTDOWN;
export const RIGHTS_SOCKET_INHERITING = RIGHTS_ALL;
export const RIGHTS_TTY_BASE =
WASI_RIGHT_FD_READ |
WASI_RIGHT_FD_FDSTAT_SET_FLAGS |
WASI_RIGHT_FD_WRITE |
WASI_RIGHT_FD_FILESTAT_GET |
WASI_RIGHT_POLL_FD_READWRITE;
export const RIGHTS_TTY_INHERITING = BigInt(0);
export const WASI_CLOCK_REALTIME = 0;
export const WASI_CLOCK_MONOTONIC = 1;
export const WASI_CLOCK_PROCESS_CPUTIME_ID = 2;
export const WASI_CLOCK_THREAD_CPUTIME_ID = 3;
export const WASI_EVENTTYPE_CLOCK = 0;
export const WASI_EVENTTYPE_FD_READ = 1;
export const WASI_EVENTTYPE_FD_WRITE = 2;
export const WASI_FILESTAT_SET_ATIM = 1 << 0;
export const WASI_FILESTAT_SET_ATIM_NOW = 1 << 1;
export const WASI_FILESTAT_SET_MTIM = 1 << 2;
export const WASI_FILESTAT_SET_MTIM_NOW = 1 << 3;
export const WASI_O_CREAT = 1 << 0;
export const WASI_O_DIRECTORY = 1 << 1;
export const WASI_O_EXCL = 1 << 2;
export const WASI_O_TRUNC = 1 << 3;
export const WASI_PREOPENTYPE_DIR = 0;
export const WASI_DIRCOOKIE_START = 0;
export const WASI_STDIN_FILENO = 0;
export const WASI_STDOUT_FILENO = 1;
export const WASI_STDERR_FILENO = 2;
export const WASI_WHENCE_SET = 0;
export const WASI_WHENCE_CUR = 1;
export const WASI_WHENCE_END = 2;
// http://man7.org/linux/man-pages/man3/errno.3.html
export const ERROR_MAP: { [key: string]: number } = {
E2BIG: WASI_E2BIG,
EACCES: WASI_EACCES,
EADDRINUSE: WASI_EADDRINUSE,
EADDRNOTAVAIL: WASI_EADDRNOTAVAIL,
EAFNOSUPPORT: WASI_EAFNOSUPPORT,
EALREADY: WASI_EALREADY,
EAGAIN: WASI_EAGAIN,
// EBADE: WASI_EBADE,
EBADF: WASI_EBADF,
// EBADFD: WASI_EBADFD,
EBADMSG: WASI_EBADMSG,
// EBADR: WASI_EBADR,
// EBADRQC: WASI_EBADRQC,
// EBADSLT: WASI_EBADSLT,
EBUSY: WASI_EBUSY,
ECANCELED: WASI_ECANCELED,
ECHILD: WASI_ECHILD,
// ECHRNG: WASI_ECHRNG,
// ECOMM: WASI_ECOMM,
ECONNABORTED: WASI_ECONNABORTED,
ECONNREFUSED: WASI_ECONNREFUSED,
ECONNRESET: WASI_ECONNRESET,
EDEADLOCK: WASI_EDEADLK,
EDESTADDRREQ: WASI_EDESTADDRREQ,
EDOM: WASI_EDOM,
EDQUOT: WASI_EDQUOT,
EEXIST: WASI_EEXIST,
EFAULT: WASI_EFAULT,
EFBIG: WASI_EFBIG,
EHOSTDOWN: WASI_EHOSTUNREACH,
EHOSTUNREACH: WASI_EHOSTUNREACH,
// EHWPOISON: WASI_EHWPOISON,
EIDRM: WASI_EIDRM,
EILSEQ: WASI_EILSEQ,
EINPROGRESS: WASI_EINPROGRESS,
EINTR: WASI_EINTR,
EINVAL: WASI_EINVAL,
EIO: WASI_EIO,
EISCONN: WASI_EISCONN,
EISDIR: WASI_EISDIR,
ELOOP: WASI_ELOOP,
EMFILE: WASI_EMFILE,
EMLINK: WASI_EMLINK,
EMSGSIZE: WASI_EMSGSIZE,
EMULTIHOP: WASI_EMULTIHOP,
ENAMETOOLONG: WASI_ENAMETOOLONG,
ENETDOWN: WASI_ENETDOWN,
ENETRESET: WASI_ENETRESET,
ENETUNREACH: WASI_ENETUNREACH,
ENFILE: WASI_ENFILE,
ENOBUFS: WASI_ENOBUFS,
ENODEV: WASI_ENODEV,
ENOENT: WASI_ENOENT,
ENOEXEC: WASI_ENOEXEC,
ENOLCK: WASI_ENOLCK,
ENOLINK: WASI_ENOLINK,
ENOMEM: WASI_ENOMEM,
ENOMSG: WASI_ENOMSG,
ENOPROTOOPT: WASI_ENOPROTOOPT,
ENOSPC: WASI_ENOSPC,
ENOSYS: WASI_ENOSYS,
ENOTCONN: WASI_ENOTCONN,
ENOTDIR: WASI_ENOTDIR,
ENOTEMPTY: WASI_ENOTEMPTY,
ENOTRECOVERABLE: WASI_ENOTRECOVERABLE,
ENOTSOCK: WASI_ENOTSOCK,
ENOTTY: WASI_ENOTTY,
ENXIO: WASI_ENXIO,
EOVERFLOW: WASI_EOVERFLOW,
EOWNERDEAD: WASI_EOWNERDEAD,
EPERM: WASI_EPERM,
EPIPE: WASI_EPIPE,
EPROTO: WASI_EPROTO,
EPROTONOSUPPORT: WASI_EPROTONOSUPPORT,
EPROTOTYPE: WASI_EPROTOTYPE,
ERANGE: WASI_ERANGE,
EROFS: WASI_EROFS,
ESPIPE: WASI_ESPIPE,
ESRCH: WASI_ESRCH,
ESTALE: WASI_ESTALE,
ETIMEDOUT: WASI_ETIMEDOUT,
ETXTBSY: WASI_ETXTBSY,
EXDEV: WASI_EXDEV
};
export const SIGNAL_MAP: { [key: string]: string } = {
[WASI_SIGHUP]: "SIGHUP",
[WASI_SIGINT]: "SIGINT",
[WASI_SIGQUIT]: "SIGQUIT",
[WASI_SIGILL]: "SIGILL",
[WASI_SIGTRAP]: "SIGTRAP",
[WASI_SIGABRT]: "SIGABRT",
[WASI_SIGBUS]: "SIGBUS",
[WASI_SIGFPE]: "SIGFPE",
[WASI_SIGKILL]: "SIGKILL",
[WASI_SIGUSR1]: "SIGUSR1",
[WASI_SIGSEGV]: "SIGSEGV",
[WASI_SIGUSR2]: "SIGUSR2",
[WASI_SIGPIPE]: "SIGPIPE",
[WASI_SIGALRM]: "SIGALRM",
[WASI_SIGTERM]: "SIGTERM",
[WASI_SIGCHLD]: "SIGCHLD",
[WASI_SIGCONT]: "SIGCONT",
[WASI_SIGSTOP]: "SIGSTOP",
[WASI_SIGTSTP]: "SIGTSTP",
[WASI_SIGTTIN]: "SIGTTIN",
[WASI_SIGTTOU]: "SIGTTOU",
[WASI_SIGURG]: "SIGURG",
[WASI_SIGXCPU]: "SIGXCPU",
[WASI_SIGXFSZ]: "SIGXFSZ",
[WASI_SIGVTALRM]: "SIGVTALRM"
};

14
example/worker.ts Normal file
View File

@ -0,0 +1,14 @@
import { importObject, setFiles, setMainThread, setStdin, zjs } from "./imports";
onmessage = async (e) => {
console.log("module received from main thread");
const [memory, instance, stdin, wasmModule, files, pid] = e.data;
console.log(wasmModule)
setStdin(stdin);
setMainThread(false);
setFiles(files);
importObject.env.memory = memory;
const results = await WebAssembly.instantiate(wasmModule, importObject);
zjs.memory = memory;
results.exports.wasi_thread_start(pid, instance);
};

View File

@ -1,3 +1,284 @@
pub const c = @cImport({ const builtin = @import("builtin");
pub const c = if (builtin.cpu.arch != .wasm32) @cImport({
@cInclude("glad/gl.h"); @cInclude("glad/gl.h");
}); }) else struct {
pub extern fn glBindBufferBase(_: c_uint, _: c_uint, _: c_uint) void;
pub extern fn glDrawElementsInstanced(_: GLenum, _: GLsizei, _: GLenum, _: ?*const anyopaque, _: GLsizei) void;
pub extern fn glUniform4f(_: c_int, _: f32, _: f32, _: f32, _: f32) void;
pub extern fn glUniform4fv(_: c_int, _: f32, _: f32, _: f32, _: f32) void;
pub extern fn glBindFramebuffer(_: c_uint, _: c_uint) void;
pub extern fn glGetIntegerv(_: GLenum, _: *GLint) void;
pub extern fn glTexSubImage2D(_: c_uint, _: c_int, _: c_int, _: isize, _: isize, _: c_int, _: c_uint, _: c_uint, _: ?*const anyopaque) void;
pub extern fn glDeleteFramebuffers(_: c_int, _: [*c]const c_uint) void;
pub extern fn glGenFramebuffers(_: c_int, _: [*c]c_uint) void;
pub extern fn glVertexAttribDivisor(_: c_uint, _: c_uint) void;
pub extern fn glVertexAttribIPointer(_: c_uint, _: c_int, _: c_uint, _: isize, _: ?*const anyopaque) void;
pub extern fn glBufferSubData(_: GLenum, _: isize, _: isize, _: ?*const anyopaque) void;
pub extern fn glViewport(_: c_int, _: c_int, _: isize, _: isize) void;
pub extern fn glClearColor(_: f32, _: f32, _: f32, _: f32) void;
pub extern fn glEnable(_: c_uint) void;
pub extern fn glDisable(_: c_uint) void;
pub extern fn glDepthFunc(_: c_uint) void;
pub extern fn glBlendFunc(_: c_uint, _: c_uint) void;
pub extern fn glClear(_: c_uint) void;
pub extern fn glGetAttribLocation(_: c_uint, _: [*]const u8, _: c_uint) c_int;
pub extern fn glGetUniformLocation(_: c_uint, _: [*]const u8) c_int;
pub extern fn glUniform1i(_: c_int, _: c_int) void;
pub extern fn glUniform1f(_: c_int, _: f32) void;
pub extern fn glUniformMatrix4fv(_: c_int, _: c_int, _: c_uint, _: *const f32) void;
pub extern fn glCreateVertexArray() c_uint;
pub extern fn glGenVertexArrays(_: c_int, [*c]c_uint) void;
pub extern fn glDeleteVertexArrays(_: c_int, [*c]const c_uint) void;
pub extern fn glBindVertexArray(_: c_uint) void;
pub extern fn glCreateBuffer() c_uint;
pub extern fn glGenBuffers(_: c_int, _: [*c]c_uint) void;
pub extern fn glDeleteBuffers(_: c_int, _: [*c]const c_uint) void;
pub extern fn glDeleteBuffer(_: c_uint) void;
pub extern fn glBindBuffer(_: c_uint, _: c_uint) void;
pub extern fn glBufferData(_: c_uint, _: isize, _: ?*const anyopaque, _: c_uint) void;
pub extern fn glPixelStorei(_: c_uint, _: c_int) void;
pub extern fn glAttachShader(_: c_uint, _: c_uint) void;
pub extern fn glDetachShader(_: c_uint, _: c_uint) void;
pub extern fn glDeleteShader(_: c_uint) void;
pub extern fn glCreateShader(_: c_uint) c_uint;
pub extern fn glCompileShader(_: c_uint) void;
pub extern fn glShaderSource(_: c_uint, _: c_uint, _: *const [*c]const u8, _: ?*GLint) void;
pub extern fn glCreateProgram() c_uint;
pub extern fn glGetShaderiv(_: c_uint, _: c_uint, _: [*c]c_int) void;
pub extern fn glGetShaderInfoLog(_: c_uint, _: c_int, _: [*c]c_int, _: [*c]u8) void;
pub extern fn glGetProgramiv(_: c_uint, _: c_uint, _: [*c]c_int) void;
pub extern fn glLinkProgram(_: c_uint) void;
pub extern fn glUseProgram(_: c_uint) void;
pub extern fn glGetProgramInfoLog(_: c_uint, _: c_int, _: [*c]c_int, _: [*c]u8) void;
pub extern fn glDeleteProgram(_: c_uint) void;
pub extern fn glEnableVertexAttribArray(_: c_uint) void;
pub extern fn glVertexAttribPointer(_: c_uint, _: c_int, _: c_uint, _: c_uint, _: isize, _: ?*const anyopaque) void;
pub extern fn glDrawArrays(_: c_uint, _: c_uint, _: c_int) void;
pub extern fn glCreateTexture() c_uint;
pub extern fn glGenTextures(_: c_int, _: [*c]c_uint) void;
pub extern fn glDeleteTextures(_: c_int, _: [*c]const c_uint) void;
pub extern fn glDeleteTexture(_: c_uint) void;
pub extern fn glBindTexture(_: c_uint, _: c_uint) void;
pub extern fn glTexImage2D(_: GLenum, _: GLint, _: c_int, _: c_int, _: c_int, _: c_int, _: c_uint, _: c_uint, _: ?*const anyopaque) void;
pub extern fn glTexParameteri(_: c_uint, _: c_uint, _: c_int) void;
pub extern fn glActiveTexture(_: c_uint) void;
pub extern fn glGenerateMipmap(_: c_uint) void;
pub extern fn glGetError() c_int;
pub extern fn glGetString(_: c_int) c_int;
pub extern fn glGetShaderParameter(_: c_uint, _: c_uint) c_int;
pub extern fn glUniform2f(_: c_int, _: f32, _: f32) void;
// Types.
pub const GLuint = c_uint;
pub const GLenum = c_uint;
pub const GLbitfield = c_uint;
pub const GLint = c_int;
pub const GLsizei = isize;
pub const GLfloat = f32;
pub const GLboolean = u8;
// https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants
pub const GL_FALSE = 0;
pub const GL_TRUE = 1;
pub const GL_TRIANGLES = 4;
pub const GL_DEPTH_BUFFER_BIT = 256;
pub const GL_SRC_ALPHA = 770;
pub const GL_ONE_MINUS_SRC_ALPHA = 771;
pub const GL_FLOAT = 5126;
pub const GL_CULL_FACE = 2884;
pub const GL_DEPTH_TEST = 2929;
pub const GL_BLEND = 3042;
pub const GL_TEXTURE_2D = 3553;
pub const GL_UNSIGNED_BYTE = 5121;
pub const GL_RED = 6403;
pub const GL_RGBA = 6408;
pub const GL_VERSION = 7938;
pub const GL_LINEAR: GLint = 9729;
pub const GL_TEXTURE_MAG_FILTER = 10240;
pub const GL_TEXTURE_MIN_FILTER = 10241;
pub const GL_TEXTURE_WRAP_S = 10242;
pub const GL_TEXTURE_WRAP_T = 10243;
pub const GL_COLOR_BUFFER_BIT = 16384;
pub const GL_CLAMP_TO_EDGE: GLint = 33071;
pub const GL_RG = 33319;
pub const GL_RG32F = 33327;
pub const GL_TEXTURE0 = 33984;
pub const GL_TEXTURE1 = 33985;
pub const GL_TEXTURE2 = 33986;
pub const GL_TEXTURE3 = 33987;
pub const GL_TEXTURE4 = 33988;
pub const GL_TEXTURE5 = 33989;
pub const GL_RGBA32F = 34836;
pub const GL_ARRAY_BUFFER = 34962;
pub const GL_STATIC_DRAW = 35044;
pub const GL_FRAGMENT_SHADER = 35632;
pub const GL_VERTEX_SHADER = 35633;
pub const GL_COMPILE_STATUS = 35713;
pub const GL_LINK_STATUS = 35714;
pub const GL_FRAMEBUFFER_COMPLETE = 36053;
pub const GL_COLOR_ATTACHMENT0 = 36064;
pub const GL_COLOR_ATTACHMENT1 = 36065;
pub const GL_COLOR_ATTACHMENT2 = 36066;
pub const GL_DEPTH_ATTACHMENT = 36096;
pub const GL_FRAMEBUFFER = 36160;
pub const GL_RENDERBUFFER = 36161;
pub const GL_NO_ERROR = 0;
pub const GL_INVALID_ENUM = 0x0500;
pub const GL_INVALID_FRAMEBUFFER_OPERATION = 0x0506;
pub const GL_INVALID_OPERATION = 0x0502;
pub const GL_INVALID_VALUE = 0x0501;
pub const GL_OUT_OF_MEMORY = 0x0505;
pub const GL_ONE = 1;
pub const GL_TEXTURE_1D = 0x0DE0;
pub const GL_TEXTURE_2D_ARRAY = 0x8C1A;
pub const GL_TEXTURE_1D_ARRAY = 0x8C18;
pub const GL_TEXTURE_3D = 0x806F;
pub const GL_TEXTURE_RECTANGLE = 0x84F5;
pub const GL_TEXTURE_CUBE_MAP = 0x8513;
pub const GL_TEXTURE_BUFFER = 0x8C2A;
pub const GL_TEXTURE_2D_MULTISAMPLE = 0x9100;
pub const GL_TEXTURE_2D_MULTISAMPLE_ARRAY = 0x9102;
pub const GL_TEXTURE_BASE_LEVEL = 0x813C;
pub const GL_TEXTURE_SWIZZLE_A = 0x8E45;
pub const GL_TEXTURE_SWIZZLE_B = 0x8E44;
pub const GL_TEXTURE_SWIZZLE_G = 0x8E43;
pub const GL_TEXTURE_SWIZZLE_R = 0x8E42;
pub const GL_TEXTURE_COMPARE_FUNC = 0x884D;
pub const GL_TEXTURE_COMPARE_MODE = 0x884C;
pub const GL_TEXTURE_LOD_BIAS = 0x8501;
pub const GL_TEXTURE_MIN_LOD = 0x813A;
pub const GL_TEXTURE_MAX_LOD = 0x813B;
pub const GL_TEXTURE_MAX_LEVEL = 0x813D;
pub const GL_TEXTURE_WRAP_R = 0x8072;
pub const GL_RGB = 0x1907;
pub const GL_BGRA = 0x80E1;
pub const GL_ELEMENT_ARRAY_BUFFER = 0x8893;
pub const GL_UNIFORM_BUFFER = 0x8A11;
pub const GL_STREAM_COPY = 0x88E2;
pub const GL_STREAM_DRAW = 0x88E0;
pub const GL_STREAM_READ = 0x88E1;
pub const GL_STATIC_COPY = 0x88E6;
pub const GL_STATIC_READ = 0x88E5;
pub const GL_DYNAMIC_COPY = 0x88EA;
pub const GL_DYNAMIC_DRAW = 0x88E8;
pub const GL_DYNAMIC_READ = 0x88E9;
pub const GL_UNSIGNED_SHORT = 0x1403;
pub const GL_INT = 0x1404;
pub const GL_UNSIGNED_INT = 0x1405;
pub const GL_DRAW_FRAMEBUFFER = 0x8CA9;
pub const GL_READ_FRAMEBUFFER_BINDING = 0x8CAA;
pub const GL_READ_FRAMEBUFFER = 0x8CA8;
pub const GL_FRAMEBUFFER_BINDING = 0x8CA6;
pub const GladGLContext = struct {
pub fn init(self: *GladGLContext) void {
self.* = .{
.Enable = glEnable,
.GetError = glGetError,
.BlendFunc = glBlendFunc,
.BindTexture = glBindTexture,
.GenTextures = glGenTextures,
.DeleteTextures = glDeleteTextures,
.TexImage2D = glTexImage2D,
.CreateShader = glCreateShader,
.CompileShader = glCompileShader,
.DeleteShader = glDeleteShader,
.AttachShader = glAttachShader,
.ShaderSource = glShaderSource,
.GetShaderiv = glGetShaderiv,
.GetShaderInfoLog = glGetShaderInfoLog,
.CreateProgram = glCreateProgram,
.LinkProgram = glLinkProgram,
.GetProgramiv = glGetProgramiv,
.GetProgramInfoLog = glGetProgramInfoLog,
.UseProgram = glUseProgram,
.DeleteProgram = glDeleteProgram,
.GetUniformLocation = glGetUniformLocation,
.Uniform4fv = glUniform4fv,
.Uniform1i = glUniform1i,
.Uniform1f = glUniform1f,
.Uniform2f = glUniform2f,
.UniformMatrix4fv = glUniformMatrix4fv,
.GenVertexArrays = glGenVertexArrays,
.DeleteVertexArrays = glDeleteVertexArrays,
.BindVertexArray = glBindVertexArray,
.GenBuffers = glGenBuffers,
.DeleteBuffers = glDeleteBuffers,
.DeleteBuffer = glDeleteBuffer,
.BindBuffer = glBindBuffer,
.BufferData = glBufferData,
.BufferSubData = glBufferSubData,
.VertexAttribPointer = glVertexAttribPointer,
.VertexAttribIPointer = glVertexAttribIPointer,
.EnableVertexAttribArray = glEnableVertexAttribArray,
.VertexAttribDivisor = glVertexAttribDivisor,
.GenFramebuffers = glGenFramebuffers,
.DeleteFramebuffers = glDeleteFramebuffers,
.TexSubImage2D = glTexSubImage2D,
.GetIntegerv = glGetIntegerv,
.BindFramebuffer = glBindFramebuffer,
.ClearColor = glClearColor,
.Clear = glClear,
.Viewport = glViewport,
.Uniform4f = glUniform4f,
.ActiveTexture = glActiveTexture,
.DrawElementsInstanced = glDrawElementsInstanced,
.BindBufferBase = glBindBufferBase,
.TexParameteri = glTexParameteri,
};
}
Enable: ?*const fn (_: GLenum) callconv(.C) void,
GetError: ?*const fn () callconv(.C) c_int,
BlendFunc: ?*const fn (_: c_uint, _: c_uint) callconv(.C) void,
BindTexture: ?*const fn (_: c_uint, _: c_uint) callconv(.C) void,
GenTextures: ?*const fn (_: c_int, _: [*c]c_uint) callconv(.C) void,
DeleteTextures: ?*const fn (_: c_int, _: [*c]const c_uint) callconv(.C) void,
TexImage2D: ?*const fn (_: GLenum, _: GLint, _: c_int, _: c_int, _: c_int, _: c_int, _: c_uint, _: c_uint, _: ?*const anyopaque) callconv(.C) void,
CreateShader: ?*const fn (_: GLenum) callconv(.C) GLuint,
CompileShader: ?*const fn (_: c_uint) callconv(.C) void,
DeleteShader: ?*const fn (_: c_uint) callconv(.C) void,
AttachShader: ?*const fn (_: c_uint, _: c_uint) callconv(.C) void,
ShaderSource: ?*const fn (_: c_uint, _: c_uint, _: *const [*c]const u8, _: ?*GLint) callconv(.C) void,
GetShaderiv: ?*const fn (_: c_uint, _: c_uint, _: [*c]c_int) callconv(.C) void,
GetShaderInfoLog: ?*const fn (_: c_uint, _: c_int, _: [*c]c_int, _: [*c]u8) callconv(.C) void,
CreateProgram: ?*const fn () callconv(.C) c_uint,
LinkProgram: ?*const fn (_: c_uint) callconv(.C) void,
GetProgramiv: ?*const fn (_: c_uint, _: c_uint, _: [*c]c_int) callconv(.C) void,
GetProgramInfoLog: ?*const fn (_: c_uint, _: c_int, _: [*c]c_int, _: [*c]u8) callconv(.C) void,
UseProgram: ?*const fn (_: c_uint) callconv(.C) void,
DeleteProgram: ?*const fn (_: c_uint) callconv(.C) void,
GetUniformLocation: ?*const fn (_: c_uint, _: [*]const u8) callconv(.C) c_int,
Uniform4fv: ?*const fn (_: c_int, _: f32, _: f32, _: f32, _: f32) callconv(.C) void,
Uniform1i: ?*const fn (_: c_int, _: c_int) callconv(.C) void,
Uniform1f: ?*const fn (_: c_int, _: f32) callconv(.C) void,
Uniform2f: ?*const fn (_: c_int, _: f32, _: f32) callconv(.C) void,
UniformMatrix4fv: ?*const fn (_: c_int, _: c_int, _: c_uint, _: *const f32) callconv(.C) void,
GenVertexArrays: ?*const fn (_: c_int, [*c]c_uint) callconv(.C) void,
DeleteVertexArrays: ?*const fn (_: c_int, [*c]const c_uint) callconv(.C) void,
BindVertexArray: ?*const fn (_: c_uint) callconv(.C) void,
GenBuffers: ?*const fn (_: c_int, _: [*c]c_uint) callconv(.C) void,
DeleteBuffers: ?*const fn (_: c_int, _: [*c]const c_uint) callconv(.C) void,
DeleteBuffer: ?*const fn (_: c_uint) callconv(.C) void,
BindBuffer: ?*const fn (_: c_uint, _: c_uint) callconv(.C) void,
BufferData: ?*const fn (_: c_uint, _: isize, _: ?*const anyopaque, _: c_uint) callconv(.C) void,
BufferSubData: ?*const fn (_: GLenum, _: isize, _: isize, _: ?*const anyopaque) callconv(.C) void,
VertexAttribPointer: ?*const fn (_: c_uint, _: c_int, _: c_uint, _: c_uint, _: isize, _: ?*const anyopaque) callconv(.C) void,
VertexAttribIPointer: ?*const fn (_: c_uint, _: c_int, _: c_uint, _: isize, _: ?*const anyopaque) callconv(.C) void,
EnableVertexAttribArray: ?*const fn (_: c_uint) callconv(.C) void,
VertexAttribDivisor: ?*const fn (_: c_uint, _: c_uint) callconv(.C) void,
GenFramebuffers: ?*const fn (_: c_int, _: [*c]c_uint) callconv(.C) void,
DeleteFramebuffers: ?*const fn (_: c_int, _: [*c]const c_uint) callconv(.C) void,
TexSubImage2D: ?*const fn (_: c_uint, _: c_int, _: c_int, _: isize, _: isize, _: c_int, _: c_uint, _: c_uint, _: ?*const anyopaque) callconv(.C) void,
GetIntegerv: ?*const fn (_: GLenum, _: *GLint) callconv(.C) void,
BindFramebuffer: ?*const fn (_: c_uint, _: c_uint) callconv(.C) void,
ClearColor: ?*const fn (_: f32, _: f32, _: f32, _: f32) callconv(.C) void,
Clear: ?*const fn (_: c_uint) callconv(.C) void,
Viewport: ?*const fn (_: c_int, _: c_int, _: isize, _: isize) callconv(.C) void,
Uniform4f: ?*const fn (_: c_int, _: f32, _: f32, _: f32, _: f32) callconv(.C) void,
ActiveTexture: ?*const fn (_: c_uint) callconv(.C) void,
DrawElementsInstanced: ?*const fn (_: GLenum, _: GLsizei, _: GLenum, _: ?*const anyopaque, _: GLsizei) callconv(.C) void,
BindBufferBase: ?*const fn (_: c_uint, _: c_uint, _: c_uint) callconv(.C) void,
TexParameteri: ?*const fn (_: c_uint, _: c_uint, _: c_int) callconv(.C) void,
};
};

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

@ -22,6 +22,7 @@ pub fn build(b: *std.Build) !void {
// Zig 0.13 bug: https://github.com/ziglang/zig/issues/20414 // Zig 0.13 bug: https://github.com/ziglang/zig/issues/20414
// (See root Ghostty build.zig on why we do this) // (See root Ghostty build.zig on why we do this)
try flags.appendSlice(&.{"-DSIMDUTF_IMPLEMENTATION_ICELAKE=0"}); try flags.appendSlice(&.{"-DSIMDUTF_IMPLEMENTATION_ICELAKE=0"});
try flags.appendSlice(&.{"-DSIMDUTF_NO_THREADS"});
lib.addCSourceFiles(.{ lib.addCSourceFiles(.{
.flags = flags.items, .flags = flags.items,

View File

@ -73,7 +73,8 @@ pseudo_console: if (builtin.os.tag == .windows) ?windows.exp.HPCON else void =
data: ?*anyopaque = null, data: ?*anyopaque = null,
/// Process ID is set after start is called. /// Process ID is set after start is called.
pid: ?posix.pid_t = null, pid: ?PidT = null,
pub const PidT = if (builtin.cpu.arch != .wasm32) posix.pid_t else i32;
/// LinuxCGroup type depends on our target OS /// LinuxCGroup type depends on our target OS
pub const LinuxCgroup = if (builtin.os.tag == .linux) ?[]const u8 else void; pub const LinuxCgroup = if (builtin.os.tag == .linux) ?[]const u8 else void;
@ -122,6 +123,7 @@ pub fn start(self: *Command, alloc: Allocator) !void {
switch (builtin.os.tag) { switch (builtin.os.tag) {
.windows => try self.startWindows(arena), .windows => try self.startWindows(arena),
.wasi => try self.startWasi(arena),
else => try self.startPosix(arena), else => try self.startPosix(arena),
} }
} }
@ -187,6 +189,26 @@ fn startPosix(self: *Command, arena: Allocator) !void {
return error.ExecFailedInChild; return error.ExecFailedInChild;
} }
fn startWasi(self: *Command, arena: Allocator) !void {
// Null-terminate all our arguments
const pathZ = try arena.dupeZ(u8, self.path);
const argsZ = try arena.allocSentinel(?[*:0]u8, self.args.len, null);
for (self.args, 0..) |arg, i| argsZ[i] = (try arena.dupeZ(u8, arg)).ptr;
// Determine our env vars
const envp = if (self.env) |env_map|
(try createNullDelimitedEnvMap(arena, env_map)).ptr
else if (builtin.link_libc)
std.c.environ
else
@compileError("missing env vars");
self.pid = 100;
std.log.err("need to fork {s} {*}", .{ pathZ, envp });
return;
}
fn startWindows(self: *Command, arena: Allocator) !void { fn startWindows(self: *Command, arena: Allocator) !void {
const application_w = try std.unicode.utf8ToUtf16LeWithNull(arena, self.path); const application_w = try std.unicode.utf8ToUtf16LeWithNull(arena, self.path);
const cwd_w = if (self.cwd) |cwd| try std.unicode.utf8ToUtf16LeWithNull(arena, cwd) else null; const cwd_w = if (self.cwd) |cwd| try std.unicode.utf8ToUtf16LeWithNull(arena, cwd) else null;
@ -324,6 +346,7 @@ fn setupFd(src: File.Handle, target: i32) !void {
try posix.dup2(src, target); try posix.dup2(src, target);
}, },
.wasi => {},
else => @compileError("unsupported platform"), else => @compileError("unsupported platform"),
} }
} }

View File

@ -592,7 +592,7 @@ pub fn init(
// Start our renderer thread // Start our renderer thread
self.renderer_thr = try std.Thread.spawn( self.renderer_thr = try std.Thread.spawn(
.{}, .{ .allocator = alloc },
renderer.Thread.threadMain, renderer.Thread.threadMain,
.{&self.renderer_thread}, .{&self.renderer_thread},
); );
@ -600,7 +600,7 @@ pub fn init(
// Start our IO thread // Start our IO thread
self.io_thr = try std.Thread.spawn( self.io_thr = try std.Thread.spawn(
.{}, .{ .allocator = alloc },
termio.Thread.threadMain, termio.Thread.threadMain,
.{ &self.io_thread, &self.io }, .{ &self.io_thread, &self.io },
); );

View File

@ -1,2 +1,101 @@
pub const App = struct {}; const CoreSurface = @import("../Surface.zig");
const CoreApp = @import("../App.zig");
const apprt = @import("../apprt.zig");
const std = @import("std");
const log = std.log;
pub const App = struct {
app: *CoreApp,
pub const Options = struct {};
pub fn init(core_app: *CoreApp, _: Options) !App {
return .{ .app = core_app };
}
pub fn performAction(
self: *App,
target: apprt.Target,
comptime action: apprt.Action.Key,
value: apprt.Action.Value(action),
) !void {
switch (action) {
.new_window => _ = try self.newSurface(switch (target) {
.app => null,
.surface => |v| v,
}),
.new_tab => try self.newTab(switch (target) {
.app => null,
.surface => |v| v,
}),
.initial_size => switch (target) {
.app => {},
.surface => |surface| try surface.rt_surface.setInitialWindowSize(
value.width,
value.height,
),
},
// Unimplemented
.size_limit,
.toggle_fullscreen,
.set_title,
.mouse_shape,
.mouse_visibility,
.open_config,
.new_split,
.goto_split,
.resize_split,
.equalize_splits,
.toggle_split_zoom,
.present_terminal,
.close_all_windows,
.toggle_tab_overview,
.toggle_window_decorations,
.toggle_quick_terminal,
.toggle_visibility,
.goto_tab,
.move_tab,
.inspector,
.render_inspector,
.quit_timer,
.secure_input,
.key_sequence,
.desktop_notification,
.mouse_over_link,
.cell_size,
.renderer_health,
.color_change,
.pwd,
.reload_config,
.config_change,
=> log.info("unimplemented action={}", .{action}),
}
}
pub fn wakeup(self: *const App) void {
_ = self;
}
};
pub const Window = struct {}; pub const Window = struct {};
pub const Surface = struct {
pub const opengl_single_threaded_draw = true;
/// The app we're part of
app: *App,
/// A core surface
core_surface: CoreSurface,
pub fn init(self: *Surface, app: *App) !void {
self.app = app;
}
pub fn getContentScale(_: *const Surface) !apprt.ContentScale {
return apprt.ContentScale{ .x = 1, .y = 1 };
}
pub fn getSize(_: *const Surface) !apprt.SurfaceSize {
return apprt.SurfaceSize{ .width = 500, .height = 500 };
}
fn setInitialWindowSize(self: *const Surface, width: u32, height: u32) !void {
_ = height; // autofix
_ = self; // autofix
_ = width; // autofix
}
};

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

@ -2638,6 +2638,7 @@ pub fn changeConditionalState(
/// Expand the relative paths in config-files to be absolute paths /// Expand the relative paths in config-files to be absolute paths
/// relative to the base directory. /// relative to the base directory.
fn expandPaths(self: *Config, base: []const u8) !void { fn expandPaths(self: *Config, base: []const u8) !void {
if (builtin.cpu.arch == .wasm32) return error.WasmCannotExpandPaths;
const arena_alloc = self._arena.?.allocator(); const arena_alloc = self._arena.?.allocator();
// Keep track of this step for replays // Keep track of this step for replays
@ -2752,19 +2753,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

@ -163,6 +163,7 @@ pub fn load(
lib: Library, lib: Library,
opts: font.face.Options, opts: font.face.Options,
) !Face { ) !Face {
log.err("load {}", .{opts.size});
return switch (options.backend) { return switch (options.backend) {
.fontconfig_freetype => try self.loadFontconfig(lib, opts), .fontconfig_freetype => try self.loadFontconfig(lib, opts),
.coretext, .coretext_harfbuzz, .coretext_noshape => try self.loadCoreText(lib, opts), .coretext, .coretext_harfbuzz, .coretext_noshape => try self.loadCoreText(lib, opts),
@ -254,7 +255,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 +393,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

@ -255,42 +255,67 @@ fn collection(
try c.completeStyles(self.alloc, config.@"font-synthetic-style"); try c.completeStyles(self.alloc, config.@"font-synthetic-style");
// Our built-in font will be used as a backup // Our built-in font will be used as a backup
_ = try c.add( if (builtin.cpu.arch != .wasm32) {
self.alloc, _ = try c.add(
.regular, self.alloc,
.{ .fallback_loaded = try Face.init( .regular,
self.font_lib, .{ .fallback_loaded = try Face.init(
font.embedded.regular, self.font_lib,
load_options.faceOptions(), font.embedded.regular,
) }, load_options.faceOptions(),
); ) },
_ = try c.add( );
self.alloc, _ = try c.add(
.bold, self.alloc,
.{ .fallback_loaded = try Face.init( .bold,
self.font_lib, .{ .fallback_loaded = try Face.init(
font.embedded.bold, self.font_lib,
load_options.faceOptions(), font.embedded.bold,
) }, load_options.faceOptions(),
); ) },
_ = try c.add( );
self.alloc, _ = try c.add(
.italic, self.alloc,
.{ .fallback_loaded = try Face.init( .italic,
self.font_lib, .{ .fallback_loaded = try Face.init(
font.embedded.italic, self.font_lib,
load_options.faceOptions(), font.embedded.italic,
) }, load_options.faceOptions(),
); ) },
_ = try c.add( );
self.alloc, _ = try c.add(
.bold_italic, self.alloc,
.{ .fallback_loaded = try Face.init( .bold_italic,
self.font_lib, .{ .fallback_loaded = try Face.init(
font.embedded.bold_italic, self.font_lib,
load_options.faceOptions(), font.embedded.bold_italic,
) }, load_options.faceOptions(),
); ) },
);
} else {
const family = if (config.@"font-family".list.items.len > 0) config.@"font-family".list.items[0] else "monospace";
_ = try c.add(
self.alloc,
.regular,
.{ .fallback_loaded = try Face.initNamed(
self.alloc,
family,
load_options.size,
.text,
) },
);
_ = try c.add(
self.alloc,
.regular,
.{ .fallback_loaded = try Face.initNamed(
self.alloc,
family,
load_options.size,
.emoji,
) },
);
}
// On macOS, always search for and add the Apple Emoji font // On macOS, always search for and add the Apple Emoji font
// as our preferred emoji font for fallback. We do this in case // as our preferred emoji font for fallback. We do this in case
@ -314,7 +339,7 @@ fn collection(
// Emoji fallback. We don't include this on Mac since Mac is expected // Emoji fallback. We don't include this on Mac since Mac is expected
// to always have the Apple Emoji available on the system. // to always have the Apple Emoji available on the system.
if (comptime !builtin.target.isDarwin() or Discover == void) { if (builtin.cpu.arch != .wasm32 and comptime !builtin.target.isDarwin() or Discover == void) {
_ = try c.add( _ = try c.add(
self.alloc, self.alloc,
.regular, .regular,

View File

@ -29,9 +29,6 @@ pub const Face = struct {
/// Metrics for this font face. These are useful for renderers. /// Metrics for this font face. These are useful for renderers.
metrics: font.face.Metrics, metrics: font.face.Metrics,
/// The canvas element that we will reuse to render glyphs
canvas: js.Object,
/// The map to store multi-codepoint grapheme clusters that are rendered. /// The map to store multi-codepoint grapheme clusters that are rendered.
/// We use 1 above the maximum unicode codepoint up to the max 32-bit /// We use 1 above the maximum unicode codepoint up to the max 32-bit
/// unsigned integer to store the "glyph index" for graphemes. /// unsigned integer to store the "glyph index" for graphemes.
@ -58,20 +55,12 @@ pub const Face = struct {
const font_str = try alloc.dupe(u8, raw); const font_str = try alloc.dupe(u8, raw);
errdefer alloc.free(font_str); 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")});
errdefer canvas.deinit();
var result = Face{ var result = Face{
.alloc = alloc, .alloc = alloc,
.font_str = font_str, .font_str = font_str,
.size = size, .size = size,
.presentation = presentation, .presentation = presentation,
.canvas = canvas,
// We're going to calculate these right after initialization. // We're going to calculate these right after initialization.
.metrics = undefined, .metrics = undefined,
}; };
@ -89,7 +78,6 @@ pub const Face = struct {
while (it.next()) |value| self.alloc.free(value.*); while (it.next()) |value| self.alloc.free(value.*);
self.glyph_to_grapheme.deinit(self.alloc); self.glyph_to_grapheme.deinit(self.alloc);
} }
self.canvas.deinit();
self.* = undefined; self.* = undefined;
} }
@ -232,7 +220,7 @@ pub const Face = struct {
.height = render.height, .height = render.height,
// TODO: this can't be right // TODO: this can't be right
.offset_x = 0, .offset_x = 0,
.offset_y = 0, .offset_y = render.y_offset,
.atlas_x = region.x, .atlas_x = region.x,
.atlas_y = region.y, .atlas_y = region.y,
.advance_x = 0, .advance_x = 0,
@ -262,8 +250,8 @@ pub const Face = struct {
// pixel height but this is a more surefire way to get it. // pixel height but this is a more surefire way to get it.
const height_metrics = try ctx.call(js.Object, "measureText", .{js.string("M_")}); const height_metrics = try ctx.call(js.Object, "measureText", .{js.string("M_")});
defer height_metrics.deinit(); defer height_metrics.deinit();
const asc = try height_metrics.get(f32, "actualBoundingBoxAscent"); const asc = try height_metrics.get(f32, "fontBoundingBoxAscent");
const desc = try height_metrics.get(f32, "actualBoundingBoxDescent"); const desc = try height_metrics.get(f32, "fontBoundingBoxDescent");
const cell_height = asc + desc; const cell_height = asc + desc;
const cell_baseline = desc; const cell_baseline = desc;
@ -291,13 +279,15 @@ pub const Face = struct {
fn context(self: Face) !js.Object { fn context(self: Face) !js.Object {
// This will return the same context on subsequent calls so it // This will return the same context on subsequent calls so it
// is important to reset it. // is important to reset it.
const ctx = try self.canvas.call(js.Object, "getContext", .{js.string("2d")}); const canvas = try js.global.get(js.Object, "fontCanvas");
defer canvas.deinit();
const ctx = try canvas.call(js.Object, "getContext", .{js.string("2d")});
errdefer ctx.deinit(); errdefer ctx.deinit();
// Clear the canvas // Clear the canvas
{ {
const width = try self.canvas.get(f64, "width"); const width = try canvas.get(f64, "width");
const height = try self.canvas.get(f64, "height"); const height = try canvas.get(f64, "height");
try ctx.call(void, "clearRect", .{ 0, 0, width, height }); try ctx.call(void, "clearRect", .{ 0, 0, width, height });
} }
@ -325,6 +315,27 @@ pub const Face = struct {
return ctx; 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 /// 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 {
@ -332,6 +343,7 @@ pub const Face = struct {
metrics: js.Object, metrics: js.Object,
width: u32, width: u32,
height: u32, height: u32,
y_offset: i32,
bitmap: []u8, bitmap: []u8,
pub fn deinit(self: *RenderedGlyph) void { pub fn deinit(self: *RenderedGlyph) void {
@ -392,24 +404,34 @@ pub const Face = struct {
// Height is our ascender + descender for this char // Height is our ascender + descender for this char
const height = if (!broken_bbox) @as(u32, @intFromFloat(@ceil(asc + desc))) + 1 else width; 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));
ctx_temp.deinit();
// Note: width and height both get "+ 1" added to them above. This // 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 // is important so that there is a 1px border around the glyph to avoid
// any clipping in the atlas. // any clipping in the atlas.
// Resize canvas to match the glyph size exactly // Resize canvas to match the glyph size exactly
{ {
try self.canvas.set("width", width); const canvas = try js.global.get(js.Object, "fontCanvas");
try self.canvas.set("height", height); defer canvas.deinit();
try canvas.set("width", width);
try canvas.set("height", height);
const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width}); // const width_str = try std.fmt.allocPrint(alloc, "{d}px", .{width});
defer alloc.free(width_str); // defer alloc.free(width_str);
const height_str = try std.fmt.allocPrint(alloc, "{d}px", .{height}); // const height_str = try std.fmt.allocPrint(alloc, "{d}px", .{height});
defer alloc.free(height_str); // defer alloc.free(height_str);
const style = try self.canvas.get(js.Object, "style"); // const style = try self.canvas.get(js.Object, "style");
defer style.deinit(); // defer style.deinit();
try style.set("width", js.string(width_str)); // try style.set("width", js.string(width_str));
try style.set("height", js.string(height_str)); // try style.set("height", js.string(height_str));
} }
// Reload our context since we resized the canvas // Reload our context since we resized the canvas
@ -484,6 +506,7 @@ pub const Face = struct {
.width = width, .width = width,
.height = height, .height = height,
.bitmap = bitmap, .bitmap = bitmap,
.y_offset = y_offset,
}; };
} }
}; };
@ -494,7 +517,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 {
@ -531,25 +554,25 @@ pub const Wasm = struct {
}; };
} }
export fn face_debug_canvas(face: *Face) void { // export fn face_debug_canvas(face: *Face) void {
face_debug_canvas_(face) catch |err| { // face_debug_canvas_(face) catch |err| {
log.warn("error adding debug canvas err={}", .{err}); // log.warn("error adding debug canvas err={}", .{err});
}; // };
} // }
fn face_debug_canvas_(face: *Face) !void { // fn face_debug_canvas_(face: *Face) !void {
const doc = try js.global.get(js.Object, "document"); // const doc = try js.global.get(js.Object, "document");
defer doc.deinit(); // defer doc.deinit();
const elem = try doc.call( // const elem = try doc.call(
?js.Object, // ?js.Object,
"getElementById", // "getElementById",
.{js.string("face-canvas")}, // .{js.string("face-canvas")},
) orelse return error.CanvasContainerNotFound; // ) orelse return error.CanvasContainerNotFound;
defer elem.deinit(); // defer elem.deinit();
try elem.call(void, "append", .{face.canvas}); // try elem.call(void, "append", .{face.canvas});
} // }
fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph { 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, .{});

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,45 +266,130 @@ 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});
}; };
} }
const js = @import("zig-js");
fn shaper_test_(self: *Shaper, group: *font.GroupCache, str: []const u8) !void { fn createImageData(self: *font.Atlas) !js.Object {
// Create a terminal and print all our characters into it. // We need to draw pixels so this is format dependent.
var term = try terminal.Terminal.init(alloc, self.cell_buf.len, 80); 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); 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 row_it = term.screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
var iter = view.iterator(); var y: usize = 0;
while (iter.nextCodepoint()) |c| { const Render = struct {
try term.print(c); 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, .{});
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. const doc = try js.global.get(js.Object, "document");
var rowIter = term.screen.rowIterator(.viewport); defer doc.deinit();
var y: usize = 0; const canvas = try doc.call(js.Object, "getElementById", .{js.string("shaper-canvas")});
while (rowIter.next()) |row| { errdefer canvas.deinit();
defer y += 1; const ctx = try canvas.call(js.Object, "getContext", .{js.string("2d")});
defer ctx.deinit();
var iter = self.runIterator(group, row, null, null); for (cell_list.items) |cell| {
while (try iter.next(alloc)) |run| { const x_start = -@as(isize, @intCast(cell.render.glyph.atlas_x));
const cells = try self.shape(run); const y_start = -@as(isize, @intCast(cell.render.glyph.atlas_y));
log.info("y={} run={d} shape={any} idx={}", .{ try ctx.call(void, "putImageData", .{
y, if (cell.render.presentation == .emoji) colour_data else gray_data,
run.cells, x_start + @as(isize, @intCast(cell.x * grid.metrics.cell_width)) - cell.render.glyph.offset_x,
cells, y_start + @as(isize, @intCast(cell.y * grid.metrics.cell_height)) - cell.render.glyph.offset_y,
run.font_index, cell.render.glyph.atlas_x,
}); cell.render.glyph.atlas_y,
} cell.render.glyph.width,
cell.render.glyph.height,
});
} }
} }
}; };

View File

@ -6,6 +6,7 @@ const Link = @This();
const oni = @import("oniguruma"); const oni = @import("oniguruma");
const Mods = @import("key.zig").Mods; const Mods = @import("key.zig").Mods;
const builtin = @import("builtin");
/// The regular expression that will be used to match the link. Ownership /// The regular expression that will be used to match the link. Ownership
/// of this memory is up to the caller. The link will never free this memory. /// of this memory is up to the caller. The link will never free this memory.

View File

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
pub const cell = @import("cell.zig"); pub const cell = @import("cell.zig");
pub const cursor = @import("cursor.zig"); pub const cursor = @import("cursor.zig");
pub const key = @import("key.zig"); pub const key = @import("key.zig");
@ -6,7 +7,7 @@ pub const page = @import("page.zig");
pub const termio = @import("termio.zig"); pub const termio = @import("termio.zig");
pub const Cell = cell.Cell; pub const Cell = cell.Cell;
pub const Inspector = @import("Inspector.zig"); pub const Inspector = if (builtin.cpu.arch != .wasm32) @import("Inspector.zig") else struct {};
test { test {
@import("std").testing.refAllDecls(@This()); @import("std").testing.refAllDecls(@This());

View File

@ -11,16 +11,79 @@ comptime {
_ = @import("App.zig").Wasm; _ = @import("App.zig").Wasm;
} }
pub const std_options = struct { const renderer = @import("renderer.zig");
const Surface = @import("Surface.zig");
const App = @import("App.zig");
const apprt = @import("apprt.zig");
const wasm = @import("os/wasm.zig");
const alloc = wasm.alloc;
const cli = @import("cli.zig");
const Config = @import("config/Config.zig");
export fn run(str: [*]const u8, len: usize) void {
run_(str[0..len]) catch |err| {
std.log.err("err: {?}", .{err});
};
}
var surf: ?*Surface = null;
export fn draw() void {
surf.?.renderer.drawFrame(surf.?.rt_surface) catch |err| {
std.log.err("err: {?}", .{err});
};
}
fn run_(str: []const u8) !void {
var config = try Config.default(alloc);
var fbs = std.io.fixedBufferStream(str);
var iter = cli.args.lineIterator(fbs.reader());
try config.loadIter(alloc, &iter);
try config.finalize();
std.log.err("font-size {}", .{config.@"font-size"});
const app = try App.create(alloc);
// Create our runtime app
var app_runtime = try apprt.App.init(app, .{});
const surface = try alloc.create(Surface);
const apprt_surface = try alloc.create(apprt.Surface);
try surface.init(alloc, &config, app, &app_runtime, apprt_surface);
std.log.err("{}", .{surface.size});
try surface.renderer.setScreenSize(surface.size);
surf = surface;
// const esc = "\x1b[";
// surface.io.processOutput("M_yhelloaaaaaaaaa\n\r🐏\n\r👍🏽\n\rM_ghostty" ++ esc ++ "2;2H" ++ esc ++ "48;2;240;40;40m" ++ esc ++ "38;2;23;255;80mhello");
// // try surface.renderer_state.terminal.printString("M_yhelloaaaaaaaaa\n🐏\n👍🏽\nM_ghostty");
// // surface.renderer_state.terminal.setCursorPos(4, 2);
// // try surface.renderer_state.terminal.setAttribute(.{ .direct_color_bg = .{
// // .r = 240,
// // .g = 40,
// // .b = 40,
// // } });
// // try surface.renderer_state.terminal.setAttribute(.{ .direct_color_fg = .{
// // .r = 255,
// // .g = 255,
// // .b = 255,
// // } });
// // try surface.renderer_state.terminal.printString("hello");
// try surface.renderer.updateFrame(apprt_surface, &surface.renderer_state, false);
// try surface.renderer.drawFrame(apprt_surface);
// try surface.renderer.updateFrame(apprt_surface, &surface.renderer_state, false);
// try surface.renderer.drawFrame(apprt_surface);
// // const webgl = try renderer.OpenGL.init(alloc, .{ .config = try renderer.OpenGL.DerivedConfig.init(alloc, &config) });
// // _ = webgl;
}
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,
}; },
// 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, .wasi => 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

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const posix = std.posix; const posix = std.posix;
pub const HostnameParsingError = error{ pub const HostnameParsingError = error{
@ -57,6 +58,7 @@ pub const LocalHostnameValidationError = error{
Unexpected, Unexpected,
}; };
const HOST_NAME_MAX = (if (builtin.cpu.arch == .wasm32) 64 else posix.HOST_NAME_MAX);
/// Checks if a hostname is local to the current machine. This matches /// Checks if a hostname is local to the current machine. This matches
/// both "localhost" and the current hostname of the machine (as returned /// both "localhost" and the current hostname of the machine (as returned
/// by `gethostname`). /// by `gethostname`).
@ -65,7 +67,8 @@ pub fn isLocalHostname(hostname: []const u8) LocalHostnameValidationError!bool {
if (std.mem.eql(u8, "localhost", hostname)) return true; if (std.mem.eql(u8, "localhost", hostname)) return true;
// If hostname is not "localhost" it must match our hostname. // If hostname is not "localhost" it must match our hostname.
var buf: [posix.HOST_NAME_MAX]u8 = undefined; if (builtin.cpu.arch == .wasm32) return false;
var buf: [HOST_NAME_MAX]u8 = undefined;
const ourHostname = try posix.gethostname(&buf); const ourHostname = try posix.gethostname(&buf);
return std.mem.eql(u8, hostname, ourHostname); return std.mem.eql(u8, hostname, ourHostname);
} }
@ -73,7 +76,7 @@ pub fn isLocalHostname(hostname: []const u8) LocalHostnameValidationError!bool {
test "bufPrintHostnameFromFileUri succeeds with ascii hostname" { test "bufPrintHostnameFromFileUri succeeds with ascii hostname" {
const uri = try std.Uri.parse("file://localhost/"); const uri = try std.Uri.parse("file://localhost/");
var buf: [posix.HOST_NAME_MAX]u8 = undefined; var buf: [HOST_NAME_MAX]u8 = undefined;
const actual = try bufPrintHostnameFromFileUri(&buf, uri); const actual = try bufPrintHostnameFromFileUri(&buf, uri);
try std.testing.expectEqualStrings("localhost", actual); try std.testing.expectEqualStrings("localhost", actual);
} }
@ -81,7 +84,7 @@ test "bufPrintHostnameFromFileUri succeeds with ascii hostname" {
test "bufPrintHostnameFromFileUri succeeds with hostname as mac address" { test "bufPrintHostnameFromFileUri succeeds with hostname as mac address" {
const uri = try std.Uri.parse("file://12:34:56:78:90:12"); const uri = try std.Uri.parse("file://12:34:56:78:90:12");
var buf: [posix.HOST_NAME_MAX]u8 = undefined; var buf: [HOST_NAME_MAX]u8 = undefined;
const actual = try bufPrintHostnameFromFileUri(&buf, uri); const actual = try bufPrintHostnameFromFileUri(&buf, uri);
try std.testing.expectEqualStrings("12:34:56:78:90:12", actual); try std.testing.expectEqualStrings("12:34:56:78:90:12", actual);
} }
@ -89,7 +92,7 @@ test "bufPrintHostnameFromFileUri succeeds with hostname as mac address" {
test "bufPrintHostnameFromFileUri succeeds with hostname as a mac address and the last section is < 10" { test "bufPrintHostnameFromFileUri succeeds with hostname as a mac address and the last section is < 10" {
const uri = try std.Uri.parse("file://12:34:56:78:90:05"); const uri = try std.Uri.parse("file://12:34:56:78:90:05");
var buf: [posix.HOST_NAME_MAX]u8 = undefined; var buf: [HOST_NAME_MAX]u8 = undefined;
const actual = try bufPrintHostnameFromFileUri(&buf, uri); const actual = try bufPrintHostnameFromFileUri(&buf, uri);
try std.testing.expectEqualStrings("12:34:56:78:90:05", actual); try std.testing.expectEqualStrings("12:34:56:78:90:05", actual);
} }
@ -98,21 +101,21 @@ test "bufPrintHostnameFromFileUri returns only hostname when there is a port com
// First: try with a non-2-digit port, to test general port handling. // First: try with a non-2-digit port, to test general port handling.
const four_port_uri = try std.Uri.parse("file://has-a-port:1234"); const four_port_uri = try std.Uri.parse("file://has-a-port:1234");
var four_port_buf: [posix.HOST_NAME_MAX]u8 = undefined; var four_port_buf: [HOST_NAME_MAX]u8 = undefined;
const four_port_actual = try bufPrintHostnameFromFileUri(&four_port_buf, four_port_uri); const four_port_actual = try bufPrintHostnameFromFileUri(&four_port_buf, four_port_uri);
try std.testing.expectEqualStrings("has-a-port", four_port_actual); try std.testing.expectEqualStrings("has-a-port", four_port_actual);
// Second: try with a 2-digit port to test mac-address handling. // Second: try with a 2-digit port to test mac-address handling.
const two_port_uri = try std.Uri.parse("file://has-a-port:12"); const two_port_uri = try std.Uri.parse("file://has-a-port:12");
var two_port_buf: [posix.HOST_NAME_MAX]u8 = undefined; var two_port_buf: [HOST_NAME_MAX]u8 = undefined;
const two_port_actual = try bufPrintHostnameFromFileUri(&two_port_buf, two_port_uri); const two_port_actual = try bufPrintHostnameFromFileUri(&two_port_buf, two_port_uri);
try std.testing.expectEqualStrings("has-a-port", two_port_actual); try std.testing.expectEqualStrings("has-a-port", two_port_actual);
// Third: try with a mac-address that has a port-component added to it to test mac-address handling. // Third: try with a mac-address that has a port-component added to it to test mac-address handling.
const mac_with_port_uri = try std.Uri.parse("file://12:34:56:78:90:12:1234"); const mac_with_port_uri = try std.Uri.parse("file://12:34:56:78:90:12:1234");
var mac_with_port_buf: [posix.HOST_NAME_MAX]u8 = undefined; var mac_with_port_buf: [HOST_NAME_MAX]u8 = undefined;
const mac_with_port_actual = try bufPrintHostnameFromFileUri(&mac_with_port_buf, mac_with_port_uri); const mac_with_port_actual = try bufPrintHostnameFromFileUri(&mac_with_port_buf, mac_with_port_uri);
try std.testing.expectEqualStrings("12:34:56:78:90:12", mac_with_port_actual); try std.testing.expectEqualStrings("12:34:56:78:90:12", mac_with_port_actual);
} }
@ -120,7 +123,7 @@ test "bufPrintHostnameFromFileUri returns only hostname when there is a port com
test "bufPrintHostnameFromFileUri returns NoHostnameInUri error when hostname is missing from uri" { test "bufPrintHostnameFromFileUri returns NoHostnameInUri error when hostname is missing from uri" {
const uri = try std.Uri.parse("file:///"); const uri = try std.Uri.parse("file:///");
var buf: [posix.HOST_NAME_MAX]u8 = undefined; var buf: [HOST_NAME_MAX]u8 = undefined;
const actual = bufPrintHostnameFromFileUri(&buf, uri); const actual = bufPrintHostnameFromFileUri(&buf, uri);
try std.testing.expectError(HostnameParsingError.NoHostnameInUri, actual); try std.testing.expectError(HostnameParsingError.NoHostnameInUri, actual);
} }
@ -138,7 +141,7 @@ test "isLocalHostname returns true when provided hostname is localhost" {
} }
test "isLocalHostname returns true when hostname is local" { test "isLocalHostname returns true when hostname is local" {
var buf: [posix.HOST_NAME_MAX]u8 = undefined; var buf: [HOST_NAME_MAX]u8 = undefined;
const localHostname = try posix.gethostname(&buf); const localHostname = try posix.gethostname(&buf);
try std.testing.expect(try isLocalHostname(localHostname)); try std.testing.expect(try isLocalHostname(localHostname));
} }

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");

View File

@ -18,6 +18,7 @@ pub const winsize = extern struct {
pub const Pty = switch (builtin.os.tag) { pub const Pty = switch (builtin.os.tag) {
.windows => WindowsPty, .windows => WindowsPty,
.ios => NullPty, .ios => NullPty,
.freestanding, .wasi => NullPty,
else => PosixPty, else => PosixPty,
}; };
@ -41,7 +42,7 @@ pub const Mode = packed struct {
// a termio that doesn't use a pty. This isn't used in any user-facing // a termio that doesn't use a pty. This isn't used in any user-facing
// artifacts, this is just a stopgap to get compilation to work on iOS. // artifacts, this is just a stopgap to get compilation to work on iOS.
const NullPty = struct { const NullPty = struct {
pub const Fd = posix.fd_t; pub const Fd = i32;
master: Fd, master: Fd,
slave: Fd, slave: Fd,

View File

@ -44,7 +44,7 @@ pub const Impl = enum {
) Impl { ) Impl {
if (target.cpu.arch == .wasm32) { if (target.cpu.arch == .wasm32) {
return switch (wasm_target) { return switch (wasm_target) {
.browser => .webgl, .browser => .opengl,
}; };
} }

View File

@ -460,6 +460,9 @@ pub fn surfaceInit(surface: *apprt.Surface) !void {
// to compile for OpenGL targets but libghostty is strictly // to compile for OpenGL targets but libghostty is strictly
// broken for rendering on this platforms. // broken for rendering on this platforms.
}, },
apprt.browser => {
gl.glad.context.init();
},
} }
// These are very noisy so this is commented, but easy to uncomment // These are very noisy so this is commented, but easy to uncomment
@ -574,6 +577,10 @@ pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void {
// to compile for OpenGL targets but libghostty is strictly // to compile for OpenGL targets but libghostty is strictly
// broken for rendering on this platforms. // broken for rendering on this platforms.
}, },
apprt.browser => {
log.err("context init", .{});
gl.glad.context.init();
},
} }
} }
@ -597,6 +604,7 @@ pub fn threadExit(self: *const OpenGL) void {
apprt.embedded => { apprt.embedded => {
// TODO: see threadEnter // TODO: see threadEnter
}, },
apprt.browser => {},
} }
} }
@ -675,6 +683,7 @@ pub fn updateFrame(
) !void { ) !void {
_ = surface; _ = surface;
std.log.err("update frame", .{});
// Data we extract out of the critical area. // Data we extract out of the critical area.
const Critical = struct { const Critical = struct {
full_rebuild: bool, full_rebuild: bool,
@ -693,6 +702,7 @@ pub fn updateFrame(
state.mutex.lock(); state.mutex.lock();
defer state.mutex.unlock(); defer state.mutex.unlock();
std.log.err("critical", .{});
// If we're in a synchronized output state, we pause all rendering. // If we're in a synchronized output state, we pause all rendering.
if (state.terminal.modes.get(.synchronized_output)) { if (state.terminal.modes.get(.synchronized_output)) {
@ -2219,7 +2229,14 @@ fn flushAtlasSingle(
/// the cells. /// the cells.
pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void { pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
// If we're in single-threaded more we grab a lock since we use shared data. // If we're in single-threaded more we grab a lock since we use shared data.
if (single_threaded_draw) self.draw_mutex.lock(); // wasm can't lock a mutex on the main thread because it uses Atomic.wait
if (single_threaded_draw) {
if (builtin.cpu.arch == .wasm32) {
if (!self.draw_mutex.tryLock()) return;
} else {
self.draw_mutex.lock();
}
}
defer if (single_threaded_draw) self.draw_mutex.unlock(); defer if (single_threaded_draw) self.draw_mutex.unlock();
const gl_state: *GLState = if (self.gl_state) |*v| v else return; const gl_state: *GLState = if (self.gl_state) |*v| v else return;
@ -2278,6 +2295,7 @@ pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
apprt.glfw => surface.window.swapBuffers(), apprt.glfw => surface.window.swapBuffers(),
apprt.gtk => {}, apprt.gtk => {},
apprt.embedded => {}, apprt.embedded => {},
apprt.browser => {},
else => @compileError("unsupported runtime"), else => @compileError("unsupported runtime"),
} }
} }
@ -2473,10 +2491,10 @@ fn drawCells(
// Our allocated buffer on the GPU is smaller than our capacity. // Our allocated buffer on the GPU is smaller than our capacity.
// We reallocate a new buffer with the full new capacity. // We reallocate a new buffer with the full new capacity.
if (self.gl_cells_size < cells.capacity) { if (self.gl_cells_size < cells.capacity) {
log.info("reallocating GPU buffer old={} new={}", .{ // log.info("reallocating GPU buffer old={} new={}", .{
self.gl_cells_size, // self.gl_cells_size,
cells.capacity, // cells.capacity,
}); // });
try bind.vbo.setDataNullManual( try bind.vbo.setDataNullManual(
@sizeOf(CellProgram.Cell) * cells.capacity, @sizeOf(CellProgram.Cell) * cells.capacity,
@ -2526,7 +2544,7 @@ const GLState = struct {
const arena_alloc = arena.allocator(); const arena_alloc = arena.allocator();
// Load our custom shaders // Load our custom shaders
const custom_state: ?custom.State = custom: { const custom_state: ?custom.State = if (builtin.cpu.arch == .wasm32) null else custom: {
const shaders: []const [:0]const u8 = shadertoy.loadFromFiles( const shaders: []const [:0]const u8 = shadertoy.loadFromFiles(
arena_alloc, arena_alloc,
config.custom_shaders, config.custom_shaders,

View File

@ -232,7 +232,7 @@ fn threadMain_(self: *Thread) !void {
self.startDrawTimer(); self.startDrawTimer();
// Run // Run
log.debug("starting renderer thread", .{}); log.err("starting renderer thread", .{});
defer log.debug("starting renderer thread shutdown", .{}); defer log.debug("starting renderer thread shutdown", .{});
_ = try self.loop.run(.until_done); _ = try self.loop.run(.until_done);
} }
@ -437,6 +437,7 @@ fn wakeupCallback(
}; };
const t = self_.?; const t = self_.?;
log.err("wakeup", .{});
// When we wake up, we check the mailbox. Mailbox producers should // When we wake up, we check the mailbox. Mailbox producers should
// wake up our thread after publishing. // wake up our thread after publishing.

View File

@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;
const gl = @import("opengl"); const gl = @import("opengl");
const wuffs = @import("wuffs"); const wuffs = @import("wuffs");
const internal_os = @import("../../os/main.zig");
/// Represents a single image placement on the grid. A placement is a /// Represents a single image placement on the grid. A placement is a
/// request to render an instance of an image. /// request to render an instance of an image.

View File

@ -1,27 +1,27 @@
#version 330 core #version 300 es
in vec2 glyph_tex_coords; in mediump vec2 glyph_tex_coords;
flat in uint mode; flat in uint mode;
// The color for this cell. If this is a background pass this is the // The color for this cell. If this is a background pass this is the
// background color. Otherwise, this is the foreground color. // background color. Otherwise, this is the foreground color.
flat in vec4 color; flat in mediump vec4 color;
// The position of the cells top-left corner. // The position of the cells top-left corner.
flat in vec2 screen_cell_pos; flat in mediump vec2 screen_cell_pos;
// Position the fragment coordinate to the upper left // Position the fragment coordinate to the upper left
layout(origin_upper_left) in vec4 gl_FragCoord; // layout(origin_upper_left) in vec4 gl_FragCoord;
// Must declare this output for some versions of OpenGL. // Must declare this output for some versions of OpenGL.
layout(location = 0) out vec4 out_FragColor; out mediump vec4 out_FragColor;
// Font texture // Font texture
uniform sampler2D text; uniform sampler2D text;
uniform sampler2D text_color; uniform sampler2D text_color;
// Dimensions of the cell // Dimensions of the cell
uniform vec2 cell_size; uniform highp vec2 cell_size;
// See vertex shader // See vertex shader
const uint MODE_BG = 1u; const uint MODE_BG = 1u;
@ -31,7 +31,7 @@ const uint MODE_FG_COLOR = 7u;
const uint MODE_FG_POWERLINE = 15u; const uint MODE_FG_POWERLINE = 15u;
void main() { void main() {
float a; highp float a;
switch (mode) { switch (mode) {
case MODE_BG: case MODE_BG:
@ -42,7 +42,7 @@ void main() {
case MODE_FG_CONSTRAINED: case MODE_FG_CONSTRAINED:
case MODE_FG_POWERLINE: case MODE_FG_POWERLINE:
a = texture(text, glyph_tex_coords).r; a = texture(text, glyph_tex_coords).r;
vec3 premult = color.rgb * color.a; highp vec3 premult = color.rgb * color.a;
out_FragColor = vec4(premult.rgb*a, a); out_FragColor = vec4(premult.rgb*a, a);
break; break;

View File

@ -1,4 +1,4 @@
#version 330 core #version 300 es
// These are the possible modes that "mode" can be set to. This is // These are the possible modes that "mode" can be set to. This is
// used to multiplex multiple render modes into a single shader. // used to multiplex multiple render modes into a single shader.
@ -11,33 +11,33 @@ const uint MODE_FG_COLOR = 7u;
const uint MODE_FG_POWERLINE = 15u; const uint MODE_FG_POWERLINE = 15u;
// The grid coordinates (x, y) where x < columns and y < rows // The grid coordinates (x, y) where x < columns and y < rows
layout (location = 0) in vec2 grid_coord; in vec2 grid_coord;
// Position of the glyph in the texture. // Position of the glyph in the texture.
layout (location = 1) in vec2 glyph_pos; in vec2 glyph_pos;
// Width/height of the glyph // Width/height of the glyph
layout (location = 2) in vec2 glyph_size; in vec2 glyph_size;
// Offset of the top-left corner of the glyph when rendered in a rect. // Offset of the top-left corner of the glyph when rendered in a rect.
layout (location = 3) in vec2 glyph_offset; in vec2 glyph_offset;
// The color for this cell in RGBA (0 to 1.0). Background or foreground // The color for this cell in RGBA (0 to 1.0). Background or foreground
// depends on mode. // depends on mode.
layout (location = 4) in vec4 color_in; in vec4 color_in;
// Only set for MODE_FG, this is the background color of the FG text. // Only set for MODE_FG, this is the background color of the FG text.
// This is used to detect minimal contrast for the text. // This is used to detect minimal contrast for the text.
layout (location = 5) in vec4 bg_color_in; in vec4 bg_color_in;
// The mode of this shader. The mode determines what fields are used, // The mode of this shader. The mode determines what fields are used,
// what the output will be, etc. This shader is capable of executing in // what the output will be, etc. This shader is capable of executing in
// multiple "modes" so that we can share some logic and so that we can draw // multiple "modes" so that we can share some logic and so that we can draw
// the entire terminal grid in a single GPU pass. // the entire terminal grid in a single GPU pass.
layout (location = 6) in uint mode_in; in uint mode_in;
// The width in cells of this item. // The width in cells of this item.
layout (location = 7) in uint grid_width; in uint grid_width;
// The background or foreground color for the fragment, depending on // The background or foreground color for the fragment, depending on
// whether this is a background or foreground pass. // whether this is a background or foreground pass.
@ -168,22 +168,22 @@ void main() {
// Scaled for wide chars // Scaled for wide chars
vec2 cell_size_scaled = cell_size; vec2 cell_size_scaled = cell_size;
cell_size_scaled.x = cell_size_scaled.x * grid_width; cell_size_scaled.x = cell_size_scaled.x * float(grid_width);
switch (mode) { switch (mode) {
case MODE_BG: case MODE_BG:
// If we're at the edge of the grid, we add our padding to the background // If we're at the edge of the grid, we add our padding to the background
// to extend it. Note: grid_padding is top/right/bottom/left. // to extend it. Note: grid_padding is top/right/bottom/left.
if (grid_coord.y == 0 && padding_vertical_top) { if (grid_coord.y == 0. && padding_vertical_top) {
cell_pos.y -= grid_padding.r; cell_pos.y -= grid_padding.r;
cell_size_scaled.y += grid_padding.r; cell_size_scaled.y += grid_padding.r;
} else if (grid_coord.y == grid_size.y - 1 && padding_vertical_bottom) { } else if (grid_coord.y == grid_size.y - 1. && padding_vertical_bottom) {
cell_size_scaled.y += grid_padding.b; cell_size_scaled.y += grid_padding.b;
} }
if (grid_coord.x == 0) { if (grid_coord.x == 0.) {
cell_pos.x -= grid_padding.a; cell_pos.x -= grid_padding.a;
cell_size_scaled.x += grid_padding.a; cell_size_scaled.x += grid_padding.a;
} else if (grid_coord.x == grid_size.x - 1) { } else if (grid_coord.x == grid_size.x - 1.) {
cell_size_scaled.x += grid_padding.g; cell_size_scaled.x += grid_padding.g;
} }
@ -205,7 +205,7 @@ void main() {
// The glyph_offset.y is the y bearing, a y value that when added // The glyph_offset.y is the y bearing, a y value that when added
// to the baseline is the offset (+y is up). Our grid goes down. // to the baseline is the offset (+y is up). Our grid goes down.
// So we flip it with `cell_size.y - glyph_offset.y`. // So we flip it with `cell_size.y - glyph_offset.y`.
glyph_offset_calc.y = cell_size_scaled.y - glyph_offset_calc.y; glyph_offset_calc.y = -glyph_offset_calc.y;
// If this is a constrained mode, we need to constrain it! // If this is a constrained mode, we need to constrain it!
// We also always constrain colored glyphs since we should have // We also always constrain colored glyphs since we should have
@ -214,7 +214,7 @@ void main() {
if (mode == MODE_FG_CONSTRAINED || mode == MODE_FG_COLOR) { if (mode == MODE_FG_CONSTRAINED || mode == MODE_FG_COLOR) {
if (glyph_size.x > cell_size_scaled.x) { if (glyph_size.x > cell_size_scaled.x) {
float new_y = glyph_size.y * (cell_size_scaled.x / glyph_size.x); float new_y = glyph_size.y * (cell_size_scaled.x / glyph_size.x);
glyph_offset_calc.y = glyph_offset_calc.y + ((glyph_size.y - new_y) / 2); glyph_offset_calc.y = glyph_offset_calc.y + ((glyph_size.y - new_y) / 2.);
glyph_size_calc.y = new_y; glyph_size_calc.y = new_y;
glyph_size_calc.x = cell_size_scaled.x; glyph_size_calc.x = cell_size_scaled.x;
} }
@ -238,8 +238,8 @@ void main() {
text_size = textureSize(text_color, 0); text_size = textureSize(text_color, 0);
break; break;
} }
vec2 glyph_tex_pos = glyph_pos / text_size; vec2 glyph_tex_pos = glyph_pos / vec2(text_size);
vec2 glyph_tex_size = glyph_size / text_size; vec2 glyph_tex_size = glyph_size / vec2(text_size);
glyph_tex_coords = glyph_tex_pos + glyph_tex_size * position; glyph_tex_coords = glyph_tex_pos + glyph_tex_size * position;
// If we have a minimum contrast, we need to check if we need to // If we have a minimum contrast, we need to check if we need to

View File

@ -1,4 +1,4 @@
#version 330 core #version 300 es
void main(){ void main(){
vec2 position; vec2 position;

View File

@ -1,12 +1,12 @@
#version 330 core #version 300 es
in vec2 tex_coord; in mediump vec2 tex_coord;
layout(location = 0) out vec4 out_FragColor; out mediump vec4 out_FragColor;
uniform sampler2D image; uniform sampler2D image;
void main() { void main() {
vec4 color = texture(image, tex_coord); mediump vec4 color = texture(image, tex_coord);
out_FragColor = vec4(color.rgb * color.a, color.a); out_FragColor = vec4(color.rgb * color.a, color.a);
} }

View File

@ -1,9 +1,9 @@
#version 330 core #version 300 es
layout (location = 0) in vec2 grid_pos; in vec2 grid_pos;
layout (location = 1) in vec2 cell_offset; in vec2 cell_offset;
layout (location = 2) in vec4 source_rect; in vec4 source_rect;
layout (location = 3) in vec2 dest_size; in vec2 dest_size;
out vec2 tex_coord; out vec2 tex_coord;
@ -13,7 +13,7 @@ uniform mat4 projection;
void main() { void main() {
// The size of the image in pixels // The size of the image in pixels
vec2 image_size = textureSize(image, 0); vec2 image_size = vec2(textureSize(image, 0));
// Turn the cell position into a vertex point depending on the // Turn the cell position into a vertex point depending on the
// gl_VertexID. Since we use instanced drawing, we have 4 vertices // gl_VertexID. Since we use instanced drawing, we have 4 vertices

View File

@ -328,8 +328,14 @@ fn doAction(self: *Parser, action: TransitionAction, c: u8) ?Action {
} }
// A numeric value. Add it to our accumulator. // A numeric value. Add it to our accumulator.
if (self.param_acc_idx > 0) { if (builtin.cpu.arch == .wasm32) {
self.param_acc *|= 10; if (self.param_acc_idx > 0) {
self.param_acc *= 10;
}
} else {
if (self.param_acc_idx > 0) {
self.param_acc *|= 10;
}
} }
self.param_acc +|= c - '0'; self.param_acc +|= c - '0';

View File

@ -108,7 +108,7 @@ pub const LoadingImage = struct {
path: []const u8, path: []const u8,
) !void { ) !void {
// windows is currently unsupported, does it support shm? // windows is currently unsupported, does it support shm?
if (comptime builtin.target.os.tag == .windows) { if (comptime builtin.target.os.tag == .windows or builtin.cpu.arch == .wasm32) {
return error.UnsupportedMedium; return error.UnsupportedMedium;
} }

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");

View File

@ -11,6 +11,7 @@ const assert = std.debug.assert;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const RGB = @import("color.zig").RGB; const RGB = @import("color.zig").RGB;
const kitty = @import("kitty.zig"); const kitty = @import("kitty.zig");
const builtin = @import("builtin");
const log = std.log.scoped(.osc); const log = std.log.scoped(.osc);
@ -871,7 +872,11 @@ pub const Parser = struct {
self.complete = true; self.complete = true;
const idx = self.buf_idx - self.buf_start; const idx = self.buf_idx - self.buf_start;
if (idx > 0) self.temp_state.num *|= 10; if (builtin.cpu.arch == .wasm32) {
if (idx > 0) self.temp_state.num *= 10;
} else {
if (idx > 0) self.temp_state.num *|= 10;
}
self.temp_state.num +|= c - '0'; self.temp_state.num +|= c - '0';
}, },
';' => { ';' => {

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

@ -13,6 +13,7 @@ const osc = @import("osc.zig");
const sgr = @import("sgr.zig"); const sgr = @import("sgr.zig");
const UTF8Decoder = @import("UTF8Decoder.zig"); const UTF8Decoder = @import("UTF8Decoder.zig");
const MouseShape = @import("mouse_shape.zig").MouseShape; const MouseShape = @import("mouse_shape.zig").MouseShape;
const builtin = @import("builtin");
const log = std.log.scoped(.stream); const log = std.log.scoped(.stream);
@ -242,8 +243,13 @@ pub fn Stream(comptime Handler: type) type {
0x18, 0x1A => self.parser.state = .ground, 0x18, 0x1A => self.parser.state = .ground,
// A parameter digit: // A parameter digit:
'0'...'9' => if (self.parser.params_idx < 16) { '0'...'9' => if (self.parser.params_idx < 16) {
self.parser.param_acc *|= 10; if (builtin.cpu.arch == .wasm32) {
self.parser.param_acc +|= c - '0'; self.parser.param_acc *= 10;
self.parser.param_acc += c - '0';
} else {
self.parser.param_acc *|= 10;
self.parser.param_acc +|= c - '0';
}
// The parser's CSI param action uses param_acc_idx // The parser's CSI param action uses param_acc_idx
// to decide if there's a final param that needs to // to decide if there's a final param that needs to
// be consumed or not, but it doesn't matter really // be consumed or not, but it doesn't matter really

View File

@ -126,7 +126,7 @@ pub fn threadEnter(
// Start our read thread // Start our read thread
const read_thread = try std.Thread.spawn( const read_thread = try std.Thread.spawn(
.{}, .{ .allocator = alloc },
if (builtin.os.tag == .windows) ReadThread.threadMainWindows else ReadThread.threadMainPosix, if (builtin.os.tag == .windows) ReadThread.threadMainWindows else ReadThread.threadMainPosix,
.{ pty_fds.read, io, pipe[0] }, .{ pty_fds.read, io, pipe[0] },
); );
@ -686,11 +686,18 @@ const Subprocess = struct {
/// a potential execution on the host. /// a potential execution on the host.
const FlatpakHostCommand = if (build_config.flatpak) internal_os.FlatpakHostCommand else void; const FlatpakHostCommand = if (build_config.flatpak) internal_os.FlatpakHostCommand else void;
const c = @cImport({ const c = if (builtin.cpu.arch != .wasm32) @cImport({
@cInclude("errno.h"); @cInclude("errno.h");
@cInclude("signal.h"); @cInclude("signal.h");
@cInclude("unistd.h"); @cInclude("unistd.h");
}); }) else struct {
pub const pid_t = Command.PidT;
pub const ESRCH = 0x03;
pub fn getpgid(pid: pid_t) pid_t {
_ = pid;
return 100;
}
};
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
cwd: ?[]const u8, cwd: ?[]const u8,
@ -744,7 +751,7 @@ const Subprocess = struct {
// Assume that the resources directory is adjacent to the terminfo // Assume that the resources directory is adjacent to the terminfo
// database // database
var buf: [std.fs.max_path_bytes]u8 = undefined; var buf: [if (builtin.cpu.arch == .wasm32) 4096 else std.fs.max_path_bytes]u8 = undefined;
const dir = try std.fmt.bufPrint(&buf, "{s}/terminfo", .{ const dir = try std.fmt.bufPrint(&buf, "{s}/terminfo", .{
std.fs.path.dirname(base) orelse unreachable, std.fs.path.dirname(base) orelse unreachable,
}); });
@ -762,8 +769,8 @@ const Subprocess = struct {
// Add our binary to the path if we can find it. // Add our binary to the path if we can find it.
ghostty_path: { ghostty_path: {
var exe_buf: [std.fs.max_path_bytes]u8 = undefined; var exe_buf: [if (builtin.cpu.arch == .wasm32) 4096 else std.fs.max_path_bytes]u8 = undefined;
const exe_bin_path = std.fs.selfExePath(&exe_buf) catch |err| { const exe_bin_path = if (builtin.cpu.arch == .wasm32) "ghostty-wasm.wasm" else std.fs.selfExePath(&exe_buf) catch |err| {
log.warn("failed to get ghostty exe path err={}", .{err}); log.warn("failed to get ghostty exe path err={}", .{err});
break :ghostty_path; break :ghostty_path;
}; };
@ -848,6 +855,7 @@ const Subprocess = struct {
// Setup our shell integration, if we can. // Setup our shell integration, if we can.
const integrated_shell: ?shell_integration.Shell, const shell_command: []const u8 = shell: { const integrated_shell: ?shell_integration.Shell, const shell_command: []const u8 = shell: {
if (builtin.cpu.arch == .wasm32) break :shell .{ null, "" };
const default_shell_command = cfg.command orelse switch (builtin.os.tag) { const default_shell_command = cfg.command orelse switch (builtin.os.tag) {
.windows => "cmd.exe", .windows => "cmd.exe",
else => "sh", else => "sh",
@ -1247,6 +1255,7 @@ const Subprocess = struct {
_ = try command.wait(false); _ = try command.wait(false);
}, },
.freestanding, .wasi => {},
else => if (getpgid(pid)) |pgid| { else => if (getpgid(pid)) |pgid| {
// It is possible to send a killpg between the time that // It is possible to send a killpg between the time that
@ -1415,6 +1424,7 @@ pub const ReadThread = struct {
// child process dies. To be safe, we just break the loop // child process dies. To be safe, we just break the loop
// and let our poll happen. // and let our poll happen.
if (n == 0) break; if (n == 0) break;
std.log.err("{} {} {}", .{ buf[0], n, @intFromPtr(&buf) });
// log.info("DATA: {d}", .{n}); // log.info("DATA: {d}", .{n});
@call(.always_inline, termio.Termio.processOutput, .{ io, buf[0..n] }); @call(.always_inline, termio.Termio.processOutput, .{ io, buf[0..n] });

View File

@ -554,6 +554,7 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
// use a timer under the covers // use a timer under the covers
if (std.time.Instant.now()) |now| cursor_reset: { if (std.time.Instant.now()) |now| cursor_reset: {
if (self.last_cursor_reset) |last| { if (self.last_cursor_reset) |last| {
log.err("now: {} last: {}", .{ now, 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;
} }
@ -571,18 +572,24 @@ fn processOutputLocked(self: *Termio, buf: []const u8) void {
// process a byte at a time alternating between the inspector handler // process a byte at a time alternating between the inspector handler
// and the termio handler. This is very slow compared to our optimizations // and the termio handler. This is very slow compared to our optimizations
// below but at least users only pay for it if they're using the inspector. // below but at least users only pay for it if they're using the inspector.
if (self.renderer_state.inspector) |insp| { std.log.err("to print {s}", .{buf});
for (buf, 0..) |byte, i| { if (builtin.cpu.arch == .wasm32) {
insp.recordPtyRead(buf[i .. i + 1]) catch |err| {
log.err("error recording pty read in inspector err={}", .{err});
};
self.terminal_stream.next(byte) catch |err|
log.err("error processing terminal data: {}", .{err});
}
} else {
self.terminal_stream.nextSlice(buf) catch |err| self.terminal_stream.nextSlice(buf) catch |err|
log.err("error processing terminal data: {}", .{err}); log.err("error processing terminal data: {}", .{err});
} else {
if (self.renderer_state.inspector) |insp| {
for (buf, 0..) |byte, i| {
insp.recordPtyRead(buf[i .. i + 1]) catch |err| {
log.err("error recording pty read in inspector err={}", .{err});
};
self.terminal_stream.next(byte) catch |err|
log.err("error processing terminal data: {}", .{err});
}
} else {
self.terminal_stream.nextSlice(buf) catch |err|
log.err("error processing terminal data: {}", .{err});
}
} }
// If our stream handling caused messages to be sent to the mailbox // If our stream handling caused messages to be sent to the mailbox

View File

@ -1102,7 +1102,7 @@ pub const StreamHandler = struct {
// See https://www.rfc-editor.org/rfc/rfc793#section-3.1. // See https://www.rfc-editor.org/rfc/rfc793#section-3.1.
const PORT_NUMBER_MAX_DIGITS = 5; const PORT_NUMBER_MAX_DIGITS = 5;
// Make sure there is space for a max length hostname + the max number of digits. // Make sure there is space for a max length hostname + the max number of digits.
var host_and_port_buf: [posix.HOST_NAME_MAX + PORT_NUMBER_MAX_DIGITS]u8 = undefined; var host_and_port_buf: [(if (builtin.cpu.arch == .wasm32) 64 else posix.HOST_NAME_MAX) + PORT_NUMBER_MAX_DIGITS]u8 = undefined;
const hostname_from_uri = internal_os.hostname.bufPrintHostnameFromFileUri( const hostname_from_uri = internal_os.hostname.bufPrintHostnameFromFileUri(
&host_and_port_buf, &host_and_port_buf,
uri, uri,