example/c-vt-selection-gesture
parent
f0fcb10406
commit
3e0477a14a
|
|
@ -0,0 +1,18 @@
|
|||
# Example: `ghostty-vt` Selection Gestures
|
||||
|
||||
This contains a simple example of how to use the `ghostty-vt` selection
|
||||
gesture API from C. It creates synthetic press, drag, release, and deep-press
|
||||
events and formats the resulting selection snapshots.
|
||||
|
||||
This uses a `build.zig` and `Zig` to build the C program so that we
|
||||
can reuse a lot of our build logic and depend directly on our source
|
||||
tree, but Ghostty emits a standard C library that can be used with any
|
||||
C tooling.
|
||||
|
||||
## Usage
|
||||
|
||||
Run the program:
|
||||
|
||||
```shell-session
|
||||
zig build run
|
||||
```
|
||||
|
|
@ -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_selection_gesture",
|
||||
.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);
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
.{
|
||||
.name = .c_vt_selection_gesture,
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0x5a4e72d27b582404,
|
||||
.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",
|
||||
},
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ghostty/vt.h>
|
||||
|
||||
//! [selection-gesture-main]
|
||||
static void vt_write(GhosttyTerminal terminal, const char *s) {
|
||||
ghostty_terminal_vt_write(terminal, (const uint8_t *)s, strlen(s));
|
||||
}
|
||||
|
||||
static GhosttyGridRef ref_at(GhosttyTerminal terminal, uint16_t x, uint16_t y) {
|
||||
GhosttyGridRef ref = GHOSTTY_INIT_SIZED(GhosttyGridRef);
|
||||
GhosttyPoint point = {
|
||||
.tag = GHOSTTY_POINT_TAG_ACTIVE,
|
||||
.value = { .coordinate = { .x = x, .y = y } },
|
||||
};
|
||||
|
||||
GhosttyResult result = ghostty_terminal_grid_ref(terminal, point, &ref);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
return ref;
|
||||
}
|
||||
|
||||
static void print_selection(
|
||||
GhosttyTerminal terminal,
|
||||
const char *label,
|
||||
const GhosttySelection *selection) {
|
||||
GhosttyTerminalSelectionFormatOptions opts =
|
||||
GHOSTTY_INIT_SIZED(GhosttyTerminalSelectionFormatOptions);
|
||||
opts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN;
|
||||
opts.trim = true;
|
||||
opts.selection = selection;
|
||||
|
||||
uint8_t *buf = NULL;
|
||||
size_t len = 0;
|
||||
GhosttyResult result = ghostty_terminal_selection_format_alloc(
|
||||
terminal, NULL, opts, &buf, &len);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
printf("%s: ", label);
|
||||
fwrite(buf, 1, len, stdout);
|
||||
printf("\n");
|
||||
|
||||
ghostty_free(NULL, buf, len);
|
||||
}
|
||||
|
||||
static GhosttySelectionGestureEvent new_event(
|
||||
GhosttySelectionGestureEventType type) {
|
||||
GhosttySelectionGestureEvent event = NULL;
|
||||
GhosttyResult result = ghostty_selection_gesture_event_new(NULL, &event, type);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
return event;
|
||||
}
|
||||
|
||||
int main() {
|
||||
GhosttyTerminal terminal;
|
||||
GhosttyTerminalOptions opts = {
|
||||
.cols = 20,
|
||||
.rows = 4,
|
||||
.max_scrollback = 100,
|
||||
};
|
||||
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
vt_write(terminal, "hello world\r\nsecond line");
|
||||
|
||||
GhosttySelectionGesture gesture = NULL;
|
||||
result = ghostty_selection_gesture_new(NULL, &gesture);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttySelectionGestureEvent press =
|
||||
new_event(GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_PRESS);
|
||||
GhosttySelectionGestureEvent drag =
|
||||
new_event(GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_DRAG);
|
||||
GhosttySelectionGestureEvent release =
|
||||
new_event(GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_RELEASE);
|
||||
GhosttySelectionGestureEvent deep_press =
|
||||
new_event(GHOSTTY_SELECTION_GESTURE_EVENT_TYPE_DEEP_PRESS);
|
||||
|
||||
GhosttySelectionGestureGeometry geometry = {
|
||||
.columns = 20,
|
||||
.cell_width = 10,
|
||||
.padding_left = 0,
|
||||
.screen_height = 40,
|
||||
};
|
||||
|
||||
// Press in the first cell. A normal single press records the click anchor but
|
||||
// doesn't produce a selection yet, so we discard the optional output.
|
||||
GhosttyGridRef press_ref = ref_at(terminal, 0, 0);
|
||||
result = ghostty_selection_gesture_event_set(
|
||||
press, GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF, &press_ref);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttySurfacePosition press_pos = { .x = 2, .y = 8 };
|
||||
result = ghostty_selection_gesture_event_set(
|
||||
press, GHOSTTY_SELECTION_GESTURE_EVENT_OPT_POSITION, &press_pos);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
result = ghostty_selection_gesture_event(
|
||||
gesture, terminal, press, NULL);
|
||||
assert(result == GHOSTTY_NO_VALUE);
|
||||
|
||||
// Drag across "hello". The drag event returns a selection snapshot that the
|
||||
// embedder can apply to its UI, copy, or format immediately.
|
||||
GhosttyGridRef drag_ref = ref_at(terminal, 4, 0);
|
||||
result = ghostty_selection_gesture_event_set(
|
||||
drag, GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF, &drag_ref);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttySurfacePosition drag_pos = { .x = 46, .y = 8 };
|
||||
result = ghostty_selection_gesture_event_set(
|
||||
drag, GHOSTTY_SELECTION_GESTURE_EVENT_OPT_POSITION, &drag_pos);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
result = ghostty_selection_gesture_event_set(
|
||||
drag, GHOSTTY_SELECTION_GESTURE_EVENT_OPT_GEOMETRY, &geometry);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
GhosttySelection selection = GHOSTTY_INIT_SIZED(GhosttySelection);
|
||||
result = ghostty_selection_gesture_event(
|
||||
gesture, terminal, drag, &selection);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
print_selection(terminal, "drag", &selection);
|
||||
|
||||
// Release updates gesture state but never produces a selection.
|
||||
result = ghostty_selection_gesture_event_set(
|
||||
release, GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF, &drag_ref);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
result = ghostty_selection_gesture_event(
|
||||
gesture, terminal, release, NULL);
|
||||
assert(result == GHOSTTY_NO_VALUE);
|
||||
|
||||
bool dragged = false;
|
||||
result = ghostty_selection_gesture_get(
|
||||
gesture, terminal, GHOSTTY_SELECTION_GESTURE_DATA_DRAGGED, &dragged);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
printf("dragged: %s\n", dragged ? "true" : "false");
|
||||
|
||||
// Deep press uses the active click anchor to select the surrounding word.
|
||||
ghostty_selection_gesture_reset(gesture, terminal);
|
||||
GhosttyGridRef world_ref = ref_at(terminal, 6, 0);
|
||||
result = ghostty_selection_gesture_event_set(
|
||||
press, GHOSTTY_SELECTION_GESTURE_EVENT_OPT_REF, &world_ref);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
result = ghostty_selection_gesture_event(
|
||||
gesture, terminal, press, NULL);
|
||||
assert(result == GHOSTTY_NO_VALUE);
|
||||
|
||||
result = ghostty_selection_gesture_event(
|
||||
gesture, terminal, deep_press, &selection);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
print_selection(terminal, "deep press", &selection);
|
||||
|
||||
ghostty_selection_gesture_event_free(deep_press);
|
||||
ghostty_selection_gesture_event_free(release);
|
||||
ghostty_selection_gesture_event_free(drag);
|
||||
ghostty_selection_gesture_event_free(press);
|
||||
ghostty_selection_gesture_free(gesture, terminal);
|
||||
ghostty_terminal_free(terminal);
|
||||
return 0;
|
||||
}
|
||||
//! [selection-gesture-main]
|
||||
|
|
@ -104,6 +104,11 @@
|
|||
* detect when it loses its value, and move it to a new point.
|
||||
*/
|
||||
|
||||
/** @example c-vt-selection-gesture/src/main.c
|
||||
* This example demonstrates how to use synthetic selection gesture events to
|
||||
* derive drag and deep-press selection snapshots.
|
||||
*/
|
||||
|
||||
/** @example c-vt-kitty-graphics/src/main.c
|
||||
* This example demonstrates how to use the system interface to install a
|
||||
* PNG decoder callback and send a Kitty Graphics Protocol image.
|
||||
|
|
|
|||
|
|
@ -32,9 +32,19 @@ extern "C" {
|
|||
* for the endpoints and reconstruct a GhosttySelection from fresh snapshots
|
||||
* when needed.
|
||||
*
|
||||
* Selection gestures provide a reusable state machine for turning UI pointer
|
||||
* interactions into selection snapshots. A caller creates one
|
||||
* GhosttySelectionGesture per active gesture stream, reuses typed
|
||||
* GhosttySelectionGestureEvent objects for synthetic press, drag, release,
|
||||
* autoscroll tick, and deep-press events, and applies each event with
|
||||
* ghostty_selection_gesture_event(). The returned GhosttySelection is a
|
||||
* snapshot; the embedder decides whether to render it, format/copy it, or
|
||||
* install it as the terminal's active selection.
|
||||
*
|
||||
* ## Examples
|
||||
*
|
||||
* @snippet c-vt-selection/src/main.c selection-main
|
||||
* @snippet c-vt-selection-gesture/src/main.c selection-gesture-main
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue