mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-30 12:27:57 +00:00
lib-vt: wasm convenience functions and a simple example (#9309)
This adds a set of Wasm convenience functions to ease memory management. These are all prefixed with `ghostty_wasm` and are documented as part of the standard Doxygen docs. I also added a very simple single-page HTML example that demonstrates how to use the Wasm module for key encoding. This also adds a bunch of safety checks to the C API to verify that valid values are actually passed to the function. This is an easy to hit bug. **AI disclosure:** The example is AI-written with Amp. I read through all the code and understand it but I can't claim there isn't a better way, I'm far from a JS expert. It is simple and works currently though. Happy to see improvements if anyone wants to contribute.
This commit is contained in:
committed by
GitHub
parent
9dc2e5978f
commit
c133fac7e7
@@ -77,7 +77,7 @@ pub fn encode(
|
||||
event: key.KeyEvent,
|
||||
opts: Options,
|
||||
) std.Io.Writer.Error!void {
|
||||
//std.log.warn("KEYENCODER event={} opts={}", .{ event, opts });
|
||||
std.log.warn("KEYENCODER event={} opts={}", .{ event, opts });
|
||||
return if (opts.kitty_flags.int() != 0) try kitty(
|
||||
writer,
|
||||
event,
|
||||
|
||||
@@ -2,6 +2,9 @@ const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const testing = std.testing;
|
||||
|
||||
/// Convenience functions
|
||||
pub const convenience = @import("allocator/convenience.zig");
|
||||
|
||||
/// Useful alias since they're required to create Zig allocators
|
||||
pub const ZigVTable = std.mem.Allocator.VTable;
|
||||
|
||||
|
||||
50
src/lib/allocator/convenience.zig
Normal file
50
src/lib/allocator/convenience.zig
Normal file
@@ -0,0 +1,50 @@
|
||||
//! This contains convenience functions for allocating various types.
|
||||
//!
|
||||
//! The primary use case for this is Wasm builds. Ghostty relies a lot on
|
||||
//! pointers to various types for ABI compatibility and creating those pointers
|
||||
//! in Wasm is tedious. This file contains a purely additive set of functions
|
||||
//! that can be exposed to the Wasm module without changing the API from the
|
||||
//! C library.
|
||||
//!
|
||||
//! Given these are convenience methods, they always use the default allocator.
|
||||
//! If a caller is using a custom allocator, they have the expertise to
|
||||
//! allocate these types manually using their custom allocator.
|
||||
|
||||
// Get our default allocator at comptime since it is known.
|
||||
const default = @import("../allocator.zig").default;
|
||||
const alloc = default(null);
|
||||
|
||||
pub const Opaque = *anyopaque;
|
||||
|
||||
pub fn allocOpaque() callconv(.c) ?*Opaque {
|
||||
return alloc.create(*anyopaque) catch return null;
|
||||
}
|
||||
|
||||
pub fn freeOpaque(ptr: ?*Opaque) callconv(.c) void {
|
||||
if (ptr) |p| alloc.destroy(p);
|
||||
}
|
||||
|
||||
pub fn allocBuffer(len: usize) callconv(.c) ?[*]u8 {
|
||||
const slice = alloc.alloc(u8, len) catch return null;
|
||||
return slice.ptr;
|
||||
}
|
||||
|
||||
pub fn freeBuffer(ptr: ?[*]u8, len: usize) callconv(.c) void {
|
||||
if (ptr) |p| alloc.free(p[0..len]);
|
||||
}
|
||||
|
||||
pub fn allocU8() callconv(.c) ?*u8 {
|
||||
return alloc.create(u8) catch return null;
|
||||
}
|
||||
|
||||
pub fn freeU8(ptr: ?*u8) callconv(.c) void {
|
||||
if (ptr) |p| alloc.destroy(p);
|
||||
}
|
||||
|
||||
pub fn allocUsize() callconv(.c) ?*usize {
|
||||
return alloc.create(usize) catch return null;
|
||||
}
|
||||
|
||||
pub fn freeUsize(ptr: ?*usize) callconv(.c) void {
|
||||
if (ptr) |p| alloc.destroy(p);
|
||||
}
|
||||
@@ -126,6 +126,19 @@ comptime {
|
||||
@export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" });
|
||||
@export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" });
|
||||
@export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" });
|
||||
|
||||
// On Wasm we need to export our allocator convenience functions.
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
const alloc = @import("lib/allocator/convenience.zig");
|
||||
@export(&alloc.allocOpaque, .{ .name = "ghostty_wasm_alloc_opaque" });
|
||||
@export(&alloc.freeOpaque, .{ .name = "ghostty_wasm_free_opaque" });
|
||||
@export(&alloc.allocBuffer, .{ .name = "ghostty_wasm_alloc_buffer" });
|
||||
@export(&alloc.freeBuffer, .{ .name = "ghostty_wasm_free_buffer" });
|
||||
@export(&alloc.allocU8, .{ .name = "ghostty_wasm_alloc_u8" });
|
||||
@export(&alloc.freeU8, .{ .name = "ghostty_wasm_free_u8" });
|
||||
@export(&alloc.allocUsize, .{ .name = "ghostty_wasm_alloc_usize" });
|
||||
@export(&alloc.freeUsize, .{ .name = "ghostty_wasm_free_usize" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ const OptionAsAlt = @import("../../input/config.zig").OptionAsAlt;
|
||||
const Result = @import("result.zig").Result;
|
||||
const KeyEvent = @import("key_event.zig").Event;
|
||||
|
||||
const log = std.log.scoped(.key_encode);
|
||||
|
||||
/// Wrapper around key encoding options that tracks the allocator for C API usage.
|
||||
const KeyEncoderWrapper = struct {
|
||||
opts: key_encode.Options,
|
||||
@@ -70,6 +72,13 @@ pub fn setopt(
|
||||
option: Option,
|
||||
value: ?*const anyopaque,
|
||||
) callconv(.c) void {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(Option, @intFromEnum(option)) catch {
|
||||
log.warn("setopt invalid option value={d}", .{@intFromEnum(option)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
return switch (option) {
|
||||
inline else => |comptime_option| setoptTyped(
|
||||
encoder_,
|
||||
@@ -95,7 +104,15 @@ fn setoptTyped(
|
||||
const bits: u5 = @truncate(value.*);
|
||||
break :flags @bitCast(bits);
|
||||
},
|
||||
.macos_option_as_alt => opts.macos_option_as_alt = value.*,
|
||||
.macos_option_as_alt => {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(OptionAsAlt, @intFromEnum(value.*)) catch {
|
||||
log.warn("setopt invalid OptionAsAlt value={d}", .{@intFromEnum(value.*)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
opts.macos_option_as_alt = value.*;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ const CAllocator = lib_alloc.Allocator;
|
||||
const key = @import("../../input/key.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
const log = std.log.scoped(.key_event);
|
||||
|
||||
/// Wrapper around KeyEvent that tracks the allocator for C API usage.
|
||||
/// The UTF-8 text is not owned by this wrapper - the caller is responsible
|
||||
/// for ensuring the lifetime of any UTF-8 text set via set_utf8.
|
||||
@@ -36,6 +38,13 @@ pub fn free(event_: Event) callconv(.c) void {
|
||||
}
|
||||
|
||||
pub fn set_action(event_: Event, action: key.Action) callconv(.c) void {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(key.Action, @intFromEnum(action)) catch {
|
||||
log.warn("set_action invalid action value={d}", .{@intFromEnum(action)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.action = action;
|
||||
}
|
||||
@@ -46,6 +55,13 @@ pub fn get_action(event_: Event) callconv(.c) key.Action {
|
||||
}
|
||||
|
||||
pub fn set_key(event_: Event, k: key.Key) callconv(.c) void {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(key.Key, @intFromEnum(k)) catch {
|
||||
log.warn("set_key invalid key value={d}", .{@intFromEnum(k)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.key = k;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ const CAllocator = lib_alloc.Allocator;
|
||||
const osc = @import("../osc.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
const log = std.log.scoped(.osc);
|
||||
|
||||
/// C: GhosttyOscParser
|
||||
pub const Parser = ?*osc.Parser;
|
||||
|
||||
@@ -68,6 +70,13 @@ pub fn commandData(
|
||||
data: CommandData,
|
||||
out: ?*anyopaque,
|
||||
) callconv(.c) bool {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(CommandData, @intFromEnum(data)) catch {
|
||||
log.warn("commandData invalid data value={d}", .{@intFromEnum(data)});
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
inline else => |comptime_data| commandDataTyped(
|
||||
command_,
|
||||
|
||||
Reference in New Issue
Block a user