diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index 064ee81dd..c7990336b 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -154,6 +154,19 @@ typedef void (*GhosttyTerminalWritePtyFn)(GhosttyTerminal terminal, const uint8_t* data, size_t len); +/** + * Callback function type for bell. + * + * Called when the terminal receives a BEL character (0x07). + * + * @param terminal The terminal handle + * @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA + * + * @ingroup terminal + */ +typedef void (*GhosttyTerminalBellFn)(GhosttyTerminal terminal, + void* userdata); + /** * Terminal option identifiers. * @@ -178,6 +191,14 @@ typedef enum { * Input type: GhosttyTerminalWritePtyFn* */ GHOSTTY_TERMINAL_OPT_WRITE_PTY = 1, + + /** + * Callback invoked when the terminal receives a BEL character + * (0x07). Set to NULL to ignore bell events. + * + * Input type: GhosttyTerminalBellFn* + */ + GHOSTTY_TERMINAL_OPT_BELL = 2, } GhosttyTerminalOption; /** diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 70997cc08..10e5a2960 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -20,9 +20,6 @@ const Handler = @import("../stream_terminal.zig").Handler; const log = std.log.scoped(.terminal_c); -/// C function pointer type for the write_pty callback. -pub const CWritePtyFn = *const fn (Terminal, ?*anyopaque, [*]const u8, usize) callconv(.c) void; - /// Wrapper around ZigTerminal that tracks additional state for C API usage, /// such as the persistent VT stream needed to handle escape sequences split /// across multiple vt_write calls. @@ -37,7 +34,14 @@ const TerminalWrapper = struct { /// no-op when the corresponding callback is null. const Effects = struct { userdata: ?*anyopaque = null, - write_pty: ?CWritePtyFn = null, + write_pty: ?WritePtyFn = null, + bell: ?BellFn = null, + + /// C function pointer type for the write_pty callback. + pub const WritePtyFn = *const fn (Terminal, ?*anyopaque, [*]const u8, usize) callconv(.c) void; + + /// C function pointer type for the bell callback. + pub const BellFn = *const fn (Terminal, ?*anyopaque) callconv(.c) void; fn writePtyTrampoline(handler: *Handler, data: [:0]const u8) void { const stream_ptr: *Stream = @fieldParentPtr("handler", handler); @@ -45,6 +49,13 @@ const Effects = struct { const func = wrapper.effects.write_pty orelse return; func(@ptrCast(wrapper), wrapper.effects.userdata, data.ptr, data.len); } + + fn bellTrampoline(handler: *Handler) void { + const stream_ptr: *Stream = @fieldParentPtr("handler", handler); + const wrapper: *TerminalWrapper = @fieldParentPtr("stream", stream_ptr); + const func = wrapper.effects.bell orelse return; + func(@ptrCast(wrapper), wrapper.effects.userdata); + } }; /// C: GhosttyTerminal @@ -105,6 +116,7 @@ fn new_( // setting C callbacks at any time takes effect immediately. var handler: Stream.Handler = t.vtHandler(); handler.effects.write_pty = &Effects.writePtyTrampoline; + handler.effects.bell = &Effects.bellTrampoline; wrapper.* = .{ .terminal = t, @@ -127,12 +139,14 @@ pub fn vt_write( pub const Option = enum(c_int) { userdata = 0, write_pty = 1, + bell = 2, /// Input type expected for setting the option. pub fn InType(comptime self: Option) type { return switch (self) { .userdata => ?*anyopaque, - .write_pty => ?CWritePtyFn, + .write_pty => ?Effects.WritePtyFn, + .bell => ?Effects.BellFn, }; } }; @@ -167,6 +181,7 @@ fn setTyped( switch (option) { .userdata => wrapper.effects.userdata = if (value) |v| v.* else null, .write_pty => wrapper.effects.write_pty = if (value) |v| v.* else null, + .bell => wrapper.effects.bell = if (value) |v| v.* else null, } }