mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
libghostty: add mouse encoding Zig + C API (#11553)
This adds a Zig and C API for mouse event encoding.
With these APIs in place, users can now create mouse events, configure a
mouse encoder with tracking mode, output format, and terminal size, and
encode those events into terminal escape sequences. All standard mouse
protocols are supported: X10, UTF-8, SGR, URxvt, and SGR-Pixels.
## Example
```c
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <ghostty/vt.h>
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,
});
// 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});
// 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);
fwrite(buf, 1, written, stdout);
ghostty_mouse_event_free(event);
ghostty_mouse_encoder_free(encoder);
return 0;
}
```
## New APIs
| Function | Description |
|----------|-------------|
| `ghostty_mouse_event_new` | Create a new mouse event instance |
| `ghostty_mouse_event_free` | Free a mouse event instance |
| `ghostty_mouse_event_set_action` | Set the event action (press,
release, motion) |
| `ghostty_mouse_event_get_action` | Get the event action |
| `ghostty_mouse_event_set_button` | Set the event button |
| `ghostty_mouse_event_clear_button` | Clear the event button (for
motion events) |
| `ghostty_mouse_event_get_button` | Get the event button (returns
whether one is set) |
| `ghostty_mouse_event_set_mods` | Set keyboard modifiers held during
the event |
| `ghostty_mouse_event_get_mods` | Get keyboard modifiers held during
the event |
| `ghostty_mouse_event_set_position` | Set position in surface-space
pixels |
| `ghostty_mouse_event_get_position` | Get position in surface-space
pixels |
| `ghostty_mouse_encoder_new` | Create a new mouse encoder instance |
| `ghostty_mouse_encoder_free` | Free a mouse encoder instance |
| `ghostty_mouse_encoder_setopt` | Set an encoder option (tracking mode,
format, size, etc.) |
| `ghostty_mouse_encoder_setopt_from_terminal` | Sync encoder options
from a terminal's current state |
| `ghostty_mouse_encoder_reset` | Reset internal encoder state (motion
deduplication) |
| `ghostty_mouse_encoder_encode` | Encode a mouse event into a terminal
escape sequence |
This commit is contained in:
23
example/c-vt-mouse-encode/README.md
Normal file
23
example/c-vt-mouse-encode/README.md
Normal file
@@ -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
|
||||
```
|
||||
42
example/c-vt-mouse-encode/build.zig
Normal file
42
example/c-vt-mouse-encode/build.zig
Normal file
@@ -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);
|
||||
}
|
||||
24
example/c-vt-mouse-encode/build.zig.zon
Normal file
24
example/c-vt-mouse-encode/build.zig.zon
Normal file
@@ -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",
|
||||
},
|
||||
}
|
||||
78
example/c-vt-mouse-encode/src/main.c
Normal file
78
example/c-vt-mouse-encode/src/main.c
Normal file
@@ -0,0 +1,78 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ghostty/vt.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
* - @ref terminal "Terminal" - Complete terminal emulator state and rendering
|
||||
* - @ref formatter "Formatter" - Format terminal content as plain text, VT sequences, or HTML
|
||||
* - @ref key "Key Encoding" - Encode key events into terminal sequences
|
||||
* - @ref mouse "Mouse Encoding" - Encode mouse events into terminal sequences
|
||||
* - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
|
||||
* - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences
|
||||
* - @ref paste "Paste Utilities" - Validate paste data safety
|
||||
@@ -42,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
|
||||
@@ -58,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.
|
||||
@@ -88,6 +95,7 @@ extern "C" {
|
||||
#include <ghostty/vt/osc.h>
|
||||
#include <ghostty/vt/sgr.h>
|
||||
#include <ghostty/vt/key.h>
|
||||
#include <ghostty/vt/mouse.h>
|
||||
#include <ghostty/vt/paste.h>
|
||||
#include <ghostty/vt/wasm.h>
|
||||
|
||||
|
||||
119
include/ghostty/vt/mouse.h
Normal file
119
include/ghostty/vt/mouse.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* @file mouse.h
|
||||
*
|
||||
* Mouse encoding module - encode mouse events into terminal escape sequences.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_MOUSE_H
|
||||
#define GHOSTTY_VT_MOUSE_H
|
||||
|
||||
/** @defgroup mouse Mouse Encoding
|
||||
*
|
||||
* Utilities for encoding mouse events into terminal escape sequences,
|
||||
* supporting X10, UTF-8, SGR, URxvt, and SGR-Pixels mouse protocols.
|
||||
*
|
||||
* ## Basic Usage
|
||||
*
|
||||
* 1. Create an encoder instance with ghostty_mouse_encoder_new().
|
||||
* 2. Configure encoder options with ghostty_mouse_encoder_setopt() or
|
||||
* ghostty_mouse_encoder_setopt_from_terminal().
|
||||
* 3. For each mouse event:
|
||||
* - Create a mouse event with ghostty_mouse_event_new().
|
||||
* - Set event properties (action, button, modifiers, position).
|
||||
* - Encode with ghostty_mouse_encoder_encode().
|
||||
* - 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 <assert.h>
|
||||
* #include <stdio.h>
|
||||
* #include <ghostty/vt.h>
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <ghostty/vt/mouse/event.h>
|
||||
#include <ghostty/vt/mouse/encoder.h>
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif /* GHOSTTY_VT_MOUSE_H */
|
||||
211
include/ghostty/vt/mouse/encoder.h
Normal file
211
include/ghostty/vt/mouse/encoder.h
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @file encoder.h
|
||||
*
|
||||
* Mouse event encoding to terminal escape sequences.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_MOUSE_ENCODER_H
|
||||
#define GHOSTTY_VT_MOUSE_ENCODER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/mouse/event.h>
|
||||
#include <ghostty/vt/terminal.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
|
||||
/**
|
||||
* Opaque handle to a mouse encoder instance.
|
||||
*
|
||||
* This handle represents a mouse encoder that converts normalized
|
||||
* mouse events into terminal escape sequences.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef struct GhosttyMouseEncoder *GhosttyMouseEncoder;
|
||||
|
||||
/**
|
||||
* Mouse tracking mode.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef enum {
|
||||
/** Mouse reporting disabled. */
|
||||
GHOSTTY_MOUSE_TRACKING_NONE = 0,
|
||||
|
||||
/** X10 mouse mode. */
|
||||
GHOSTTY_MOUSE_TRACKING_X10 = 1,
|
||||
|
||||
/** Normal mouse mode (button press/release only). */
|
||||
GHOSTTY_MOUSE_TRACKING_NORMAL = 2,
|
||||
|
||||
/** Button-event tracking mode. */
|
||||
GHOSTTY_MOUSE_TRACKING_BUTTON = 3,
|
||||
|
||||
/** Any-event tracking mode. */
|
||||
GHOSTTY_MOUSE_TRACKING_ANY = 4,
|
||||
} GhosttyMouseTrackingMode;
|
||||
|
||||
/**
|
||||
* Mouse output format.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef enum {
|
||||
GHOSTTY_MOUSE_FORMAT_X10 = 0,
|
||||
GHOSTTY_MOUSE_FORMAT_UTF8 = 1,
|
||||
GHOSTTY_MOUSE_FORMAT_SGR = 2,
|
||||
GHOSTTY_MOUSE_FORMAT_URXVT = 3,
|
||||
GHOSTTY_MOUSE_FORMAT_SGR_PIXELS = 4,
|
||||
} GhosttyMouseFormat;
|
||||
|
||||
/**
|
||||
* Mouse encoder size and geometry context.
|
||||
*
|
||||
* This describes the rendered terminal geometry used to convert
|
||||
* surface-space positions into encoded coordinates.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef struct {
|
||||
/** Size of this struct in bytes. Must be set to sizeof(GhosttyMouseEncoderSize). */
|
||||
size_t size;
|
||||
|
||||
/** Full screen width in pixels. */
|
||||
uint32_t screen_width;
|
||||
|
||||
/** Full screen height in pixels. */
|
||||
uint32_t screen_height;
|
||||
|
||||
/** Cell width in pixels. Must be non-zero. */
|
||||
uint32_t cell_width;
|
||||
|
||||
/** Cell height in pixels. Must be non-zero. */
|
||||
uint32_t cell_height;
|
||||
|
||||
/** Top padding in pixels. */
|
||||
uint32_t padding_top;
|
||||
|
||||
/** Bottom padding in pixels. */
|
||||
uint32_t padding_bottom;
|
||||
|
||||
/** Right padding in pixels. */
|
||||
uint32_t padding_right;
|
||||
|
||||
/** Left padding in pixels. */
|
||||
uint32_t padding_left;
|
||||
} GhosttyMouseEncoderSize;
|
||||
|
||||
/**
|
||||
* Mouse encoder option identifiers.
|
||||
*
|
||||
* These values are used with ghostty_mouse_encoder_setopt() to configure
|
||||
* the behavior of the mouse encoder.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef enum {
|
||||
/** Mouse tracking mode (value: GhosttyMouseTrackingMode). */
|
||||
GHOSTTY_MOUSE_ENCODER_OPT_EVENT = 0,
|
||||
|
||||
/** Mouse output format (value: GhosttyMouseFormat). */
|
||||
GHOSTTY_MOUSE_ENCODER_OPT_FORMAT = 1,
|
||||
|
||||
/** Renderer size context (value: GhosttyMouseEncoderSize). */
|
||||
GHOSTTY_MOUSE_ENCODER_OPT_SIZE = 2,
|
||||
|
||||
/** Whether any mouse button is currently pressed (value: bool). */
|
||||
GHOSTTY_MOUSE_ENCODER_OPT_ANY_BUTTON_PRESSED = 3,
|
||||
|
||||
/** Whether to enable motion deduplication by last cell (value: bool). */
|
||||
GHOSTTY_MOUSE_ENCODER_OPT_TRACK_LAST_CELL = 4,
|
||||
} GhosttyMouseEncoderOption;
|
||||
|
||||
/**
|
||||
* Create a new mouse encoder instance.
|
||||
*
|
||||
* @param allocator Pointer to allocator, or NULL to use the default allocator
|
||||
* @param encoder Pointer to store the created encoder handle
|
||||
* @return GHOSTTY_SUCCESS on success, or an error code on failure
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
GhosttyResult ghostty_mouse_encoder_new(const GhosttyAllocator *allocator,
|
||||
GhosttyMouseEncoder *encoder);
|
||||
|
||||
/**
|
||||
* Free a mouse encoder instance.
|
||||
*
|
||||
* @param encoder The encoder handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_encoder_free(GhosttyMouseEncoder encoder);
|
||||
|
||||
/**
|
||||
* Set an option on the mouse encoder.
|
||||
*
|
||||
* A null pointer value does nothing. It does not reset to defaults.
|
||||
*
|
||||
* @param encoder The encoder handle, must not be NULL
|
||||
* @param option The option to set
|
||||
* @param value Pointer to option value (type depends on option)
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_encoder_setopt(GhosttyMouseEncoder encoder,
|
||||
GhosttyMouseEncoderOption option,
|
||||
const void *value);
|
||||
|
||||
/**
|
||||
* Set encoder options from a terminal's current state.
|
||||
*
|
||||
* This sets tracking mode and output format from terminal state.
|
||||
* It does not modify size or any-button state.
|
||||
*
|
||||
* @param encoder The encoder handle, must not be NULL
|
||||
* @param terminal The terminal handle, must not be NULL
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_encoder_setopt_from_terminal(GhosttyMouseEncoder encoder,
|
||||
GhosttyTerminal terminal);
|
||||
|
||||
/**
|
||||
* Reset internal encoder state.
|
||||
*
|
||||
* This clears motion deduplication state (last tracked cell).
|
||||
*
|
||||
* @param encoder The encoder handle (may be NULL)
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_encoder_reset(GhosttyMouseEncoder encoder);
|
||||
|
||||
/**
|
||||
* Encode a mouse event into a terminal escape sequence.
|
||||
*
|
||||
* Not all mouse events produce output. In such cases this returns
|
||||
* GHOSTTY_SUCCESS with out_len set to 0.
|
||||
*
|
||||
* If the output buffer is too small, this returns GHOSTTY_OUT_OF_MEMORY
|
||||
* and out_len contains the required size.
|
||||
*
|
||||
* @param encoder The encoder handle, must not be NULL
|
||||
* @param event The mouse event to encode, must not be NULL
|
||||
* @param out_buf Buffer to write encoded bytes to, or NULL to query required size
|
||||
* @param out_buf_size Size of out_buf in bytes
|
||||
* @param out_len Pointer to store bytes written (or required bytes on failure)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY if buffer is too small,
|
||||
* or another error code
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
GhosttyResult ghostty_mouse_encoder_encode(GhosttyMouseEncoder encoder,
|
||||
GhosttyMouseEvent event,
|
||||
char *out_buf,
|
||||
size_t out_buf_size,
|
||||
size_t *out_len);
|
||||
|
||||
#endif /* GHOSTTY_VT_MOUSE_ENCODER_H */
|
||||
193
include/ghostty/vt/mouse/event.h
Normal file
193
include/ghostty/vt/mouse/event.h
Normal file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* @file event.h
|
||||
*
|
||||
* Mouse event representation and manipulation.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_MOUSE_EVENT_H
|
||||
#define GHOSTTY_VT_MOUSE_EVENT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/key/event.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
|
||||
/**
|
||||
* Opaque handle to a mouse event.
|
||||
*
|
||||
* This handle represents a normalized mouse input event containing
|
||||
* action, button, modifiers, and surface-space position.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef struct GhosttyMouseEvent *GhosttyMouseEvent;
|
||||
|
||||
/**
|
||||
* Mouse event action type.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef enum {
|
||||
/** Mouse button was pressed. */
|
||||
GHOSTTY_MOUSE_ACTION_PRESS = 0,
|
||||
|
||||
/** Mouse button was released. */
|
||||
GHOSTTY_MOUSE_ACTION_RELEASE = 1,
|
||||
|
||||
/** Mouse moved. */
|
||||
GHOSTTY_MOUSE_ACTION_MOTION = 2,
|
||||
} GhosttyMouseAction;
|
||||
|
||||
/**
|
||||
* Mouse button identity.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef enum {
|
||||
GHOSTTY_MOUSE_BUTTON_UNKNOWN = 0,
|
||||
GHOSTTY_MOUSE_BUTTON_LEFT = 1,
|
||||
GHOSTTY_MOUSE_BUTTON_RIGHT = 2,
|
||||
GHOSTTY_MOUSE_BUTTON_MIDDLE = 3,
|
||||
GHOSTTY_MOUSE_BUTTON_FOUR = 4,
|
||||
GHOSTTY_MOUSE_BUTTON_FIVE = 5,
|
||||
GHOSTTY_MOUSE_BUTTON_SIX = 6,
|
||||
GHOSTTY_MOUSE_BUTTON_SEVEN = 7,
|
||||
GHOSTTY_MOUSE_BUTTON_EIGHT = 8,
|
||||
GHOSTTY_MOUSE_BUTTON_NINE = 9,
|
||||
GHOSTTY_MOUSE_BUTTON_TEN = 10,
|
||||
GHOSTTY_MOUSE_BUTTON_ELEVEN = 11,
|
||||
} GhosttyMouseButton;
|
||||
|
||||
/**
|
||||
* Mouse position in surface-space pixels.
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
typedef struct {
|
||||
float x;
|
||||
float y;
|
||||
} GhosttyMousePosition;
|
||||
|
||||
/**
|
||||
* Create a new mouse event instance.
|
||||
*
|
||||
* @param allocator Pointer to allocator, or NULL to use the default allocator
|
||||
* @param event Pointer to store the created event handle
|
||||
* @return GHOSTTY_SUCCESS on success, or an error code on failure
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
GhosttyResult ghostty_mouse_event_new(const GhosttyAllocator *allocator,
|
||||
GhosttyMouseEvent *event);
|
||||
|
||||
/**
|
||||
* Free a mouse event instance.
|
||||
*
|
||||
* @param event The mouse event handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_event_free(GhosttyMouseEvent event);
|
||||
|
||||
/**
|
||||
* Set the event action.
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
* @param action The action to set
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_event_set_action(GhosttyMouseEvent event,
|
||||
GhosttyMouseAction action);
|
||||
|
||||
/**
|
||||
* Get the event action.
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
* @return The event action
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
GhosttyMouseAction ghostty_mouse_event_get_action(GhosttyMouseEvent event);
|
||||
|
||||
/**
|
||||
* Set the event button.
|
||||
*
|
||||
* This sets a concrete button identity for the event.
|
||||
* To represent "no button" (for motion events), use
|
||||
* ghostty_mouse_event_clear_button().
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
* @param button The button to set
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_event_set_button(GhosttyMouseEvent event,
|
||||
GhosttyMouseButton button);
|
||||
|
||||
/**
|
||||
* Clear the event button.
|
||||
*
|
||||
* This sets the event button to "none".
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_event_clear_button(GhosttyMouseEvent event);
|
||||
|
||||
/**
|
||||
* Get the event button.
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
* @param out_button Output pointer for the button value (may be NULL)
|
||||
* @return true if a button is set, false if no button is set
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
bool ghostty_mouse_event_get_button(GhosttyMouseEvent event,
|
||||
GhosttyMouseButton *out_button);
|
||||
|
||||
/**
|
||||
* Set keyboard modifiers held during the event.
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
* @param mods Modifier bitmask
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_event_set_mods(GhosttyMouseEvent event,
|
||||
GhosttyMods mods);
|
||||
|
||||
/**
|
||||
* Get keyboard modifiers held during the event.
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
* @return Modifier bitmask
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
GhosttyMods ghostty_mouse_event_get_mods(GhosttyMouseEvent event);
|
||||
|
||||
/**
|
||||
* Set the event position in surface-space pixels.
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
* @param position The position to set
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
void ghostty_mouse_event_set_position(GhosttyMouseEvent event,
|
||||
GhosttyMousePosition position);
|
||||
|
||||
/**
|
||||
* Get the event position in surface-space pixels.
|
||||
*
|
||||
* @param event The event handle, must not be NULL
|
||||
* @return The current event position
|
||||
*
|
||||
* @ingroup mouse
|
||||
*/
|
||||
GhosttyMousePosition ghostty_mouse_event_get_position(GhosttyMouseEvent event);
|
||||
|
||||
#endif /* GHOSTTY_VT_MOUSE_EVENT_H */
|
||||
@@ -4,7 +4,7 @@ const std = @import("std");
|
||||
/// from ButtonState because button state is simply the current state
|
||||
/// of a mouse button but an action is something that triggers via
|
||||
/// an GUI event and supports more.
|
||||
pub const Action = enum { press, release, motion };
|
||||
pub const Action = enum(c_int) { press, release, motion };
|
||||
|
||||
/// The state of a mouse button.
|
||||
///
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const Terminal = @import("../terminal/Terminal.zig");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
const Terminal = terminal.Terminal;
|
||||
const renderer_size = @import("../renderer/size.zig");
|
||||
const point = @import("../terminal/point.zig");
|
||||
const key = @import("key.zig");
|
||||
@@ -11,10 +12,10 @@ const log = std.log.scoped(.mouse_encode);
|
||||
/// Options that affect mouse encoding behavior and provide runtime context.
|
||||
pub const Options = struct {
|
||||
/// Terminal mouse reporting mode (X10, normal, button, any).
|
||||
event: Terminal.MouseEvent = .none,
|
||||
event: terminal.MouseEvent = .none,
|
||||
|
||||
/// Terminal mouse reporting format.
|
||||
format: Terminal.MouseFormat = .x10,
|
||||
format: terminal.MouseFormat = .x10,
|
||||
|
||||
/// Full renderer size used to convert surface-space pixel positions
|
||||
/// into grid cell coordinates (for most formats) and terminal-space
|
||||
@@ -68,7 +69,7 @@ pub const Event = struct {
|
||||
pos: Pos = .{},
|
||||
|
||||
/// Mouse position in surface-space pixels.
|
||||
pub const Pos = struct {
|
||||
pub const Pos = extern struct {
|
||||
x: f32 = 0,
|
||||
y: f32 = 0,
|
||||
};
|
||||
@@ -92,7 +93,7 @@ pub fn encode(
|
||||
// If we don't have a motion-tracking event mode, do nothing,
|
||||
// because events outside the viewport are never reported in
|
||||
// such cases.
|
||||
if (!opts.event.motion()) return;
|
||||
if (!terminal.mouse.eventSendsMotion(opts.event)) return;
|
||||
|
||||
// For motion modes, we only report if a button is currently pressed.
|
||||
// This lets a TUI detect a click over the surface + drag out
|
||||
|
||||
@@ -84,6 +84,7 @@ pub const input = struct {
|
||||
const paste = @import("input/paste.zig");
|
||||
const key = @import("input/key.zig");
|
||||
const key_encode = @import("input/key_encode.zig");
|
||||
const mouse_encode = @import("input/mouse_encode.zig");
|
||||
|
||||
// Paste-related APIs
|
||||
pub const PasteError = paste.Error;
|
||||
@@ -98,6 +99,13 @@ pub const input = struct {
|
||||
pub const KeyMods = key.Mods;
|
||||
pub const KeyEncodeOptions = key_encode.Options;
|
||||
pub const encodeKey = key_encode.encode;
|
||||
|
||||
// Mouse encoding
|
||||
pub const MouseAction = @import("input/mouse.zig").Action;
|
||||
pub const MouseButton = @import("input/mouse.zig").Button;
|
||||
pub const MouseEncodeOptions = mouse_encode.Options;
|
||||
pub const MouseEncodeEvent = mouse_encode.Event;
|
||||
pub const encodeMouse = mouse_encode.encode;
|
||||
};
|
||||
|
||||
comptime {
|
||||
@@ -126,6 +134,23 @@ comptime {
|
||||
@export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" });
|
||||
@export(&c.key_encoder_setopt_from_terminal, .{ .name = "ghostty_key_encoder_setopt_from_terminal" });
|
||||
@export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" });
|
||||
@export(&c.mouse_event_new, .{ .name = "ghostty_mouse_event_new" });
|
||||
@export(&c.mouse_event_free, .{ .name = "ghostty_mouse_event_free" });
|
||||
@export(&c.mouse_event_set_action, .{ .name = "ghostty_mouse_event_set_action" });
|
||||
@export(&c.mouse_event_get_action, .{ .name = "ghostty_mouse_event_get_action" });
|
||||
@export(&c.mouse_event_set_button, .{ .name = "ghostty_mouse_event_set_button" });
|
||||
@export(&c.mouse_event_clear_button, .{ .name = "ghostty_mouse_event_clear_button" });
|
||||
@export(&c.mouse_event_get_button, .{ .name = "ghostty_mouse_event_get_button" });
|
||||
@export(&c.mouse_event_set_mods, .{ .name = "ghostty_mouse_event_set_mods" });
|
||||
@export(&c.mouse_event_get_mods, .{ .name = "ghostty_mouse_event_get_mods" });
|
||||
@export(&c.mouse_event_set_position, .{ .name = "ghostty_mouse_event_set_position" });
|
||||
@export(&c.mouse_event_get_position, .{ .name = "ghostty_mouse_event_get_position" });
|
||||
@export(&c.mouse_encoder_new, .{ .name = "ghostty_mouse_encoder_new" });
|
||||
@export(&c.mouse_encoder_free, .{ .name = "ghostty_mouse_encoder_free" });
|
||||
@export(&c.mouse_encoder_setopt, .{ .name = "ghostty_mouse_encoder_setopt" });
|
||||
@export(&c.mouse_encoder_setopt_from_terminal, .{ .name = "ghostty_mouse_encoder_setopt_from_terminal" });
|
||||
@export(&c.mouse_encoder_reset, .{ .name = "ghostty_mouse_encoder_reset" });
|
||||
@export(&c.mouse_encoder_encode, .{ .name = "ghostty_mouse_encoder_encode" });
|
||||
@export(&c.osc_new, .{ .name = "ghostty_osc_new" });
|
||||
@export(&c.osc_free, .{ .name = "ghostty_osc_free" });
|
||||
@export(&c.osc_next, .{ .name = "ghostty_osc_next" });
|
||||
|
||||
@@ -172,7 +172,7 @@ pub const Coordinate = union(enum) {
|
||||
///
|
||||
/// The units for the width and height are in world space. They have to
|
||||
/// be normalized for any renderer implementation.
|
||||
pub const CellSize = struct {
|
||||
pub const CellSize = extern struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
};
|
||||
@@ -180,7 +180,7 @@ pub const CellSize = struct {
|
||||
/// The dimensions of the screen that the grid is rendered to. This is the
|
||||
/// terminal screen, so it is likely a subset of the window size. The dimensions
|
||||
/// should be in pixels.
|
||||
pub const ScreenSize = struct {
|
||||
pub const ScreenSize = extern struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
@@ -224,7 +224,7 @@ pub const ScreenSize = struct {
|
||||
};
|
||||
|
||||
/// The dimensions of the grid itself, in rows/columns units.
|
||||
pub const GridSize = struct {
|
||||
pub const GridSize = extern struct {
|
||||
pub const Unit = terminal.size.CellCountInt;
|
||||
|
||||
columns: Unit = 0,
|
||||
@@ -257,7 +257,7 @@ pub const GridSize = struct {
|
||||
};
|
||||
|
||||
/// The padding to add to a screen.
|
||||
pub const Padding = struct {
|
||||
pub const Padding = extern struct {
|
||||
top: u32 = 0,
|
||||
bottom: u32 = 0,
|
||||
right: u32 = 0,
|
||||
|
||||
@@ -9,14 +9,14 @@ const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const input = @import("input.zig");
|
||||
const terminal = @import("terminal/main.zig");
|
||||
const MouseShape = @import("terminal/mouse_shape.zig").MouseShape;
|
||||
const MouseShape = terminal.MouseShape;
|
||||
|
||||
/// For processing key events; the key that was physically pressed on the
|
||||
/// keyboard.
|
||||
physical_key: input.Key,
|
||||
|
||||
/// The mouse event tracking mode, if any.
|
||||
mouse_event: terminal.Terminal.MouseEvent,
|
||||
mouse_event: terminal.MouseEvent,
|
||||
|
||||
/// The current terminal's mouse shape.
|
||||
mouse_shape: MouseShape,
|
||||
|
||||
@@ -23,7 +23,7 @@ const point = @import("point.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const Tabstops = @import("Tabstops.zig");
|
||||
const color = @import("color.zig");
|
||||
const mouse_shape_pkg = @import("mouse_shape.zig");
|
||||
const mouse = @import("mouse.zig");
|
||||
const ReadonlyHandler = @import("stream_readonly.zig").Handler;
|
||||
const ReadonlyStream = @import("stream_readonly.zig").Stream;
|
||||
|
||||
@@ -79,7 +79,7 @@ previous_char: ?u21 = null,
|
||||
modes: modespkg.ModeState = .{},
|
||||
|
||||
/// The most recently set mouse shape for the terminal.
|
||||
mouse_shape: mouse_shape_pkg.MouseShape = .text,
|
||||
mouse_shape: mouse.Shape = .text,
|
||||
|
||||
/// These are just a packed set of flags we may set on the terminal.
|
||||
flags: packed struct {
|
||||
@@ -96,8 +96,8 @@ flags: packed struct {
|
||||
/// set mode in modes. You can't get the right event/format to use
|
||||
/// based on modes alone because modes don't show you what order
|
||||
/// this was called so we have to track it separately.
|
||||
mouse_event: MouseEvent = .none,
|
||||
mouse_format: MouseFormat = .x10,
|
||||
mouse_event: mouse.Event = .none,
|
||||
mouse_format: mouse.Format = .x10,
|
||||
|
||||
/// Set via the XTSHIFTESCAPE sequence. If true (XTSHIFTESCAPE = 1)
|
||||
/// then we want to capture the shift key for the mouse protocol
|
||||
@@ -167,31 +167,6 @@ pub const Dirty = packed struct {
|
||||
preedit: bool = false,
|
||||
};
|
||||
|
||||
/// The event types that can be reported for mouse-related activities.
|
||||
/// These are all mutually exclusive (hence in a single enum).
|
||||
pub const MouseEvent = enum(u3) {
|
||||
none = 0,
|
||||
x10 = 1, // 9
|
||||
normal = 2, // 1000
|
||||
button = 3, // 1002
|
||||
any = 4, // 1003
|
||||
|
||||
/// Returns true if this event sends motion events.
|
||||
pub fn motion(self: MouseEvent) bool {
|
||||
return self == .button or self == .any;
|
||||
}
|
||||
};
|
||||
|
||||
/// The format of mouse events when enabled.
|
||||
/// These are all mutually exclusive (hence in a single enum).
|
||||
pub const MouseFormat = enum(u3) {
|
||||
x10 = 0,
|
||||
utf8 = 1, // 1005
|
||||
sgr = 2, // 1006
|
||||
urxvt = 3, // 1015
|
||||
sgr_pixels = 4, // 1016
|
||||
};
|
||||
|
||||
/// Scrolling region is the area of the screen designated where scrolling
|
||||
/// occurs. When scrolling the screen, only this viewport is scrolled.
|
||||
pub const ScrollingRegion = struct {
|
||||
|
||||
@@ -3,6 +3,8 @@ pub const formatter = @import("formatter.zig");
|
||||
pub const osc = @import("osc.zig");
|
||||
pub const key_event = @import("key_event.zig");
|
||||
pub const key_encode = @import("key_encode.zig");
|
||||
pub const mouse_event = @import("mouse_event.zig");
|
||||
pub const mouse_encode = @import("mouse_encode.zig");
|
||||
pub const paste = @import("paste.zig");
|
||||
pub const sgr = @import("sgr.zig");
|
||||
pub const terminal = @import("terminal.zig");
|
||||
@@ -58,6 +60,25 @@ pub const key_encoder_setopt = key_encode.setopt;
|
||||
pub const key_encoder_setopt_from_terminal = key_encode.setopt_from_terminal;
|
||||
pub const key_encoder_encode = key_encode.encode;
|
||||
|
||||
pub const mouse_event_new = mouse_event.new;
|
||||
pub const mouse_event_free = mouse_event.free;
|
||||
pub const mouse_event_set_action = mouse_event.set_action;
|
||||
pub const mouse_event_get_action = mouse_event.get_action;
|
||||
pub const mouse_event_set_button = mouse_event.set_button;
|
||||
pub const mouse_event_clear_button = mouse_event.clear_button;
|
||||
pub const mouse_event_get_button = mouse_event.get_button;
|
||||
pub const mouse_event_set_mods = mouse_event.set_mods;
|
||||
pub const mouse_event_get_mods = mouse_event.get_mods;
|
||||
pub const mouse_event_set_position = mouse_event.set_position;
|
||||
pub const mouse_event_get_position = mouse_event.get_position;
|
||||
|
||||
pub const mouse_encoder_new = mouse_encode.new;
|
||||
pub const mouse_encoder_free = mouse_encode.free;
|
||||
pub const mouse_encoder_setopt = mouse_encode.setopt;
|
||||
pub const mouse_encoder_setopt_from_terminal = mouse_encode.setopt_from_terminal;
|
||||
pub const mouse_encoder_reset = mouse_encode.reset;
|
||||
pub const mouse_encoder_encode = mouse_encode.encode;
|
||||
|
||||
pub const paste_is_safe = paste.is_safe;
|
||||
|
||||
pub const terminal_new = terminal.new;
|
||||
@@ -73,6 +94,8 @@ test {
|
||||
_ = osc;
|
||||
_ = key_event;
|
||||
_ = key_encode;
|
||||
_ = mouse_event;
|
||||
_ = mouse_encode;
|
||||
_ = paste;
|
||||
_ = sgr;
|
||||
_ = terminal;
|
||||
|
||||
529
src/terminal/c/mouse_encode.zig
Normal file
529
src/terminal/c/mouse_encode.zig
Normal file
@@ -0,0 +1,529 @@
|
||||
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 input_mouse_encode = @import("../../input/mouse_encode.zig");
|
||||
const renderer_size = @import("../../renderer/size.zig");
|
||||
const point = @import("../point.zig");
|
||||
const terminal_mouse = @import("../mouse.zig");
|
||||
const mouse_event = @import("mouse_event.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
const Event = mouse_event.Event;
|
||||
const Terminal = @import("terminal.zig").Terminal;
|
||||
|
||||
const log = std.log.scoped(.mouse_encode);
|
||||
|
||||
/// Wrapper around mouse encoding options that tracks the allocator for C API usage.
|
||||
const MouseEncoderWrapper = struct {
|
||||
opts: input_mouse_encode.Options,
|
||||
track_last_cell: bool = false,
|
||||
last_cell: ?point.Coordinate = null,
|
||||
alloc: Allocator,
|
||||
};
|
||||
|
||||
/// C: GhosttyMouseEncoder
|
||||
pub const Encoder = ?*MouseEncoderWrapper;
|
||||
|
||||
/// C: GhosttyMouseTrackingMode
|
||||
pub const TrackingMode = terminal_mouse.Event;
|
||||
|
||||
/// C: GhosttyMouseFormat
|
||||
pub const Format = terminal_mouse.Format;
|
||||
|
||||
/// C: GhosttyMouseEncoderSize
|
||||
pub const Size = extern struct {
|
||||
size: usize = @sizeOf(Size),
|
||||
screen_width: u32,
|
||||
screen_height: u32,
|
||||
cell_width: u32,
|
||||
cell_height: u32,
|
||||
padding_top: u32,
|
||||
padding_bottom: u32,
|
||||
padding_right: u32,
|
||||
padding_left: u32,
|
||||
|
||||
fn toRenderer(self: Size) ?renderer_size.Size {
|
||||
if (self.cell_width == 0 or self.cell_height == 0) return null;
|
||||
return .{
|
||||
.screen = .{
|
||||
.width = self.screen_width,
|
||||
.height = self.screen_height,
|
||||
},
|
||||
.cell = .{
|
||||
.width = self.cell_width,
|
||||
.height = self.cell_height,
|
||||
},
|
||||
.padding = .{
|
||||
.top = self.padding_top,
|
||||
.bottom = self.padding_bottom,
|
||||
.right = self.padding_right,
|
||||
.left = self.padding_left,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// C: GhosttyMouseEncoderOption
|
||||
pub const Option = enum(c_int) {
|
||||
event = 0,
|
||||
format = 1,
|
||||
size = 2,
|
||||
any_button_pressed = 3,
|
||||
track_last_cell = 4,
|
||||
|
||||
/// Input type expected for setting the option.
|
||||
pub fn InType(comptime self: Option) type {
|
||||
return switch (self) {
|
||||
.event => TrackingMode,
|
||||
.format => Format,
|
||||
.size => Size,
|
||||
.any_button_pressed,
|
||||
.track_last_cell,
|
||||
=> bool,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn new(
|
||||
alloc_: ?*const CAllocator,
|
||||
result: *Encoder,
|
||||
) callconv(.c) Result {
|
||||
const alloc = lib_alloc.default(alloc_);
|
||||
const ptr = alloc.create(MouseEncoderWrapper) catch
|
||||
return .out_of_memory;
|
||||
ptr.* = .{
|
||||
.opts = .{ .size = defaultSize() },
|
||||
.alloc = alloc,
|
||||
};
|
||||
result.* = ptr;
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn free(encoder_: Encoder) callconv(.c) void {
|
||||
const wrapper = encoder_ orelse return;
|
||||
const alloc = wrapper.alloc;
|
||||
alloc.destroy(wrapper);
|
||||
}
|
||||
|
||||
pub fn setopt(
|
||||
encoder_: Encoder,
|
||||
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_,
|
||||
comptime_option,
|
||||
@ptrCast(@alignCast(value orelse return)),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
fn setoptTyped(
|
||||
encoder_: Encoder,
|
||||
comptime option: Option,
|
||||
value: *const option.InType(),
|
||||
) void {
|
||||
const wrapper = encoder_.?;
|
||||
switch (option) {
|
||||
.event => {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(TrackingMode, @intFromEnum(value.*)) catch {
|
||||
log.warn("setopt invalid TrackingMode value={d}", .{@intFromEnum(value.*)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
if (wrapper.opts.event != value.*) wrapper.last_cell = null;
|
||||
wrapper.opts.event = value.*;
|
||||
},
|
||||
|
||||
.format => {
|
||||
if (comptime std.debug.runtime_safety) {
|
||||
_ = std.meta.intToEnum(Format, @intFromEnum(value.*)) catch {
|
||||
log.warn("setopt invalid Format value={d}", .{@intFromEnum(value.*)});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
if (wrapper.opts.format != value.*) wrapper.last_cell = null;
|
||||
wrapper.opts.format = value.*;
|
||||
},
|
||||
|
||||
.size => {
|
||||
if (value.size < @sizeOf(Size)) {
|
||||
log.warn("setopt size struct too small size={d} expected>={d}", .{
|
||||
value.size,
|
||||
@sizeOf(Size),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
wrapper.opts.size = value.toRenderer() orelse {
|
||||
log.warn("setopt invalid size values (cell width and height must be non-zero)", .{});
|
||||
return;
|
||||
};
|
||||
wrapper.last_cell = null;
|
||||
},
|
||||
|
||||
.any_button_pressed => wrapper.opts.any_button_pressed = value.*,
|
||||
|
||||
.track_last_cell => {
|
||||
wrapper.track_last_cell = value.*;
|
||||
if (!value.*) wrapper.last_cell = null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setopt_from_terminal(
|
||||
encoder_: Encoder,
|
||||
terminal_: Terminal,
|
||||
) callconv(.c) void {
|
||||
const wrapper = encoder_ orelse return;
|
||||
const t = terminal_ orelse return;
|
||||
wrapper.opts.event = t.flags.mouse_event;
|
||||
wrapper.opts.format = t.flags.mouse_format;
|
||||
wrapper.last_cell = null;
|
||||
}
|
||||
|
||||
pub fn reset(encoder_: Encoder) callconv(.c) void {
|
||||
const wrapper = encoder_ orelse return;
|
||||
wrapper.last_cell = null;
|
||||
}
|
||||
|
||||
pub fn encode(
|
||||
encoder_: Encoder,
|
||||
event_: Event,
|
||||
out_: ?[*]u8,
|
||||
out_len: usize,
|
||||
out_written: *usize,
|
||||
) callconv(.c) Result {
|
||||
const wrapper = encoder_ orelse return .invalid_value;
|
||||
const event = event_ orelse return .invalid_value;
|
||||
|
||||
const prev_last_cell = wrapper.last_cell;
|
||||
|
||||
var opts = wrapper.opts;
|
||||
opts.last_cell = if (wrapper.track_last_cell) &wrapper.last_cell else null;
|
||||
|
||||
var writer: std.Io.Writer = .fixed(if (out_) |out| out[0..out_len] else &.{});
|
||||
input_mouse_encode.encode(
|
||||
&writer,
|
||||
event.event,
|
||||
opts,
|
||||
) catch |err| switch (err) {
|
||||
error.WriteFailed => {
|
||||
// Failed writes should not mutate motion dedupe state because no
|
||||
// complete sequence was produced.
|
||||
wrapper.last_cell = prev_last_cell;
|
||||
|
||||
// Use a discarding writer to count how much space we would have needed.
|
||||
var count_last_cell = prev_last_cell;
|
||||
var count_opts = wrapper.opts;
|
||||
count_opts.last_cell = if (wrapper.track_last_cell) &count_last_cell else null;
|
||||
|
||||
var discarding: std.Io.Writer.Discarding = .init(&.{});
|
||||
input_mouse_encode.encode(
|
||||
&discarding.writer,
|
||||
event.event,
|
||||
count_opts,
|
||||
) catch unreachable;
|
||||
|
||||
// Discarding always uses a u64. If we're on 32-bit systems
|
||||
// we cast down. We should make this safer in the future.
|
||||
out_written.* = @intCast(discarding.count);
|
||||
return .out_of_memory;
|
||||
},
|
||||
};
|
||||
|
||||
out_written.* = writer.end;
|
||||
return .success;
|
||||
}
|
||||
|
||||
fn defaultSize() renderer_size.Size {
|
||||
return .{
|
||||
.screen = .{ .width = 1, .height = 1 },
|
||||
.cell = .{ .width = 1, .height = 1 },
|
||||
.padding = .{},
|
||||
};
|
||||
}
|
||||
|
||||
fn testSize() Size {
|
||||
return .{
|
||||
.size = @sizeOf(Size),
|
||||
.screen_width = 1_000,
|
||||
.screen_height = 1_000,
|
||||
.cell_width = 1,
|
||||
.cell_height = 1,
|
||||
.padding_top = 0,
|
||||
.padding_bottom = 0,
|
||||
.padding_right = 0,
|
||||
.padding_left = 0,
|
||||
};
|
||||
}
|
||||
|
||||
test "alloc" {
|
||||
var e: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
free(e);
|
||||
}
|
||||
|
||||
test "setopt" {
|
||||
var e: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
|
||||
const event_mode: TrackingMode = .any;
|
||||
setopt(e, .event, &event_mode);
|
||||
try testing.expectEqual(TrackingMode.any, e.?.opts.event);
|
||||
|
||||
const format_mode: Format = .sgr;
|
||||
setopt(e, .format, &format_mode);
|
||||
try testing.expectEqual(Format.sgr, e.?.opts.format);
|
||||
|
||||
const size = testSize();
|
||||
setopt(e, .size, &size);
|
||||
try testing.expectEqual(size.screen_width, e.?.opts.size.screen.width);
|
||||
try testing.expectEqual(size.screen_height, e.?.opts.size.screen.height);
|
||||
|
||||
const any_button_pressed = true;
|
||||
setopt(e, .any_button_pressed, &any_button_pressed);
|
||||
try testing.expect(e.?.opts.any_button_pressed);
|
||||
|
||||
const track_last_cell = true;
|
||||
setopt(e, .track_last_cell, &track_last_cell);
|
||||
try testing.expect(e.?.track_last_cell);
|
||||
}
|
||||
|
||||
test "setopt_from_terminal" {
|
||||
const terminal_c = @import("terminal.zig");
|
||||
|
||||
var e: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
|
||||
var t: Terminal = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
|
||||
const event_mode: TrackingMode = .any;
|
||||
setopt(e, .event, &event_mode);
|
||||
const format_mode: Format = .sgr;
|
||||
setopt(e, .format, &format_mode);
|
||||
|
||||
setopt_from_terminal(e, t);
|
||||
try testing.expectEqual(TrackingMode.none, e.?.opts.event);
|
||||
try testing.expectEqual(Format.x10, e.?.opts.format);
|
||||
}
|
||||
|
||||
test "setopt_from_terminal null" {
|
||||
setopt_from_terminal(null, null);
|
||||
|
||||
const terminal_c = @import("terminal.zig");
|
||||
var t: Terminal = undefined;
|
||||
try testing.expectEqual(Result.success, terminal_c.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{ .cols = 80, .rows = 24, .max_scrollback = 0 },
|
||||
));
|
||||
defer terminal_c.free(t);
|
||||
setopt_from_terminal(null, t);
|
||||
|
||||
var e: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
setopt_from_terminal(e, null);
|
||||
}
|
||||
|
||||
test "encode: sgr press left" {
|
||||
var encoder: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&encoder,
|
||||
));
|
||||
defer free(encoder);
|
||||
|
||||
const event_mode: TrackingMode = .any;
|
||||
setopt(encoder, .event, &event_mode);
|
||||
const format_mode: Format = .sgr;
|
||||
setopt(encoder, .format, &format_mode);
|
||||
const size = testSize();
|
||||
setopt(encoder, .size, &size);
|
||||
|
||||
var event: Event = undefined;
|
||||
try testing.expectEqual(Result.success, mouse_event.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&event,
|
||||
));
|
||||
defer mouse_event.free(event);
|
||||
|
||||
mouse_event.set_action(event, .press);
|
||||
mouse_event.set_button(event, .left);
|
||||
mouse_event.set_position(event, .{ .x = 0, .y = 0 });
|
||||
|
||||
var required: usize = 0;
|
||||
try testing.expectEqual(Result.out_of_memory, encode(
|
||||
encoder,
|
||||
event,
|
||||
null,
|
||||
0,
|
||||
&required,
|
||||
));
|
||||
try testing.expectEqual(@as(usize, 9), required);
|
||||
|
||||
var buf: [32]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, encode(
|
||||
encoder,
|
||||
event,
|
||||
&buf,
|
||||
buf.len,
|
||||
&written,
|
||||
));
|
||||
try testing.expectEqual(required, written);
|
||||
try testing.expectEqualStrings("\x1b[<0;1;1M", buf[0..written]);
|
||||
}
|
||||
|
||||
test "encode: motion dedupe and reset" {
|
||||
var encoder: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&encoder,
|
||||
));
|
||||
defer free(encoder);
|
||||
|
||||
const event_mode: TrackingMode = .any;
|
||||
setopt(encoder, .event, &event_mode);
|
||||
const format_mode: Format = .sgr;
|
||||
setopt(encoder, .format, &format_mode);
|
||||
const size = testSize();
|
||||
setopt(encoder, .size, &size);
|
||||
const track_last_cell = true;
|
||||
setopt(encoder, .track_last_cell, &track_last_cell);
|
||||
|
||||
var event: Event = undefined;
|
||||
try testing.expectEqual(Result.success, mouse_event.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&event,
|
||||
));
|
||||
defer mouse_event.free(event);
|
||||
|
||||
mouse_event.set_action(event, .motion);
|
||||
mouse_event.set_button(event, .left);
|
||||
mouse_event.set_position(event, .{ .x = 5, .y = 6 });
|
||||
|
||||
{
|
||||
var buf: [32]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, encode(
|
||||
encoder,
|
||||
event,
|
||||
&buf,
|
||||
buf.len,
|
||||
&written,
|
||||
));
|
||||
try testing.expect(written > 0);
|
||||
}
|
||||
|
||||
{
|
||||
var buf: [32]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, encode(
|
||||
encoder,
|
||||
event,
|
||||
&buf,
|
||||
buf.len,
|
||||
&written,
|
||||
));
|
||||
try testing.expectEqual(@as(usize, 0), written);
|
||||
}
|
||||
|
||||
reset(encoder);
|
||||
|
||||
{
|
||||
var buf: [32]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, encode(
|
||||
encoder,
|
||||
event,
|
||||
&buf,
|
||||
buf.len,
|
||||
&written,
|
||||
));
|
||||
try testing.expect(written > 0);
|
||||
}
|
||||
}
|
||||
|
||||
test "encode: querying required size doesn't update dedupe state" {
|
||||
var encoder: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&encoder,
|
||||
));
|
||||
defer free(encoder);
|
||||
|
||||
const event_mode: TrackingMode = .any;
|
||||
setopt(encoder, .event, &event_mode);
|
||||
const format_mode: Format = .sgr;
|
||||
setopt(encoder, .format, &format_mode);
|
||||
const size = testSize();
|
||||
setopt(encoder, .size, &size);
|
||||
const track_last_cell = true;
|
||||
setopt(encoder, .track_last_cell, &track_last_cell);
|
||||
|
||||
var event: Event = undefined;
|
||||
try testing.expectEqual(Result.success, mouse_event.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&event,
|
||||
));
|
||||
defer mouse_event.free(event);
|
||||
|
||||
mouse_event.set_action(event, .motion);
|
||||
mouse_event.set_button(event, .left);
|
||||
mouse_event.set_position(event, .{ .x = 5, .y = 6 });
|
||||
|
||||
var required: usize = 0;
|
||||
try testing.expectEqual(Result.out_of_memory, encode(
|
||||
encoder,
|
||||
event,
|
||||
null,
|
||||
0,
|
||||
&required,
|
||||
));
|
||||
try testing.expect(required > 0);
|
||||
|
||||
var buf: [32]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, encode(
|
||||
encoder,
|
||||
event,
|
||||
&buf,
|
||||
buf.len,
|
||||
&written,
|
||||
));
|
||||
try testing.expect(written > 0);
|
||||
}
|
||||
155
src/terminal/c/mouse_event.zig
Normal file
155
src/terminal/c/mouse_event.zig
Normal file
@@ -0,0 +1,155 @@
|
||||
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);
|
||||
}
|
||||
@@ -31,7 +31,10 @@ pub const Cell = page.Cell;
|
||||
pub const Coordinate = point.Coordinate;
|
||||
pub const CSI = Parser.Action.CSI;
|
||||
pub const DCS = Parser.Action.DCS;
|
||||
pub const MouseShape = @import("mouse_shape.zig").MouseShape;
|
||||
pub const mouse = @import("mouse.zig");
|
||||
pub const MouseEvent = mouse.Event;
|
||||
pub const MouseFormat = mouse.Format;
|
||||
pub const MouseShape = mouse.Shape;
|
||||
pub const Page = page.Page;
|
||||
pub const PageList = @import("PageList.zig");
|
||||
pub const Parser = @import("Parser.zig");
|
||||
|
||||
@@ -1,13 +1,39 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("terminal_options");
|
||||
const lib = @import("../lib/main.zig");
|
||||
const lib_target: lib.Target = if (build_options.c_abi) .c else .zig;
|
||||
|
||||
/// The event types that can be reported for mouse-related activities.
|
||||
/// These are all mutually exclusive (hence in a single enum).
|
||||
pub const Event = lib.Enum(lib_target, &.{
|
||||
"none",
|
||||
"x10", // 9
|
||||
"normal", // 1000
|
||||
"button", // 1002
|
||||
"any", // 1003
|
||||
});
|
||||
|
||||
/// Returns true if this event sends motion events.
|
||||
pub fn eventSendsMotion(event: Event) bool {
|
||||
return event == .button or event == .any;
|
||||
}
|
||||
|
||||
/// The format of mouse events when enabled.
|
||||
/// These are all mutually exclusive (hence in a single enum).
|
||||
pub const Format = lib.Enum(lib_target, &.{
|
||||
"x10",
|
||||
"utf8", // 1005
|
||||
"sgr", // 1006
|
||||
"urxvt", // 1015
|
||||
"sgr_pixels", // 1016
|
||||
});
|
||||
|
||||
/// The possible cursor shapes. Not all app runtimes support these shapes.
|
||||
/// The shapes are always based on the W3C supported cursor styles so we
|
||||
/// can have a cross platform list.
|
||||
//
|
||||
// Must be kept in sync with ghostty_cursor_shape_e
|
||||
pub const MouseShape = enum(c_int) {
|
||||
pub const Shape = enum(c_int) {
|
||||
default,
|
||||
context_menu,
|
||||
help,
|
||||
@@ -44,7 +70,7 @@ pub const MouseShape = enum(c_int) {
|
||||
zoom_out,
|
||||
|
||||
/// Build cursor shape from string or null if its unknown.
|
||||
pub fn fromString(v: []const u8) ?MouseShape {
|
||||
pub fn fromString(v: []const u8) ?Shape {
|
||||
return string_map.get(v);
|
||||
}
|
||||
|
||||
@@ -57,7 +83,7 @@ pub const MouseShape = enum(c_int) {
|
||||
|
||||
break :gtk switch (@import("../build_config.zig").app_runtime) {
|
||||
.gtk => @import("gobject").ext.defineEnum(
|
||||
MouseShape,
|
||||
Shape,
|
||||
.{ .name = "GhosttyMouseShape" },
|
||||
),
|
||||
|
||||
@@ -66,11 +92,11 @@ pub const MouseShape = enum(c_int) {
|
||||
};
|
||||
|
||||
test "ghostty.h MouseShape" {
|
||||
try lib.checkGhosttyHEnum(MouseShape, "GHOSTTY_MOUSE_SHAPE_");
|
||||
try lib.checkGhosttyHEnum(Shape, "GHOSTTY_MOUSE_SHAPE_");
|
||||
}
|
||||
};
|
||||
|
||||
const string_map = std.StaticStringMap(MouseShape).initComptime(.{
|
||||
const string_map = std.StaticStringMap(Shape).initComptime(.{
|
||||
// W3C
|
||||
.{ "default", .default },
|
||||
.{ "context-menu", .context_menu },
|
||||
@@ -134,7 +160,7 @@ const string_map = std.StaticStringMap(MouseShape).initComptime(.{
|
||||
|
||||
test "cursor shape from string" {
|
||||
const testing = std.testing;
|
||||
try testing.expectEqual(MouseShape.default, MouseShape.fromString("default").?);
|
||||
try testing.expectEqual(Shape.default, Shape.fromString("default").?);
|
||||
}
|
||||
|
||||
test {
|
||||
@@ -66,7 +66,7 @@ pub const Point = union(Tag) {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Coordinate = struct {
|
||||
pub const Coordinate = extern struct {
|
||||
/// x can use size.CellCountInt because the number of columns
|
||||
/// can't ever be more than a valid number of columns in a Page.
|
||||
x: size.CellCountInt = 0,
|
||||
|
||||
@@ -16,7 +16,7 @@ const modes = @import("modes.zig");
|
||||
const osc = @import("osc.zig");
|
||||
const sgr = @import("sgr.zig");
|
||||
const UTF8Decoder = @import("UTF8Decoder.zig");
|
||||
const MouseShape = @import("mouse_shape.zig").MouseShape;
|
||||
const MouseShape = @import("mouse.zig").Shape;
|
||||
|
||||
const log = std.log.scoped(.stream);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user