From 33b05b9876ab8940e7ad2f98d3cf5ede277cf4d9 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 15 Mar 2026 20:12:05 -0700 Subject: [PATCH] example: add C mouse encoding example Add a new c-vt-mouse-encode example that demonstrates how to use the mouse encoder C API. The example creates a mouse encoder configured with SGR format and normal tracking mode, sets up terminal geometry for pixel-to-cell coordinate mapping, and encodes a left button press event into a terminal escape sequence. Mirrors the structure of the existing c-vt-key-encode example. Also adds the corresponding @example doxygen reference in vt.h. --- example/c-vt-mouse-encode/README.md | 23 +++++++ example/c-vt-mouse-encode/build.zig | 42 +++++++++++++ example/c-vt-mouse-encode/build.zig.zon | 24 +++++++ example/c-vt-mouse-encode/src/main.c | 78 +++++++++++++++++++++++ include/ghostty/vt.h | 6 ++ include/ghostty/vt/mouse.h | 84 +++++++++++++++++++++++++ 6 files changed, 257 insertions(+) create mode 100644 example/c-vt-mouse-encode/README.md create mode 100644 example/c-vt-mouse-encode/build.zig create mode 100644 example/c-vt-mouse-encode/build.zig.zon create mode 100644 example/c-vt-mouse-encode/src/main.c diff --git a/example/c-vt-mouse-encode/README.md b/example/c-vt-mouse-encode/README.md new file mode 100644 index 000000000..754e09805 --- /dev/null +++ b/example/c-vt-mouse-encode/README.md @@ -0,0 +1,23 @@ +# Example: `ghostty-vt` C Mouse Encoding + +This example demonstrates how to use the `ghostty-vt` C library to encode mouse +events into terminal escape sequences. + +This example specifically shows how to: + +1. Create a mouse encoder with the C API +2. Configure tracking mode and output format (this example uses SGR) +3. Set terminal geometry for pixel-to-cell coordinate mapping +4. Create and configure a mouse event +5. Encode the mouse event into a terminal escape sequence + +The example encodes a left button press at pixel position (50, 40) using SGR +format, producing an escape sequence like `\x1b[<0;6;3M`. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-mouse-encode/build.zig b/example/c-vt-mouse-encode/build.zig new file mode 100644 index 000000000..350d0a89e --- /dev/null +++ b/example/c-vt-mouse-encode/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_mouse_encode", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-mouse-encode/build.zig.zon b/example/c-vt-mouse-encode/build.zig.zon new file mode 100644 index 000000000..1ab5da284 --- /dev/null +++ b/example/c-vt-mouse-encode/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt, + .version = "0.0.0", + .fingerprint = 0x413a8529a6dd3c51, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-mouse-encode/src/main.c b/example/c-vt-mouse-encode/src/main.c new file mode 100644 index 000000000..55f867252 --- /dev/null +++ b/example/c-vt-mouse-encode/src/main.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include + +int main() { + GhosttyMouseEncoder encoder; + GhosttyResult result = ghostty_mouse_encoder_new(NULL, &encoder); + assert(result == GHOSTTY_SUCCESS); + + // Set tracking mode to normal (button press/release) + ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_EVENT, + &(GhosttyMouseTrackingMode){GHOSTTY_MOUSE_TRACKING_NORMAL}); + + // Set output format to SGR + ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_FORMAT, + &(GhosttyMouseFormat){GHOSTTY_MOUSE_FORMAT_SGR}); + + // Set terminal geometry so the encoder can map pixel positions to cells + ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_SIZE, + &(GhosttyMouseEncoderSize){ + .size = sizeof(GhosttyMouseEncoderSize), + .screen_width = 800, + .screen_height = 600, + .cell_width = 10, + .cell_height = 20, + .padding_top = 0, + .padding_bottom = 0, + .padding_right = 0, + .padding_left = 0, + }); + + // Create mouse event: left button press at pixel position (50, 40) + GhosttyMouseEvent event; + result = ghostty_mouse_event_new(NULL, &event); + assert(result == GHOSTTY_SUCCESS); + ghostty_mouse_event_set_action(event, GHOSTTY_MOUSE_ACTION_PRESS); + ghostty_mouse_event_set_button(event, GHOSTTY_MOUSE_BUTTON_LEFT); + ghostty_mouse_event_set_position(event, (GhosttyMousePosition){.x = 50.0f, .y = 40.0f}); + printf("Encoding event: left button press at (50, 40) in SGR format\n"); + + // Optionally, encode with null buffer to get required size. You can + // skip this step and provide a sufficiently large buffer directly. + // If there isn't enough space, the function will return an out of memory + // error. + size_t required = 0; + result = ghostty_mouse_encoder_encode(encoder, event, NULL, 0, &required); + assert(result == GHOSTTY_OUT_OF_MEMORY); + printf("Required buffer size: %zu bytes\n", required); + + // Encode the mouse event. We don't use our required size above because + // that was just an example; we know 128 bytes is enough. + char buf[128]; + size_t written = 0; + result = ghostty_mouse_encoder_encode(encoder, event, buf, sizeof(buf), &written); + assert(result == GHOSTTY_SUCCESS); + printf("Encoded %zu bytes\n", written); + + // Print the encoded sequence (hex and string) + printf("Hex: "); + for (size_t i = 0; i < written; i++) printf("%02x ", (unsigned char)buf[i]); + printf("\n"); + + printf("String: "); + for (size_t i = 0; i < written; i++) { + if (buf[i] == 0x1b) { + printf("\\x1b"); + } else { + printf("%c", buf[i]); + } + } + printf("\n"); + + ghostty_mouse_event_free(event); + ghostty_mouse_encoder_free(encoder); + return 0; +} diff --git a/include/ghostty/vt.h b/include/ghostty/vt.h index f3958ccc3..9d22b647b 100644 --- a/include/ghostty/vt.h +++ b/include/ghostty/vt.h @@ -43,6 +43,7 @@ * Complete working examples: * - @ref c-vt/src/main.c - OSC parser example * - @ref c-vt-key-encode/src/main.c - Key encoding example + * - @ref c-vt-mouse-encode/src/main.c - Mouse encoding example * - @ref c-vt-paste/src/main.c - Paste safety check example * - @ref c-vt-sgr/src/main.c - SGR parser example * - @ref c-vt-formatter/src/main.c - Terminal formatter example @@ -59,6 +60,11 @@ * into terminal escape sequences using the Kitty keyboard protocol. */ +/** @example c-vt-mouse-encode/src/main.c + * This example demonstrates how to use the mouse encoder to convert mouse events + * into terminal escape sequences using the SGR mouse format. + */ + /** @example c-vt-paste/src/main.c * This example demonstrates how to use the paste utilities to check if * paste data is safe before sending it to the terminal. diff --git a/include/ghostty/vt/mouse.h b/include/ghostty/vt/mouse.h index 4e44e15b5..f5f37ce7b 100644 --- a/include/ghostty/vt/mouse.h +++ b/include/ghostty/vt/mouse.h @@ -24,6 +24,90 @@ * - Free the event with ghostty_mouse_event_free() or reuse it. * 4. Free the encoder with ghostty_mouse_encoder_free() when done. * + * For a complete working example, see example/c-vt-mouse-encode in the + * repository. + * + * ## Example + * + * @code{.c} + * #include + * #include + * #include + * + * int main() { + * // Create encoder + * GhosttyMouseEncoder encoder; + * GhosttyResult result = ghostty_mouse_encoder_new(NULL, &encoder); + * assert(result == GHOSTTY_SUCCESS); + * + * // Configure SGR format with normal tracking + * ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_EVENT, + * &(GhosttyMouseTrackingMode){GHOSTTY_MOUSE_TRACKING_NORMAL}); + * ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_FORMAT, + * &(GhosttyMouseFormat){GHOSTTY_MOUSE_FORMAT_SGR}); + * + * // Set terminal geometry for coordinate mapping + * ghostty_mouse_encoder_setopt(encoder, GHOSTTY_MOUSE_ENCODER_OPT_SIZE, + * &(GhosttyMouseEncoderSize){ + * .size = sizeof(GhosttyMouseEncoderSize), + * .screen_width = 800, .screen_height = 600, + * .cell_width = 10, .cell_height = 20, + * }); + * + * // Create and configure a left button press event + * GhosttyMouseEvent event; + * result = ghostty_mouse_event_new(NULL, &event); + * assert(result == GHOSTTY_SUCCESS); + * ghostty_mouse_event_set_action(event, GHOSTTY_MOUSE_ACTION_PRESS); + * ghostty_mouse_event_set_button(event, GHOSTTY_MOUSE_BUTTON_LEFT); + * ghostty_mouse_event_set_position(event, + * (GhosttyMousePosition){.x = 50.0f, .y = 40.0f}); + * + * // Encode the mouse event + * char buf[128]; + * size_t written = 0; + * result = ghostty_mouse_encoder_encode(encoder, event, + * buf, sizeof(buf), &written); + * assert(result == GHOSTTY_SUCCESS); + * + * // Use the encoded sequence (e.g., write to terminal) + * fwrite(buf, 1, written, stdout); + * + * // Cleanup + * ghostty_mouse_event_free(event); + * ghostty_mouse_encoder_free(encoder); + * return 0; + * } + * @endcode + * + * ## Example: Encoding with Terminal State + * + * When you have a GhosttyTerminal, you can sync its tracking mode and + * output format into the encoder automatically: + * + * @code{.c} + * // Create a terminal and feed it some VT data that enables mouse tracking + * GhosttyTerminal terminal; + * ghostty_terminal_new(NULL, &terminal, + * (GhosttyTerminalOptions){.cols = 80, .rows = 24, .max_scrollback = 0}); + * + * // Application might write data that enables mouse reporting, etc. + * ghostty_terminal_vt_write(terminal, vt_data, vt_len); + * + * // Create an encoder and sync its options from the terminal + * GhosttyMouseEncoder encoder; + * ghostty_mouse_encoder_new(NULL, &encoder); + * ghostty_mouse_encoder_setopt_from_terminal(encoder, terminal); + * + * // Encode a mouse event using the terminal-derived options + * char buf[128]; + * size_t written = 0; + * ghostty_mouse_encoder_encode(encoder, event, buf, sizeof(buf), &written); + * + * ghostty_mouse_encoder_free(encoder); + * ghostty_terminal_free(terminal); + * @endcode + * * @{ */