diff --git a/example/c-vt-effects/src/main.c b/example/c-vt-effects/src/main.c index c6688cf5c..1e3a3d645 100644 --- a/example/c-vt-effects/src/main.c +++ b/example/c-vt-effects/src/main.c @@ -55,18 +55,15 @@ int main() { // Set up userdata — a simple bell counter int bell_count = 0; - void* ud = &bell_count; - ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_USERDATA, &ud); + ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_USERDATA, &bell_count); // Register effect callbacks - GhosttyTerminalWritePtyFn write_fn = on_write_pty; - ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_WRITE_PTY, &write_fn); - - GhosttyTerminalBellFn bell_fn = on_bell; - ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_BELL, &bell_fn); - - GhosttyTerminalTitleChangedFn title_fn = on_title_changed; - ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_TITLE_CHANGED, &title_fn); + ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_WRITE_PTY, + (const void *)on_write_pty); + ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_BELL, + (const void *)on_bell); + ghostty_terminal_set(terminal, GHOSTTY_TERMINAL_OPT_TITLE_CHANGED, + (const void *)on_title_changed); // Feed VT data that triggers effects: diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index 30f6179c8..f8bb3aa22 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -341,7 +341,7 @@ typedef enum { /** * Opaque userdata pointer passed to all callbacks. * - * Input type: void** + * Input type: void* */ GHOSTTY_TERMINAL_OPT_USERDATA = 0, @@ -350,7 +350,7 @@ typedef enum { * to the pty (e.g. in response to a DECRQM query or device * status report). Set to NULL to ignore such sequences. * - * Input type: GhosttyTerminalWritePtyFn* + * Input type: GhosttyTerminalWritePtyFn */ GHOSTTY_TERMINAL_OPT_WRITE_PTY = 1, @@ -358,7 +358,7 @@ typedef enum { * Callback invoked when the terminal receives a BEL character * (0x07). Set to NULL to ignore bell events. * - * Input type: GhosttyTerminalBellFn* + * Input type: GhosttyTerminalBellFn */ GHOSTTY_TERMINAL_OPT_BELL = 2, @@ -366,7 +366,7 @@ typedef enum { * Callback invoked when the terminal receives an ENQ character * (0x05). Set to NULL to send no response. * - * Input type: GhosttyTerminalEnquiryFn* + * Input type: GhosttyTerminalEnquiryFn */ GHOSTTY_TERMINAL_OPT_ENQUIRY = 3, @@ -374,7 +374,7 @@ typedef enum { * Callback invoked when the terminal receives an XTVERSION query * (CSI > q). Set to NULL to report the default "libghostty" string. * - * Input type: GhosttyTerminalXtversionFn* + * Input type: GhosttyTerminalXtversionFn */ GHOSTTY_TERMINAL_OPT_XTVERSION = 4, @@ -383,7 +383,7 @@ typedef enum { * sequences (e.g. OSC 0 or OSC 2). Set to NULL to ignore title * change events. * - * Input type: GhosttyTerminalTitleChangedFn* + * Input type: GhosttyTerminalTitleChangedFn */ GHOSTTY_TERMINAL_OPT_TITLE_CHANGED = 5, @@ -391,7 +391,7 @@ typedef enum { * Callback invoked in response to XTWINOPS size queries * (CSI 14/16/18 t). Set to NULL to silently ignore size queries. * - * Input type: GhosttyTerminalSizeFn* + * Input type: GhosttyTerminalSizeFn */ GHOSTTY_TERMINAL_OPT_SIZE = 6, @@ -401,7 +401,7 @@ typedef enum { * to report the current scheme, or return false to silently ignore. * Set to NULL to ignore color scheme queries. * - * Input type: GhosttyTerminalColorSchemeFn* + * Input type: GhosttyTerminalColorSchemeFn */ GHOSTTY_TERMINAL_OPT_COLOR_SCHEME = 7, @@ -411,7 +411,7 @@ typedef enum { * pointer with response data, or return false to silently ignore. * Set to NULL to ignore device attributes queries. * - * Input type: GhosttyTerminalDeviceAttributesFn* + * Input type: GhosttyTerminalDeviceAttributesFn */ GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES = 8, @@ -619,8 +619,10 @@ GhosttyResult ghostty_terminal_resize(GhosttyTerminal terminal, * Set an option on the terminal. * * Configures terminal callbacks and associated state such as the - * write_pty callback and userdata pointer. A NULL value pointer - * clears the option to its default (NULL/disabled). + * write_pty callback and userdata pointer. The value is passed + * directly for pointer types (callbacks, userdata) or as a pointer + * to the value for non-pointer types (e.g. GhosttyString*). + * NULL clears the option to its default. * * Callbacks are invoked synchronously during ghostty_terminal_vt_write(). * Callbacks must not call ghostty_terminal_vt_write() on the same diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index da8e91a0f..f5b271e35 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -304,7 +304,7 @@ pub const Option = enum(c_int) { /// Input type expected for setting the option. pub fn InType(comptime self: Option) type { return switch (self) { - .userdata => ?*anyopaque, + .userdata => ?*const anyopaque, .write_pty => ?Effects.WritePtyFn, .bell => ?Effects.BellFn, .color_scheme => ?Effects.ColorSchemeFn, @@ -313,7 +313,7 @@ pub const Option = enum(c_int) { .xtversion => ?Effects.XtversionFn, .title_changed => ?Effects.TitleChangedFn, .size_cb => ?Effects.SizeFn, - .title, .pwd => lib.String, + .title, .pwd => ?*const lib.String, }; } }; @@ -330,9 +330,11 @@ pub fn set( }; } + const wrapper = terminal_ orelse return .invalid_value; + return switch (option) { inline else => |comptime_option| setTyped( - terminal_, + wrapper, comptime_option, @ptrCast(@alignCast(value)), ), @@ -340,21 +342,20 @@ pub fn set( } fn setTyped( - terminal_: Terminal, + wrapper: *TerminalWrapper, comptime option: Option, - value: ?*const option.InType(), + value: option.InType(), ) Result { - const wrapper = terminal_ orelse return .invalid_value; 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, - .color_scheme => wrapper.effects.color_scheme = if (value) |v| v.* else null, - .device_attributes => wrapper.effects.device_attributes_cb = if (value) |v| v.* else null, - .enquiry => wrapper.effects.enquiry = if (value) |v| v.* else null, - .xtversion => wrapper.effects.xtversion = if (value) |v| v.* else null, - .title_changed => wrapper.effects.title_changed = if (value) |v| v.* else null, - .size_cb => wrapper.effects.size_cb = if (value) |v| v.* else null, + .userdata => wrapper.effects.userdata = @constCast(value), + .write_pty => wrapper.effects.write_pty = value, + .bell => wrapper.effects.bell = value, + .color_scheme => wrapper.effects.color_scheme = value, + .device_attributes => wrapper.effects.device_attributes_cb = value, + .enquiry => wrapper.effects.enquiry = value, + .xtversion => wrapper.effects.xtversion = value, + .title_changed => wrapper.effects.title_changed = value, + .size_cb => wrapper.effects.size_cb = value, .title => { const str = if (value) |v| v.ptr[0..v.len] else ""; wrapper.terminal.setTitle(str) catch return .out_of_memory; @@ -1090,10 +1091,8 @@ test "set write_pty callback" { // Set userdata and write_pty callback var sentinel: u8 = 42; - const ud: ?*anyopaque = @ptrCast(&sentinel); - try testing.expectEqual(Result.success, set(t, .userdata, @ptrCast(&ud))); - const cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&cb))); + try testing.expectEqual(Result.success, set(t, .userdata, @ptrCast(&sentinel))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); // DECRQM for wraparound mode (mode 7, set by default) should trigger write_pty vt_write(t, "\x1B[?7$p", 6); @@ -1141,8 +1140,7 @@ test "set write_pty null clears callback" { S.called = false; // Set then clear the callback - const cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); try testing.expectEqual(Result.success, set(t, .write_pty, null)); vt_write(t, "\x1B[?7$p", 6); @@ -1176,10 +1174,8 @@ test "set bell callback" { // Set userdata and bell callback var sentinel: u8 = 99; - const ud: ?*anyopaque = @ptrCast(&sentinel); - try testing.expectEqual(Result.success, set(t, .userdata, @ptrCast(&ud))); - const cb: ?Effects.BellFn = &S.bell; - try testing.expectEqual(Result.success, set(t, .bell, @ptrCast(&cb))); + try testing.expectEqual(Result.success, set(t, .userdata, @ptrCast(&sentinel))); + try testing.expectEqual(Result.success, set(t, .bell, @ptrCast(&S.bell))); // Single BEL vt_write(t, "\x07", 1); @@ -1241,10 +1237,8 @@ test "set enquiry callback" { }; defer S.deinit(); - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); - const enq_cb: ?Effects.EnquiryFn = &S.enquiry; - try testing.expectEqual(Result.success, set(t, .enquiry, @ptrCast(&enq_cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); + try testing.expectEqual(Result.success, set(t, .enquiry, @ptrCast(&S.enquiry))); // ENQ (0x05) should trigger the enquiry callback and write response via write_pty vt_write(t, "\x05", 1); @@ -1302,10 +1296,8 @@ test "set xtversion callback" { }; defer S.deinit(); - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); - const xtv_cb: ?Effects.XtversionFn = &S.xtversion; - try testing.expectEqual(Result.success, set(t, .xtversion, @ptrCast(&xtv_cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); + try testing.expectEqual(Result.success, set(t, .xtversion, @ptrCast(&S.xtversion))); // XTVERSION: CSI > q vt_write(t, "\x1B[>q", 4); @@ -1343,8 +1335,7 @@ test "xtversion without callback reports default" { defer S.deinit(); // Set write_pty but not xtversion — should get default "libghostty" - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); vt_write(t, "\x1B[>q", 4); try testing.expect(S.last_data != null); @@ -1377,10 +1368,8 @@ test "set title_changed callback" { S.last_userdata = null; var sentinel: u8 = 77; - const ud: ?*anyopaque = @ptrCast(&sentinel); - try testing.expectEqual(Result.success, set(t, .userdata, @ptrCast(&ud))); - const cb: ?Effects.TitleChangedFn = &S.titleChanged; - try testing.expectEqual(Result.success, set(t, .title_changed, @ptrCast(&cb))); + try testing.expectEqual(Result.success, set(t, .userdata, @ptrCast(&sentinel))); + try testing.expectEqual(Result.success, set(t, .title_changed, @ptrCast(&S.titleChanged))); // OSC 2 ; title ST — set window title vt_write(t, "\x1B]2;Hello\x1B\\", 10); @@ -1447,10 +1436,8 @@ test "set size callback" { }; defer S.deinit(); - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); - const size_cb_fn: ?Effects.SizeFn = &S.sizeCb; - try testing.expectEqual(Result.success, set(t, .size_cb, @ptrCast(&size_cb_fn))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); + try testing.expectEqual(Result.success, set(t, .size_cb, @ptrCast(&S.sizeCb))); // CSI 18 t — report text area size in characters vt_write(t, "\x1B[18t", 5); @@ -1520,10 +1507,8 @@ test "set device_attributes callback primary" { }; defer S.deinit(); - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); - const da_cb: ?Effects.DeviceAttributesFn = &S.da; - try testing.expectEqual(Result.success, set(t, .device_attributes, @ptrCast(&da_cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); + try testing.expectEqual(Result.success, set(t, .device_attributes, @ptrCast(&S.da))); // CSI c — primary DA vt_write(t, "\x1B[c", 3); @@ -1576,10 +1561,8 @@ test "set device_attributes callback secondary" { }; defer S.deinit(); - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); - const da_cb: ?Effects.DeviceAttributesFn = &S.da; - try testing.expectEqual(Result.success, set(t, .device_attributes, @ptrCast(&da_cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); + try testing.expectEqual(Result.success, set(t, .device_attributes, @ptrCast(&S.da))); // CSI > c — secondary DA vt_write(t, "\x1B[>c", 4); @@ -1632,10 +1615,8 @@ test "set device_attributes callback tertiary" { }; defer S.deinit(); - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); - const da_cb: ?Effects.DeviceAttributesFn = &S.da; - try testing.expectEqual(Result.success, set(t, .device_attributes, @ptrCast(&da_cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); + try testing.expectEqual(Result.success, set(t, .device_attributes, @ptrCast(&S.da))); // CSI = c — tertiary DA vt_write(t, "\x1B[=c", 4); @@ -1671,8 +1652,7 @@ test "device_attributes without callback uses default" { }; defer S.deinit(); - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); // Without setting a device_attributes callback, DA1 should return the default vt_write(t, "\x1B[c", 3); @@ -1712,10 +1692,8 @@ test "device_attributes callback returns false uses default" { }; defer S.deinit(); - const write_cb: ?Effects.WritePtyFn = &S.writePty; - try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&write_cb))); - const da_cb: ?Effects.DeviceAttributesFn = &S.da; - try testing.expectEqual(Result.success, set(t, .device_attributes, @ptrCast(&da_cb))); + try testing.expectEqual(Result.success, set(t, .write_pty, @ptrCast(&S.writePty))); + try testing.expectEqual(Result.success, set(t, .device_attributes, @ptrCast(&S.da))); // Callback returns false, should use default response vt_write(t, "\x1B[c", 3);