Files
ghostty/src/terminal/c/mouse_event.zig
Mitchell Hashimoto 9b35c2bb65 vt: add mouse encoding C API
Expose the internal mouse encoding functionality through the C API,
following the same pattern as the existing key encoding API. This
allows external consumers of libvt to encode mouse events into
terminal escape sequences (X10, UTF-8, SGR, URxvt, SGR-Pixels).

The API is split into two opaque handle types: GhosttyMouseEvent
for building normalized mouse events (action, button, modifiers,
position) and GhosttyMouseEncoder for converting those events into
escape sequences. The encoder is configured via a setopt interface
supporting tracking mode, output format, renderer geometry, button
state, and optional motion deduplication by last cell.

Encoder state can also be bulk-configured from a terminal handle
via ghostty_mouse_encoder_setopt_from_terminal. Failed encodes due
to insufficient buffer space report the required size without
mutating deduplication state.
2026-03-15 20:09:54 -07:00

156 lines
3.9 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
const testing = std.testing;
const lib_alloc = @import("../../lib/allocator.zig");
const CAllocator = lib_alloc.Allocator;
const key = @import("../../input/key.zig");
const mouse = @import("../../input/mouse.zig");
const mouse_encode = @import("../../input/mouse_encode.zig");
const Result = @import("result.zig").Result;
const log = std.log.scoped(.mouse_event);
/// Wrapper around mouse event that tracks the allocator for C API usage.
const MouseEventWrapper = struct {
event: mouse_encode.Event = .{},
alloc: Allocator,
};
/// C: GhosttyMouseEvent
pub const Event = ?*MouseEventWrapper;
/// C: GhosttyMouseAction
pub const Action = mouse.Action;
/// C: GhosttyMouseButton
pub const Button = mouse.Button;
/// C: GhosttyMousePosition
pub const Position = mouse_encode.Event.Pos;
/// C: GhosttyMods
pub const Mods = key.Mods;
pub fn new(
alloc_: ?*const CAllocator,
result: *Event,
) callconv(.c) Result {
const alloc = lib_alloc.default(alloc_);
const ptr = alloc.create(MouseEventWrapper) catch
return .out_of_memory;
ptr.* = .{ .alloc = alloc };
result.* = ptr;
return .success;
}
pub fn free(event_: Event) callconv(.c) void {
const wrapper = event_ orelse return;
const alloc = wrapper.alloc;
alloc.destroy(wrapper);
}
pub fn set_action(event_: Event, action: Action) callconv(.c) void {
if (comptime std.debug.runtime_safety) {
_ = std.meta.intToEnum(Action, @intFromEnum(action)) catch {
log.warn("set_action invalid action value={d}", .{@intFromEnum(action)});
return;
};
}
event_.?.event.action = action;
}
pub fn get_action(event_: Event) callconv(.c) Action {
return event_.?.event.action;
}
pub fn set_button(event_: Event, button: Button) callconv(.c) void {
if (comptime std.debug.runtime_safety) {
_ = std.meta.intToEnum(Button, @intFromEnum(button)) catch {
log.warn("set_button invalid button value={d}", .{@intFromEnum(button)});
return;
};
}
event_.?.event.button = button;
}
pub fn clear_button(event_: Event) callconv(.c) void {
event_.?.event.button = null;
}
pub fn get_button(event_: Event, out: ?*Button) callconv(.c) bool {
if (event_.?.event.button) |button| {
if (out) |ptr| ptr.* = button;
return true;
}
return false;
}
pub fn set_mods(event_: Event, mods: Mods) callconv(.c) void {
event_.?.event.mods = mods;
}
pub fn get_mods(event_: Event) callconv(.c) Mods {
return event_.?.event.mods;
}
pub fn set_position(event_: Event, pos: Position) callconv(.c) void {
event_.?.event.pos = pos;
}
pub fn get_position(event_: Event) callconv(.c) Position {
return event_.?.event.pos;
}
test "alloc" {
var e: Event = undefined;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&e,
));
free(e);
}
test "free null" {
free(null);
}
test "set/get" {
var e: Event = undefined;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&e,
));
defer free(e);
// Action
set_action(e, .motion);
try testing.expectEqual(Action.motion, get_action(e));
// Button
set_button(e, .left);
var button: Button = .unknown;
try testing.expect(get_button(e, &button));
try testing.expectEqual(Button.left, button);
try testing.expect(get_button(e, null));
clear_button(e);
try testing.expect(!get_button(e, &button));
// Mods
const mods: Mods = .{ .shift = true, .ctrl = true };
set_mods(e, mods);
const got_mods = get_mods(e);
try testing.expect(got_mods.shift);
try testing.expect(got_mods.ctrl);
try testing.expect(!got_mods.alt);
// Position
set_position(e, .{ .x = 12.5, .y = -4.0 });
const pos = get_position(e);
try testing.expectEqual(@as(f32, 12.5), pos.x);
try testing.expectEqual(@as(f32, -4.0), pos.y);
}