feat(wasm): port Ghostty's wasm32 build
Ports main_wasm.zig as crates/ghostty-wasm (cdylib exporting the Zig names: atlas_*, face_*, deferred_face_*, shaper_*, config_*), the web_canvas font face and shaper (backend selected on wasm32), os/wasm (logging, target, JS interop) and config/Wasm.zig. C-library dependencies (freetype, harfbuzz, fontconfig, onig, gettext, D-Bus, rustix) are target-gated. Like Zig's wasm build, link regex search (StringMap::search_iterator) is unavailable without Oniguruma; Collection/SharedGrid/harfbuzz are compiled out on wasm32. ghostty-os switches from forbid to deny unsafe_code for the JS import blocks. The JS import ABI is currently a documented handle-based interface with a reference host (js/ghostty_host.js); aligning it with zig-js's exact import names follows separately. Test Plan: - cargo build --target wasm32-unknown-unknown -p ghostty-font \ -p ghostty-config -p ghostty-terminal -p ghostty-vt -p ghostty-wasm - cargo test -p ghostty-wasm (node runs the module against a fake canvas)
This commit is contained in:
1 parent
75252a7651
commit
42f05c926d
33 files changed
+2866
-30
No files matched your search
@@ -0,0 +1,86 @@
|
||||
//! The Ghostty WASM module. Port of `ZIG/src/main_wasm.zig`.
|
||||
//!
|
||||
//! The module exports a C ABI. Like Zig's `main_wasm.zig` it pulls in
|
||||
//! `os/wasm` (logging, JS interop), `font` (atlas, deferred face, canvas face,
|
||||
//! shaper), `terminal` and the config/app wasm APIs:
|
||||
//!
|
||||
//! - module `atlas`, `font/Atlas.zig` `Wasm` (`atlas_*`)
|
||||
//! - module `face`, `font/face/web_canvas.zig` `Wasm` (`face_*`) and
|
||||
//! `font/DeferredFace.zig` `Wasm` (`deferred_face_*`)
|
||||
//! - module `shaper`, `font/shaper/web_canvas.zig` `Wasm` (`shaper_*`)
|
||||
//! - module `config`, `config/Wasm.zig` (`config_*`)
|
||||
//! - module `app`, `App.zig` `Wasm` (commented out upstream, nothing exported)
|
||||
//!
|
||||
//! The host provides the `ghostty_js` imports (see
|
||||
//! `ghostty_os::wasm::js`) and `env.log`. Strings are passed as
|
||||
//! pointer+length pairs into wasm memory allocated by the host with
|
||||
//! [`wasm_alloc`] / released with [`wasm_free`].
|
||||
//!
|
||||
//! The crate is empty on non-wasm targets so the native workspace builds.
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
// The C ABI needs raw pointers; every unsafe block has a SAFETY comment.
|
||||
#![allow(unsafe_code)]
|
||||
// Exports are `extern "C"` taking raw pointers by design.
|
||||
#![allow(clippy::not_unsafe_ptr_arg_deref, clippy::missing_safety_doc)]
|
||||
|
||||
pub mod app;
|
||||
pub mod atlas;
|
||||
pub mod config;
|
||||
pub mod face;
|
||||
pub mod shaper;
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
/// Installs the logger and panic hook (Zig: `std_options.logFn`). Called by
|
||||
/// every constructor export so the host does not need a separate start call;
|
||||
/// also exported for hosts that want logging before the first constructor.
|
||||
pub(crate) fn ensure_init() {
|
||||
INIT.call_once(|| {
|
||||
ghostty_os::wasm::log::init();
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
ghostty_os::wasm::log::host_log(&format!("error(panic): {info}"));
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn ghostty_wasm_init() {
|
||||
ensure_init();
|
||||
}
|
||||
|
||||
/// Allocates `len` bytes in wasm memory for the host to write into (strings,
|
||||
/// bitmaps). Release with [`wasm_free`]. Returns null on `len == 0`.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn wasm_alloc(len: usize) -> *mut u8 {
|
||||
if len == 0 {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
let mut v = vec![0u8; len].into_boxed_slice();
|
||||
let p = v.as_mut_ptr();
|
||||
std::mem::forget(v);
|
||||
p
|
||||
}
|
||||
|
||||
/// Releases memory from [`wasm_alloc`].
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn wasm_free(ptr: *mut u8, len: usize) {
|
||||
if ptr.is_null() || len == 0 {
|
||||
return;
|
||||
}
|
||||
// SAFETY: `ptr`/`len` came from `wasm_alloc` (boxed slice of `len` bytes).
|
||||
drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len)) });
|
||||
}
|
||||
|
||||
/// Borrows a host string (`ptr`, `len`) as bytes.
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must be valid for `len` bytes (or `len == 0`).
|
||||
pub(crate) unsafe fn host_bytes<'a>(ptr: *const u8, len: usize) -> &'a [u8] {
|
||||
if len == 0 {
|
||||
return &[];
|
||||
}
|
||||
// SAFETY: guaranteed by the caller.
|
||||
unsafe { std::slice::from_raw_parts(ptr, len) }
|
||||
}
|
||||
Reference in new issue
Block a user