mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-17 15:21:50 +00:00
lib-vt: expose key encoding as a C API
This commit is contained in:
@@ -13,10 +13,17 @@ A file for [guiding coding agents](https://agents.md/).
|
||||
## Directory Structure
|
||||
|
||||
- Shared Zig core: `src/`
|
||||
- C API: `include/ghostty.h`
|
||||
- C API: `include`
|
||||
- macOS app: `macos/`
|
||||
- GTK (Linux and FreeBSD) app: `src/apprt/gtk`
|
||||
|
||||
## libghostty-vt
|
||||
|
||||
- Build: `zig build lib-vt`
|
||||
- Test: `zig build test-lib-vt`
|
||||
- Test filter: `zig build test-lib-vt -Dtest-filter=<test name>`
|
||||
- When working on libghostty-vt, do not build the full app.
|
||||
|
||||
## macOS App
|
||||
|
||||
- Do not use `xcodebuild`
|
||||
|
22
example/c-vt-key-encode/README.md
Normal file
22
example/c-vt-key-encode/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Example: `ghostty-vt` C Key Encoding
|
||||
|
||||
This example demonstrates how to use the `ghostty-vt` C library to encode key
|
||||
events into terminal escape sequences.
|
||||
|
||||
This example specifically shows how to:
|
||||
|
||||
1. Create a key encoder with the C API
|
||||
2. Configure Kitty keyboard protocol flags (this example uses KKP)
|
||||
3. Create and configure a key event
|
||||
4. Encode the key event into a terminal escape sequence
|
||||
|
||||
The example encodes a Ctrl key release event with the Ctrl modifier set,
|
||||
producing the escape sequence `\x1b[57442;5:3u`.
|
||||
|
||||
## Usage
|
||||
|
||||
Run the program:
|
||||
|
||||
```shell-session
|
||||
zig build run
|
||||
```
|
42
example/c-vt-key-encode/build.zig
Normal file
42
example/c-vt-key-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_key_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-key-encode/build.zig.zon
Normal file
24
example/c-vt-key-encode/build.zig.zon
Normal file
@@ -0,0 +1,24 @@
|
||||
.{
|
||||
.name = .c_vt,
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0x413a8529b1255f9a,
|
||||
.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",
|
||||
},
|
||||
}
|
59
example/c-vt-key-encode/src/main.c
Normal file
59
example/c-vt-key-encode/src/main.c
Normal file
@@ -0,0 +1,59 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ghostty/vt.h>
|
||||
|
||||
int main() {
|
||||
GhosttyKeyEncoder encoder;
|
||||
GhosttyResult result = ghostty_key_encoder_new(NULL, &encoder);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
|
||||
// Set kitty flags with all features enabled
|
||||
ghostty_key_encoder_setopt(encoder, GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS, &(uint8_t){GHOSTTY_KITTY_KEY_ALL});
|
||||
|
||||
// Create key event
|
||||
GhosttyKeyEvent event;
|
||||
result = ghostty_key_event_new(NULL, &event);
|
||||
assert(result == GHOSTTY_SUCCESS);
|
||||
ghostty_key_event_set_action(event, GHOSTTY_KEY_ACTION_RELEASE);
|
||||
ghostty_key_event_set_key(event, GHOSTTY_KEY_CONTROL_LEFT);
|
||||
ghostty_key_event_set_mods(event, GHOSTTY_MODS_CTRL);
|
||||
printf("Encoding event: left ctrl release with all Kitty flags enabled\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 enoug hspace, the function will return an out of memory
|
||||
// error.
|
||||
size_t required = 0;
|
||||
result = ghostty_key_encoder_encode(encoder, event, NULL, 0, &required);
|
||||
assert(result == GHOSTTY_OUT_OF_MEMORY);
|
||||
printf("Required buffer size: %zu bytes\n", required);
|
||||
|
||||
// Encode the key 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_key_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_key_event_free(event);
|
||||
ghostty_key_encoder_free(encoder);
|
||||
return 0;
|
||||
}
|
@@ -27,6 +27,7 @@
|
||||
* @section groups_sec API Reference
|
||||
*
|
||||
* The API is organized into the following groups:
|
||||
* - @ref key "Key Encoding" - Encode key events into terminal sequences
|
||||
* - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
|
||||
* - @ref allocator "Memory Management" - Memory management and custom allocators
|
||||
*
|
||||
@@ -319,7 +320,643 @@ typedef struct {
|
||||
/** @} */ // end of allocator group
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Functions
|
||||
// Key Encoding
|
||||
|
||||
/** @defgroup key Key Encoding
|
||||
*
|
||||
* Utilities for encoding key events into terminal escape sequences,
|
||||
* supporting both legacy encoding as well as Kitty Keyboard Protocol.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Opaque handle to a key event.
|
||||
*
|
||||
* This handle represents a keyboard input event containing information about
|
||||
* the physical key pressed, modifiers, and generated text. The event can be
|
||||
* configured using the `ghostty_key_event_set_*` functions.
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
typedef struct GhosttyKeyEvent *GhosttyKeyEvent;
|
||||
|
||||
/**
|
||||
* Keyboard input event types.
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
typedef enum {
|
||||
/** Key was released */
|
||||
GHOSTTY_KEY_ACTION_RELEASE = 0,
|
||||
/** Key was pressed */
|
||||
GHOSTTY_KEY_ACTION_PRESS = 1,
|
||||
/** Key is being repeated (held down) */
|
||||
GHOSTTY_KEY_ACTION_REPEAT = 2,
|
||||
} GhosttyKeyAction;
|
||||
|
||||
/**
|
||||
* Keyboard modifier keys bitmask.
|
||||
*
|
||||
* A bitmask representing all keyboard modifiers. This tracks which modifier keys
|
||||
* are pressed and, where supported by the platform, which side (left or right)
|
||||
* of each modifier is active.
|
||||
*
|
||||
* Use the GHOSTTY_MODS_* constants to test and set individual modifiers.
|
||||
*
|
||||
* Modifier side bits are only meaningful when the corresponding modifier bit is set.
|
||||
* Not all platforms support distinguishing between left and right modifier
|
||||
* keys and Ghostty is built to expect that some platforms may not provide this
|
||||
* information.
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
typedef uint16_t GhosttyMods;
|
||||
|
||||
/** Shift key is pressed */
|
||||
#define GHOSTTY_MODS_SHIFT (1 << 0)
|
||||
/** Control key is pressed */
|
||||
#define GHOSTTY_MODS_CTRL (1 << 1)
|
||||
/** Alt/Option key is pressed */
|
||||
#define GHOSTTY_MODS_ALT (1 << 2)
|
||||
/** Super/Command/Windows key is pressed */
|
||||
#define GHOSTTY_MODS_SUPER (1 << 3)
|
||||
/** Caps Lock is active */
|
||||
#define GHOSTTY_MODS_CAPS_LOCK (1 << 4)
|
||||
/** Num Lock is active */
|
||||
#define GHOSTTY_MODS_NUM_LOCK (1 << 5)
|
||||
|
||||
/**
|
||||
* Right shift is pressed (0 = left, 1 = right).
|
||||
* Only meaningful when GHOSTTY_MODS_SHIFT is set.
|
||||
*/
|
||||
#define GHOSTTY_MODS_SHIFT_SIDE (1 << 6)
|
||||
/**
|
||||
* Right ctrl is pressed (0 = left, 1 = right).
|
||||
* Only meaningful when GHOSTTY_MODS_CTRL is set.
|
||||
*/
|
||||
#define GHOSTTY_MODS_CTRL_SIDE (1 << 7)
|
||||
/**
|
||||
* Right alt is pressed (0 = left, 1 = right).
|
||||
* Only meaningful when GHOSTTY_MODS_ALT is set.
|
||||
*/
|
||||
#define GHOSTTY_MODS_ALT_SIDE (1 << 8)
|
||||
/**
|
||||
* Right super is pressed (0 = left, 1 = right).
|
||||
* Only meaningful when GHOSTTY_MODS_SUPER is set.
|
||||
*/
|
||||
#define GHOSTTY_MODS_SUPER_SIDE (1 << 9)
|
||||
|
||||
/**
|
||||
* Physical key codes.
|
||||
*
|
||||
* The set of key codes that Ghostty is aware of. These represent physical keys
|
||||
* on the keyboard and are layout-independent. For example, the "a" key on a US
|
||||
* keyboard is the same as the "ф" key on a Russian keyboard, but both will
|
||||
* report the same key_a value.
|
||||
*
|
||||
* Layout-dependent strings are provided separately as UTF-8 text and are produced
|
||||
* by the platform. These values are based on the W3C UI Events KeyboardEvent code
|
||||
* standard. See: https://www.w3.org/TR/uievents-code
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
typedef enum {
|
||||
GHOSTTY_KEY_UNIDENTIFIED = 0,
|
||||
|
||||
// Writing System Keys (W3C § 3.1.1)
|
||||
GHOSTTY_KEY_BACKQUOTE,
|
||||
GHOSTTY_KEY_BACKSLASH,
|
||||
GHOSTTY_KEY_BRACKET_LEFT,
|
||||
GHOSTTY_KEY_BRACKET_RIGHT,
|
||||
GHOSTTY_KEY_COMMA,
|
||||
GHOSTTY_KEY_DIGIT_0,
|
||||
GHOSTTY_KEY_DIGIT_1,
|
||||
GHOSTTY_KEY_DIGIT_2,
|
||||
GHOSTTY_KEY_DIGIT_3,
|
||||
GHOSTTY_KEY_DIGIT_4,
|
||||
GHOSTTY_KEY_DIGIT_5,
|
||||
GHOSTTY_KEY_DIGIT_6,
|
||||
GHOSTTY_KEY_DIGIT_7,
|
||||
GHOSTTY_KEY_DIGIT_8,
|
||||
GHOSTTY_KEY_DIGIT_9,
|
||||
GHOSTTY_KEY_EQUAL,
|
||||
GHOSTTY_KEY_INTL_BACKSLASH,
|
||||
GHOSTTY_KEY_INTL_RO,
|
||||
GHOSTTY_KEY_INTL_YEN,
|
||||
GHOSTTY_KEY_A,
|
||||
GHOSTTY_KEY_B,
|
||||
GHOSTTY_KEY_C,
|
||||
GHOSTTY_KEY_D,
|
||||
GHOSTTY_KEY_E,
|
||||
GHOSTTY_KEY_F,
|
||||
GHOSTTY_KEY_G,
|
||||
GHOSTTY_KEY_H,
|
||||
GHOSTTY_KEY_I,
|
||||
GHOSTTY_KEY_J,
|
||||
GHOSTTY_KEY_K,
|
||||
GHOSTTY_KEY_L,
|
||||
GHOSTTY_KEY_M,
|
||||
GHOSTTY_KEY_N,
|
||||
GHOSTTY_KEY_O,
|
||||
GHOSTTY_KEY_P,
|
||||
GHOSTTY_KEY_Q,
|
||||
GHOSTTY_KEY_R,
|
||||
GHOSTTY_KEY_S,
|
||||
GHOSTTY_KEY_T,
|
||||
GHOSTTY_KEY_U,
|
||||
GHOSTTY_KEY_V,
|
||||
GHOSTTY_KEY_W,
|
||||
GHOSTTY_KEY_X,
|
||||
GHOSTTY_KEY_Y,
|
||||
GHOSTTY_KEY_Z,
|
||||
GHOSTTY_KEY_MINUS,
|
||||
GHOSTTY_KEY_PERIOD,
|
||||
GHOSTTY_KEY_QUOTE,
|
||||
GHOSTTY_KEY_SEMICOLON,
|
||||
GHOSTTY_KEY_SLASH,
|
||||
|
||||
// Functional Keys (W3C § 3.1.2)
|
||||
GHOSTTY_KEY_ALT_LEFT,
|
||||
GHOSTTY_KEY_ALT_RIGHT,
|
||||
GHOSTTY_KEY_BACKSPACE,
|
||||
GHOSTTY_KEY_CAPS_LOCK,
|
||||
GHOSTTY_KEY_CONTEXT_MENU,
|
||||
GHOSTTY_KEY_CONTROL_LEFT,
|
||||
GHOSTTY_KEY_CONTROL_RIGHT,
|
||||
GHOSTTY_KEY_ENTER,
|
||||
GHOSTTY_KEY_META_LEFT,
|
||||
GHOSTTY_KEY_META_RIGHT,
|
||||
GHOSTTY_KEY_SHIFT_LEFT,
|
||||
GHOSTTY_KEY_SHIFT_RIGHT,
|
||||
GHOSTTY_KEY_SPACE,
|
||||
GHOSTTY_KEY_TAB,
|
||||
GHOSTTY_KEY_CONVERT,
|
||||
GHOSTTY_KEY_KANA_MODE,
|
||||
GHOSTTY_KEY_NON_CONVERT,
|
||||
|
||||
// Control Pad Section (W3C § 3.2)
|
||||
GHOSTTY_KEY_DELETE,
|
||||
GHOSTTY_KEY_END,
|
||||
GHOSTTY_KEY_HELP,
|
||||
GHOSTTY_KEY_HOME,
|
||||
GHOSTTY_KEY_INSERT,
|
||||
GHOSTTY_KEY_PAGE_DOWN,
|
||||
GHOSTTY_KEY_PAGE_UP,
|
||||
|
||||
// Arrow Pad Section (W3C § 3.3)
|
||||
GHOSTTY_KEY_ARROW_DOWN,
|
||||
GHOSTTY_KEY_ARROW_LEFT,
|
||||
GHOSTTY_KEY_ARROW_RIGHT,
|
||||
GHOSTTY_KEY_ARROW_UP,
|
||||
|
||||
// Numpad Section (W3C § 3.4)
|
||||
GHOSTTY_KEY_NUM_LOCK,
|
||||
GHOSTTY_KEY_NUMPAD_0,
|
||||
GHOSTTY_KEY_NUMPAD_1,
|
||||
GHOSTTY_KEY_NUMPAD_2,
|
||||
GHOSTTY_KEY_NUMPAD_3,
|
||||
GHOSTTY_KEY_NUMPAD_4,
|
||||
GHOSTTY_KEY_NUMPAD_5,
|
||||
GHOSTTY_KEY_NUMPAD_6,
|
||||
GHOSTTY_KEY_NUMPAD_7,
|
||||
GHOSTTY_KEY_NUMPAD_8,
|
||||
GHOSTTY_KEY_NUMPAD_9,
|
||||
GHOSTTY_KEY_NUMPAD_ADD,
|
||||
GHOSTTY_KEY_NUMPAD_BACKSPACE,
|
||||
GHOSTTY_KEY_NUMPAD_CLEAR,
|
||||
GHOSTTY_KEY_NUMPAD_CLEAR_ENTRY,
|
||||
GHOSTTY_KEY_NUMPAD_COMMA,
|
||||
GHOSTTY_KEY_NUMPAD_DECIMAL,
|
||||
GHOSTTY_KEY_NUMPAD_DIVIDE,
|
||||
GHOSTTY_KEY_NUMPAD_ENTER,
|
||||
GHOSTTY_KEY_NUMPAD_EQUAL,
|
||||
GHOSTTY_KEY_NUMPAD_MEMORY_ADD,
|
||||
GHOSTTY_KEY_NUMPAD_MEMORY_CLEAR,
|
||||
GHOSTTY_KEY_NUMPAD_MEMORY_RECALL,
|
||||
GHOSTTY_KEY_NUMPAD_MEMORY_STORE,
|
||||
GHOSTTY_KEY_NUMPAD_MEMORY_SUBTRACT,
|
||||
GHOSTTY_KEY_NUMPAD_MULTIPLY,
|
||||
GHOSTTY_KEY_NUMPAD_PAREN_LEFT,
|
||||
GHOSTTY_KEY_NUMPAD_PAREN_RIGHT,
|
||||
GHOSTTY_KEY_NUMPAD_SUBTRACT,
|
||||
GHOSTTY_KEY_NUMPAD_SEPARATOR,
|
||||
GHOSTTY_KEY_NUMPAD_UP,
|
||||
GHOSTTY_KEY_NUMPAD_DOWN,
|
||||
GHOSTTY_KEY_NUMPAD_RIGHT,
|
||||
GHOSTTY_KEY_NUMPAD_LEFT,
|
||||
GHOSTTY_KEY_NUMPAD_BEGIN,
|
||||
GHOSTTY_KEY_NUMPAD_HOME,
|
||||
GHOSTTY_KEY_NUMPAD_END,
|
||||
GHOSTTY_KEY_NUMPAD_INSERT,
|
||||
GHOSTTY_KEY_NUMPAD_DELETE,
|
||||
GHOSTTY_KEY_NUMPAD_PAGE_UP,
|
||||
GHOSTTY_KEY_NUMPAD_PAGE_DOWN,
|
||||
|
||||
// Function Section (W3C § 3.5)
|
||||
GHOSTTY_KEY_ESCAPE,
|
||||
GHOSTTY_KEY_F1,
|
||||
GHOSTTY_KEY_F2,
|
||||
GHOSTTY_KEY_F3,
|
||||
GHOSTTY_KEY_F4,
|
||||
GHOSTTY_KEY_F5,
|
||||
GHOSTTY_KEY_F6,
|
||||
GHOSTTY_KEY_F7,
|
||||
GHOSTTY_KEY_F8,
|
||||
GHOSTTY_KEY_F9,
|
||||
GHOSTTY_KEY_F10,
|
||||
GHOSTTY_KEY_F11,
|
||||
GHOSTTY_KEY_F12,
|
||||
GHOSTTY_KEY_F13,
|
||||
GHOSTTY_KEY_F14,
|
||||
GHOSTTY_KEY_F15,
|
||||
GHOSTTY_KEY_F16,
|
||||
GHOSTTY_KEY_F17,
|
||||
GHOSTTY_KEY_F18,
|
||||
GHOSTTY_KEY_F19,
|
||||
GHOSTTY_KEY_F20,
|
||||
GHOSTTY_KEY_F21,
|
||||
GHOSTTY_KEY_F22,
|
||||
GHOSTTY_KEY_F23,
|
||||
GHOSTTY_KEY_F24,
|
||||
GHOSTTY_KEY_F25,
|
||||
GHOSTTY_KEY_FN,
|
||||
GHOSTTY_KEY_FN_LOCK,
|
||||
GHOSTTY_KEY_PRINT_SCREEN,
|
||||
GHOSTTY_KEY_SCROLL_LOCK,
|
||||
GHOSTTY_KEY_PAUSE,
|
||||
|
||||
// Media Keys (W3C § 3.6)
|
||||
GHOSTTY_KEY_BROWSER_BACK,
|
||||
GHOSTTY_KEY_BROWSER_FAVORITES,
|
||||
GHOSTTY_KEY_BROWSER_FORWARD,
|
||||
GHOSTTY_KEY_BROWSER_HOME,
|
||||
GHOSTTY_KEY_BROWSER_REFRESH,
|
||||
GHOSTTY_KEY_BROWSER_SEARCH,
|
||||
GHOSTTY_KEY_BROWSER_STOP,
|
||||
GHOSTTY_KEY_EJECT,
|
||||
GHOSTTY_KEY_LAUNCH_APP_1,
|
||||
GHOSTTY_KEY_LAUNCH_APP_2,
|
||||
GHOSTTY_KEY_LAUNCH_MAIL,
|
||||
GHOSTTY_KEY_MEDIA_PLAY_PAUSE,
|
||||
GHOSTTY_KEY_MEDIA_SELECT,
|
||||
GHOSTTY_KEY_MEDIA_STOP,
|
||||
GHOSTTY_KEY_MEDIA_TRACK_NEXT,
|
||||
GHOSTTY_KEY_MEDIA_TRACK_PREVIOUS,
|
||||
GHOSTTY_KEY_POWER,
|
||||
GHOSTTY_KEY_SLEEP,
|
||||
GHOSTTY_KEY_AUDIO_VOLUME_DOWN,
|
||||
GHOSTTY_KEY_AUDIO_VOLUME_MUTE,
|
||||
GHOSTTY_KEY_AUDIO_VOLUME_UP,
|
||||
GHOSTTY_KEY_WAKE_UP,
|
||||
|
||||
// Legacy, Non-standard, and Special Keys (W3C § 3.7)
|
||||
GHOSTTY_KEY_COPY,
|
||||
GHOSTTY_KEY_CUT,
|
||||
GHOSTTY_KEY_PASTE,
|
||||
} GhosttyKey;
|
||||
|
||||
/**
|
||||
* Kitty keyboard protocol flags.
|
||||
*
|
||||
* Bitflags representing the various modes of the Kitty keyboard protocol.
|
||||
* These can be combined using bitwise OR operations. Valid values all
|
||||
* start with `GHOSTTY_KITTY_KEY_`.
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
typedef uint8_t GhosttyKittyKeyFlags;
|
||||
|
||||
/** Kitty keyboard protocol disabled (all flags off) */
|
||||
#define GHOSTTY_KITTY_KEY_DISABLED 0
|
||||
|
||||
/** Disambiguate escape codes */
|
||||
#define GHOSTTY_KITTY_KEY_DISAMBIGUATE (1 << 0)
|
||||
|
||||
/** Report key press and release events */
|
||||
#define GHOSTTY_KITTY_KEY_REPORT_EVENTS (1 << 1)
|
||||
|
||||
/** Report alternate key codes */
|
||||
#define GHOSTTY_KITTY_KEY_REPORT_ALTERNATES (1 << 2)
|
||||
|
||||
/** Report all key events including those normally handled by the terminal */
|
||||
#define GHOSTTY_KITTY_KEY_REPORT_ALL (1 << 3)
|
||||
|
||||
/** Report associated text with key events */
|
||||
#define GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED (1 << 4)
|
||||
|
||||
/** All Kitty keyboard protocol flags enabled */
|
||||
#define GHOSTTY_KITTY_KEY_ALL (GHOSTTY_KITTY_KEY_DISAMBIGUATE | GHOSTTY_KITTY_KEY_REPORT_EVENTS | GHOSTTY_KITTY_KEY_REPORT_ALTERNATES | GHOSTTY_KITTY_KEY_REPORT_ALL | GHOSTTY_KITTY_KEY_REPORT_ASSOCIATED)
|
||||
|
||||
/**
|
||||
* macOS option key behavior.
|
||||
*
|
||||
* Determines whether the "option" key on macOS is treated as "alt" or not.
|
||||
* See the Ghostty `macos-option-as-alt` configuration option for more details.
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
typedef enum {
|
||||
/** Option key is not treated as alt */
|
||||
GHOSTTY_OPTION_AS_ALT_FALSE = 0,
|
||||
/** Option key is treated as alt */
|
||||
GHOSTTY_OPTION_AS_ALT_TRUE = 1,
|
||||
/** Only left option key is treated as alt */
|
||||
GHOSTTY_OPTION_AS_ALT_LEFT = 2,
|
||||
/** Only right option key is treated as alt */
|
||||
GHOSTTY_OPTION_AS_ALT_RIGHT = 3,
|
||||
} GhosttyOptionAsAlt;
|
||||
|
||||
/**
|
||||
* Create a new key event instance.
|
||||
*
|
||||
* Creates a new key event with default values. The event must be freed using
|
||||
* ghostty_key_event_free() when no longer needed.
|
||||
*
|
||||
* @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
|
||||
* @param event Pointer to store the created key event handle
|
||||
* @return GHOSTTY_SUCCESS on success, or an error code on failure
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
GhosttyResult ghostty_key_event_new(const GhosttyAllocator *allocator, GhosttyKeyEvent *event);
|
||||
|
||||
/**
|
||||
* Free a key event instance.
|
||||
*
|
||||
* Releases all resources associated with the key event. After this call,
|
||||
* the event handle becomes invalid and must not be used.
|
||||
*
|
||||
* @param event The key event handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_event_free(GhosttyKeyEvent event);
|
||||
|
||||
/**
|
||||
* Set the key action (press, release, repeat).
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @param action The action to set
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_event_set_action(GhosttyKeyEvent event, GhosttyKeyAction action);
|
||||
|
||||
/**
|
||||
* Get the key action (press, release, repeat).
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @return The key action
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
GhosttyKeyAction ghostty_key_event_get_action(GhosttyKeyEvent event);
|
||||
|
||||
/**
|
||||
* Set the physical key code.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @param key The physical key code to set
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_event_set_key(GhosttyKeyEvent event, GhosttyKey key);
|
||||
|
||||
/**
|
||||
* Get the physical key code.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @return The physical key code
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
GhosttyKey ghostty_key_event_get_key(GhosttyKeyEvent event);
|
||||
|
||||
/**
|
||||
* Set the modifier keys bitmask.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @param mods The modifier keys bitmask to set
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_event_set_mods(GhosttyKeyEvent event, GhosttyMods mods);
|
||||
|
||||
/**
|
||||
* Get the modifier keys bitmask.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @return The modifier keys bitmask
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
GhosttyMods ghostty_key_event_get_mods(GhosttyKeyEvent event);
|
||||
|
||||
/**
|
||||
* Set the consumed modifiers bitmask.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @param consumed_mods The consumed modifiers bitmask to set
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_event_set_consumed_mods(GhosttyKeyEvent event, GhosttyMods consumed_mods);
|
||||
|
||||
/**
|
||||
* Get the consumed modifiers bitmask.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @return The consumed modifiers bitmask
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
GhosttyMods ghostty_key_event_get_consumed_mods(GhosttyKeyEvent event);
|
||||
|
||||
/**
|
||||
* Set whether the key event is part of a composition sequence.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @param composing Whether the key event is part of a composition sequence
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_event_set_composing(GhosttyKeyEvent event, bool composing);
|
||||
|
||||
/**
|
||||
* Get whether the key event is part of a composition sequence.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @return Whether the key event is part of a composition sequence
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
bool ghostty_key_event_get_composing(GhosttyKeyEvent event);
|
||||
|
||||
/**
|
||||
* Set the UTF-8 text generated by the key event.
|
||||
*
|
||||
* The key event does NOT take ownership of the text pointer. The caller
|
||||
* must ensure the string remains valid for the lifetime needed by the event.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @param utf8 The UTF-8 text to set (or NULL for empty)
|
||||
* @param len Length of the UTF-8 text in bytes
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_event_set_utf8(GhosttyKeyEvent event, const char *utf8, size_t len);
|
||||
|
||||
/**
|
||||
* Get the UTF-8 text generated by the key event.
|
||||
*
|
||||
* The returned pointer is valid until the event is freed or the UTF-8 text is modified.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @param len Pointer to store the length of the UTF-8 text in bytes (may be NULL)
|
||||
* @return The UTF-8 text (or NULL for empty)
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
const char *ghostty_key_event_get_utf8(GhosttyKeyEvent event, size_t *len);
|
||||
|
||||
/**
|
||||
* Set the unshifted Unicode codepoint.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @param codepoint The unshifted Unicode codepoint to set
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_event_set_unshifted_codepoint(GhosttyKeyEvent event, uint32_t codepoint);
|
||||
|
||||
/**
|
||||
* Get the unshifted Unicode codepoint.
|
||||
*
|
||||
* @param event The key event handle, must not be NULL
|
||||
* @return The unshifted Unicode codepoint
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
uint32_t ghostty_key_event_get_unshifted_codepoint(GhosttyKeyEvent event);
|
||||
|
||||
/**
|
||||
* Opaque handle to a key encoder instance.
|
||||
*
|
||||
* This handle represents a key encoder that converts key events into terminal
|
||||
* escape sequences. The encoder supports both legacy encoding and the Kitty
|
||||
* Keyboard Protocol, depending on the options set.
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
typedef struct GhosttyKeyEncoder *GhosttyKeyEncoder;
|
||||
|
||||
/**
|
||||
* Key encoder option identifiers.
|
||||
*
|
||||
* These values are used with ghostty_key_encoder_setopt() to configure
|
||||
* the behavior of the key encoder.
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
typedef enum {
|
||||
/** Terminal DEC mode 1: cursor key application mode (value: bool) */
|
||||
GHOSTTY_KEY_ENCODER_OPT_CURSOR_KEY_APPLICATION = 0,
|
||||
|
||||
/** Terminal DEC mode 66: keypad key application mode (value: bool) */
|
||||
GHOSTTY_KEY_ENCODER_OPT_KEYPAD_KEY_APPLICATION = 1,
|
||||
|
||||
/** Terminal DEC mode 1035: ignore keypad with numlock (value: bool) */
|
||||
GHOSTTY_KEY_ENCODER_OPT_IGNORE_KEYPAD_WITH_NUMLOCK = 2,
|
||||
|
||||
/** Terminal DEC mode 1036: alt sends escape prefix (value: bool) */
|
||||
GHOSTTY_KEY_ENCODER_OPT_ALT_ESC_PREFIX = 3,
|
||||
|
||||
/** xterm modifyOtherKeys mode 2 (value: bool) */
|
||||
GHOSTTY_KEY_ENCODER_OPT_MODIFY_OTHER_KEYS_STATE_2 = 4,
|
||||
|
||||
/** Kitty keyboard protocol flags (value: GhosttyKittyKeyFlags bitmask) */
|
||||
GHOSTTY_KEY_ENCODER_OPT_KITTY_FLAGS = 5,
|
||||
|
||||
/** macOS option-as-alt setting (value: GhosttyOptionAsAlt) */
|
||||
GHOSTTY_KEY_ENCODER_OPT_MACOS_OPTION_AS_ALT = 6,
|
||||
} GhosttyKeyEncoderOption;
|
||||
|
||||
/**
|
||||
* Create a new key encoder instance.
|
||||
*
|
||||
* Creates a new key encoder with default options. The encoder can be configured
|
||||
* using ghostty_key_encoder_setopt() and must be freed using
|
||||
* ghostty_key_encoder_free() when no longer needed.
|
||||
*
|
||||
* @param allocator Pointer to the allocator to use for memory management, 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 key
|
||||
*/
|
||||
GhosttyResult ghostty_key_encoder_new(const GhosttyAllocator *allocator, GhosttyKeyEncoder *encoder);
|
||||
|
||||
/**
|
||||
* Free a key encoder instance.
|
||||
*
|
||||
* Releases all resources associated with the key encoder. After this call,
|
||||
* the encoder handle becomes invalid and must not be used.
|
||||
*
|
||||
* @param encoder The encoder handle to free (may be NULL)
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_encoder_free(GhosttyKeyEncoder encoder);
|
||||
|
||||
/**
|
||||
* Set an option on the key encoder.
|
||||
*
|
||||
* Configures the behavior of the key encoder. Options control various aspects
|
||||
* of encoding such as terminal modes (cursor key application mode, keypad mode),
|
||||
* protocol selection (Kitty keyboard protocol flags), and platform-specific
|
||||
* behaviors (macOS option-as-alt).
|
||||
*
|
||||
* A null pointer value does nothing. It does not reset the value to the
|
||||
* default. The setopt call will do nothing.
|
||||
*
|
||||
* @param encoder The encoder handle, must not be NULL
|
||||
* @param option The option to set
|
||||
* @param value Pointer to the value to set (type depends on the option)
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
void ghostty_key_encoder_setopt(GhosttyKeyEncoder encoder, GhosttyKeyEncoderOption option, const void *value);
|
||||
|
||||
/**
|
||||
* Encode a key event into a terminal escape sequence.
|
||||
*
|
||||
* Converts a key event into the appropriate terminal escape sequence based on
|
||||
* the encoder's current options. The sequence is written to the provided buffer.
|
||||
*
|
||||
* Not all key events produce output. For example, unmodified modifier keys
|
||||
* typically don't generate escape sequences. Check the out_len parameter to
|
||||
* determine if any data was written.
|
||||
*
|
||||
* If the output buffer is too small, this function returns GHOSTTY_OUT_OF_MEMORY
|
||||
* and out_len will contain the required buffer size. The caller can then
|
||||
* allocate a larger buffer and call the function again.
|
||||
*
|
||||
* @param encoder The encoder handle, must not be NULL
|
||||
* @param event The key event to encode, must not be NULL
|
||||
* @param out_buf Buffer to write the encoded sequence to
|
||||
* @param out_buf_size Size of the output buffer in bytes
|
||||
* @param out_len Pointer to store the number of bytes written (may be NULL)
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_OUT_OF_MEMORY if buffer too small, or other error code
|
||||
*
|
||||
* @ingroup key
|
||||
*/
|
||||
GhosttyResult ghostty_key_encoder_encode(GhosttyKeyEncoder encoder, GhosttyKeyEvent event, char *out_buf, size_t out_buf_size, size_t *out_len);
|
||||
|
||||
/** @} */ // end of key group
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// OSC Parser
|
||||
|
||||
/** @defgroup osc OSC Parser
|
||||
*
|
||||
|
@@ -36,6 +36,16 @@ pub const Options = struct {
|
||||
/// docs for a more detailed description of why this is needed.
|
||||
macos_option_as_alt: OptionAsAlt = .false,
|
||||
|
||||
pub const default: Options = .{
|
||||
.cursor_key_application = false,
|
||||
.keypad_key_application = false,
|
||||
.ignore_keypad_with_numlock = false,
|
||||
.alt_esc_prefix = false,
|
||||
.modify_other_keys_state_2 = false,
|
||||
.kitty_flags = .disabled,
|
||||
.macos_option_as_alt = .false,
|
||||
};
|
||||
|
||||
/// Initialize our options from the terminal state.
|
||||
///
|
||||
/// Note that `macos_option_as_alt` cannot be determined from
|
||||
|
@@ -102,6 +102,26 @@ comptime {
|
||||
@export(&c.osc_end, .{ .name = "ghostty_osc_end" });
|
||||
@export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" });
|
||||
@export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" });
|
||||
@export(&c.key_event_new, .{ .name = "ghostty_key_event_new" });
|
||||
@export(&c.key_event_free, .{ .name = "ghostty_key_event_free" });
|
||||
@export(&c.key_event_set_action, .{ .name = "ghostty_key_event_set_action" });
|
||||
@export(&c.key_event_get_action, .{ .name = "ghostty_key_event_get_action" });
|
||||
@export(&c.key_event_set_key, .{ .name = "ghostty_key_event_set_key" });
|
||||
@export(&c.key_event_get_key, .{ .name = "ghostty_key_event_get_key" });
|
||||
@export(&c.key_event_set_mods, .{ .name = "ghostty_key_event_set_mods" });
|
||||
@export(&c.key_event_get_mods, .{ .name = "ghostty_key_event_get_mods" });
|
||||
@export(&c.key_event_set_consumed_mods, .{ .name = "ghostty_key_event_set_consumed_mods" });
|
||||
@export(&c.key_event_get_consumed_mods, .{ .name = "ghostty_key_event_get_consumed_mods" });
|
||||
@export(&c.key_event_set_composing, .{ .name = "ghostty_key_event_set_composing" });
|
||||
@export(&c.key_event_get_composing, .{ .name = "ghostty_key_event_get_composing" });
|
||||
@export(&c.key_event_set_utf8, .{ .name = "ghostty_key_event_set_utf8" });
|
||||
@export(&c.key_event_get_utf8, .{ .name = "ghostty_key_event_get_utf8" });
|
||||
@export(&c.key_event_set_unshifted_codepoint, .{ .name = "ghostty_key_event_set_unshifted_codepoint" });
|
||||
@export(&c.key_event_get_unshifted_codepoint, .{ .name = "ghostty_key_event_get_unshifted_codepoint" });
|
||||
@export(&c.key_encoder_new, .{ .name = "ghostty_key_encoder_new" });
|
||||
@export(&c.key_encoder_free, .{ .name = "ghostty_key_encoder_free" });
|
||||
@export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" });
|
||||
@export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" });
|
||||
}
|
||||
}
|
||||
|
||||
|
269
src/terminal/c/key_encode.zig
Normal file
269
src/terminal/c/key_encode.zig
Normal file
@@ -0,0 +1,269 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const lib_alloc = @import("../../lib/allocator.zig");
|
||||
const CAllocator = lib_alloc.Allocator;
|
||||
const key_encode = @import("../../input/key_encode.zig");
|
||||
const key_event = @import("key_event.zig");
|
||||
const KittyFlags = @import("../../terminal/kitty/key.zig").Flags;
|
||||
const OptionAsAlt = @import("../../input/config.zig").OptionAsAlt;
|
||||
const Result = @import("result.zig").Result;
|
||||
const KeyEvent = @import("key_event.zig").Event;
|
||||
|
||||
/// Wrapper around key encoding options that tracks the allocator for C API usage.
|
||||
const KeyEncoderWrapper = struct {
|
||||
opts: key_encode.Options,
|
||||
alloc: Allocator,
|
||||
};
|
||||
|
||||
/// C: GhosttyKeyEncoder
|
||||
pub const Encoder = ?*KeyEncoderWrapper;
|
||||
|
||||
pub fn new(
|
||||
alloc_: ?*const CAllocator,
|
||||
result: *Encoder,
|
||||
) callconv(.c) Result {
|
||||
const alloc = lib_alloc.default(alloc_);
|
||||
const ptr = alloc.create(KeyEncoderWrapper) catch
|
||||
return .out_of_memory;
|
||||
ptr.* = .{
|
||||
.opts = .{},
|
||||
.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);
|
||||
}
|
||||
|
||||
/// C: GhosttyKeyEncoderOption
|
||||
pub const Option = enum(c_int) {
|
||||
cursor_key_application = 0,
|
||||
keypad_key_application = 1,
|
||||
ignore_keypad_with_numlock = 2,
|
||||
alt_esc_prefix = 3,
|
||||
modify_other_keys_state_2 = 4,
|
||||
kitty_flags = 5,
|
||||
macos_option_as_alt = 6,
|
||||
|
||||
/// Input type expected for setting the option.
|
||||
pub fn InType(comptime self: Option) type {
|
||||
return switch (self) {
|
||||
.cursor_key_application,
|
||||
.keypad_key_application,
|
||||
.ignore_keypad_with_numlock,
|
||||
.alt_esc_prefix,
|
||||
.modify_other_keys_state_2,
|
||||
=> bool,
|
||||
.kitty_flags => u8,
|
||||
.macos_option_as_alt => OptionAsAlt,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn setopt(
|
||||
encoder_: Encoder,
|
||||
option: Option,
|
||||
value: ?*const anyopaque,
|
||||
) callconv(.c) void {
|
||||
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 opts = &encoder_.?.opts;
|
||||
switch (option) {
|
||||
.cursor_key_application => opts.cursor_key_application = value.*,
|
||||
.keypad_key_application => opts.keypad_key_application = value.*,
|
||||
.ignore_keypad_with_numlock => opts.ignore_keypad_with_numlock = value.*,
|
||||
.alt_esc_prefix => opts.alt_esc_prefix = value.*,
|
||||
.modify_other_keys_state_2 => opts.modify_other_keys_state_2 = value.*,
|
||||
.kitty_flags => opts.kitty_flags = flags: {
|
||||
const bits: u5 = @truncate(value.*);
|
||||
break :flags @bitCast(bits);
|
||||
},
|
||||
.macos_option_as_alt => opts.macos_option_as_alt = value.*,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode(
|
||||
encoder_: Encoder,
|
||||
event_: KeyEvent,
|
||||
out_: ?[*]u8,
|
||||
out_len: usize,
|
||||
out_written: *usize,
|
||||
) callconv(.c) Result {
|
||||
// Attempt to write to this buffer
|
||||
var writer: std.Io.Writer = .fixed(if (out_) |out| out[0..out_len] else &.{});
|
||||
key_encode.encode(
|
||||
&writer,
|
||||
event_.?.event,
|
||||
encoder_.?.opts,
|
||||
) catch |err| switch (err) {
|
||||
error.WriteFailed => {
|
||||
// If we don't have space, use a discarding writer to count
|
||||
// how much space we would have needed.
|
||||
var discarding: std.Io.Writer.Discarding = .init(&.{});
|
||||
key_encode.encode(
|
||||
&discarding.writer,
|
||||
event_.?.event,
|
||||
encoder_.?.opts,
|
||||
) catch unreachable;
|
||||
|
||||
out_written.* = discarding.count;
|
||||
return .out_of_memory;
|
||||
},
|
||||
};
|
||||
|
||||
out_written.* = writer.end;
|
||||
return .success;
|
||||
}
|
||||
|
||||
test "alloc" {
|
||||
const testing = std.testing;
|
||||
var e: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
free(e);
|
||||
}
|
||||
|
||||
test "setopt bool" {
|
||||
const testing = std.testing;
|
||||
var e: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
|
||||
// Test setting bool options
|
||||
const val_true: bool = true;
|
||||
setopt(e, .cursor_key_application, &val_true);
|
||||
try testing.expect(e.?.opts.cursor_key_application);
|
||||
|
||||
const val_false: bool = false;
|
||||
setopt(e, .cursor_key_application, &val_false);
|
||||
try testing.expect(!e.?.opts.cursor_key_application);
|
||||
|
||||
setopt(e, .keypad_key_application, &val_true);
|
||||
try testing.expect(e.?.opts.keypad_key_application);
|
||||
}
|
||||
|
||||
test "setopt kitty flags" {
|
||||
const testing = std.testing;
|
||||
var e: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
|
||||
// Test setting kitty flags
|
||||
const flags: KittyFlags = .{
|
||||
.disambiguate = true,
|
||||
.report_events = true,
|
||||
};
|
||||
const flags_int: u8 = @intCast(flags.int());
|
||||
setopt(e, .kitty_flags, &flags_int);
|
||||
try testing.expect(e.?.opts.kitty_flags.disambiguate);
|
||||
try testing.expect(e.?.opts.kitty_flags.report_events);
|
||||
try testing.expect(!e.?.opts.kitty_flags.report_alternates);
|
||||
}
|
||||
|
||||
test "setopt macos option as alt" {
|
||||
const testing = std.testing;
|
||||
var e: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
|
||||
// Test setting option as alt
|
||||
const opt_left: OptionAsAlt = .left;
|
||||
setopt(e, .macos_option_as_alt, &opt_left);
|
||||
try testing.expectEqual(OptionAsAlt.left, e.?.opts.macos_option_as_alt);
|
||||
|
||||
const opt_true: OptionAsAlt = .true;
|
||||
setopt(e, .macos_option_as_alt, &opt_true);
|
||||
try testing.expectEqual(OptionAsAlt.true, e.?.opts.macos_option_as_alt);
|
||||
}
|
||||
|
||||
test "encode: kitty ctrl release with ctrl mod set" {
|
||||
const testing = std.testing;
|
||||
|
||||
// Create encoder
|
||||
var encoder: Encoder = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&encoder,
|
||||
));
|
||||
defer free(encoder);
|
||||
|
||||
// Set kitty flags with all features enabled
|
||||
{
|
||||
const flags: KittyFlags = .{
|
||||
.disambiguate = true,
|
||||
.report_events = true,
|
||||
.report_alternates = true,
|
||||
.report_all = true,
|
||||
.report_associated = true,
|
||||
};
|
||||
const flags_int: u8 = @intCast(flags.int());
|
||||
setopt(encoder, .kitty_flags, &flags_int);
|
||||
}
|
||||
|
||||
// Create key event
|
||||
var event: key_event.Event = undefined;
|
||||
try testing.expectEqual(Result.success, key_event.new(
|
||||
&lib_alloc.test_allocator,
|
||||
&event,
|
||||
));
|
||||
defer key_event.free(event);
|
||||
|
||||
// Set event properties: release action, ctrl key, ctrl modifier
|
||||
key_event.set_action(event, .release);
|
||||
key_event.set_key(event, .control_left);
|
||||
key_event.set_mods(event, .{ .ctrl = true });
|
||||
|
||||
// Encode null should give us the length required
|
||||
var required: usize = 0;
|
||||
try testing.expectEqual(Result.out_of_memory, encode(
|
||||
encoder,
|
||||
event,
|
||||
null,
|
||||
0,
|
||||
&required,
|
||||
));
|
||||
|
||||
// Encode the key event
|
||||
var buf: [128]u8 = undefined;
|
||||
var written: usize = 0;
|
||||
try testing.expectEqual(Result.success, encode(
|
||||
encoder,
|
||||
event,
|
||||
&buf,
|
||||
buf.len,
|
||||
&written,
|
||||
));
|
||||
try testing.expectEqual(required, written);
|
||||
|
||||
// Expected: ESC[57442;5:3u (ctrl key code with mods and release event)
|
||||
const actual = buf[0..written];
|
||||
try testing.expectEqualStrings("\x1b[57442;5:3u", actual);
|
||||
}
|
253
src/terminal/c/key_event.zig
Normal file
253
src/terminal/c/key_event.zig
Normal file
@@ -0,0 +1,253 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const lib_alloc = @import("../../lib/allocator.zig");
|
||||
const CAllocator = lib_alloc.Allocator;
|
||||
const key = @import("../../input/key.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
/// Wrapper around KeyEvent that tracks the allocator for C API usage.
|
||||
/// The UTF-8 text is not owned by this wrapper - the caller is responsible
|
||||
/// for ensuring the lifetime of any UTF-8 text set via set_utf8.
|
||||
const KeyEventWrapper = struct {
|
||||
event: key.KeyEvent = .{},
|
||||
alloc: Allocator,
|
||||
};
|
||||
|
||||
/// C: GhosttyKeyEvent
|
||||
pub const Event = ?*KeyEventWrapper;
|
||||
|
||||
pub fn new(
|
||||
alloc_: ?*const CAllocator,
|
||||
result: *Event,
|
||||
) callconv(.c) Result {
|
||||
const alloc = lib_alloc.default(alloc_);
|
||||
const ptr = alloc.create(KeyEventWrapper) 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: key.Action) callconv(.c) void {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.action = action;
|
||||
}
|
||||
|
||||
pub fn get_action(event_: Event) callconv(.c) key.Action {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
return event.action;
|
||||
}
|
||||
|
||||
pub fn set_key(event_: Event, k: key.Key) callconv(.c) void {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.key = k;
|
||||
}
|
||||
|
||||
pub fn get_key(event_: Event) callconv(.c) key.Key {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
return event.key;
|
||||
}
|
||||
|
||||
pub fn set_mods(event_: Event, mods: key.Mods) callconv(.c) void {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.mods = mods;
|
||||
}
|
||||
|
||||
pub fn get_mods(event_: Event) callconv(.c) key.Mods {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
return event.mods;
|
||||
}
|
||||
|
||||
pub fn set_consumed_mods(event_: Event, consumed_mods: key.Mods) callconv(.c) void {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.consumed_mods = consumed_mods;
|
||||
}
|
||||
|
||||
pub fn get_consumed_mods(event_: Event) callconv(.c) key.Mods {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
return event.consumed_mods;
|
||||
}
|
||||
|
||||
pub fn set_composing(event_: Event, composing: bool) callconv(.c) void {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.composing = composing;
|
||||
}
|
||||
|
||||
pub fn get_composing(event_: Event) callconv(.c) bool {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
return event.composing;
|
||||
}
|
||||
|
||||
pub fn set_utf8(event_: Event, utf8: ?[*]const u8, len: usize) callconv(.c) void {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.utf8 = if (utf8) |ptr| ptr[0..len] else "";
|
||||
}
|
||||
|
||||
pub fn get_utf8(event_: Event, len: ?*usize) callconv(.c) ?[*]const u8 {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
if (len) |l| l.* = event.utf8.len;
|
||||
return if (event.utf8.len == 0) null else event.utf8.ptr;
|
||||
}
|
||||
|
||||
pub fn set_unshifted_codepoint(event_: Event, codepoint: u32) callconv(.c) void {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
event.unshifted_codepoint = @truncate(codepoint);
|
||||
}
|
||||
|
||||
pub fn get_unshifted_codepoint(event_: Event) callconv(.c) u32 {
|
||||
const event: *key.KeyEvent = &event_.?.event;
|
||||
return event.unshifted_codepoint;
|
||||
}
|
||||
|
||||
test "alloc" {
|
||||
const testing = std.testing;
|
||||
var e: Event = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
free(e);
|
||||
}
|
||||
|
||||
test "set" {
|
||||
const testing = std.testing;
|
||||
var e: Event = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
|
||||
// Test action
|
||||
set_action(e, .press);
|
||||
try testing.expectEqual(key.Action.press, e.?.event.action);
|
||||
|
||||
// Test key
|
||||
set_key(e, .key_a);
|
||||
try testing.expectEqual(key.Key.key_a, e.?.event.key);
|
||||
|
||||
// Test mods
|
||||
const mods: key.Mods = .{ .shift = true, .ctrl = true };
|
||||
set_mods(e, mods);
|
||||
try testing.expect(e.?.event.mods.shift);
|
||||
try testing.expect(e.?.event.mods.ctrl);
|
||||
|
||||
// Test consumed mods
|
||||
const consumed: key.Mods = .{ .shift = true };
|
||||
set_consumed_mods(e, consumed);
|
||||
try testing.expect(e.?.event.consumed_mods.shift);
|
||||
try testing.expect(!e.?.event.consumed_mods.ctrl);
|
||||
|
||||
// Test composing
|
||||
set_composing(e, true);
|
||||
try testing.expect(e.?.event.composing);
|
||||
|
||||
// Test UTF-8
|
||||
const text = "hello";
|
||||
set_utf8(e, text.ptr, text.len);
|
||||
try testing.expectEqualStrings(text, e.?.event.utf8);
|
||||
|
||||
// Test UTF-8 null
|
||||
set_utf8(e, null, 0);
|
||||
try testing.expectEqualStrings("", e.?.event.utf8);
|
||||
|
||||
// Test unshifted codepoint
|
||||
set_unshifted_codepoint(e, 'a');
|
||||
try testing.expectEqual(@as(u21, 'a'), e.?.event.unshifted_codepoint);
|
||||
}
|
||||
|
||||
test "get" {
|
||||
const testing = std.testing;
|
||||
var e: Event = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
|
||||
// Set some values
|
||||
set_action(e, .repeat);
|
||||
set_key(e, .key_z);
|
||||
|
||||
const mods: key.Mods = .{ .alt = true, .super = true };
|
||||
set_mods(e, mods);
|
||||
|
||||
const consumed: key.Mods = .{ .alt = true };
|
||||
set_consumed_mods(e, consumed);
|
||||
|
||||
set_composing(e, true);
|
||||
|
||||
const text = "test";
|
||||
set_utf8(e, text.ptr, text.len);
|
||||
|
||||
set_unshifted_codepoint(e, 'z');
|
||||
|
||||
// Get them back
|
||||
try testing.expectEqual(key.Action.repeat, get_action(e));
|
||||
try testing.expectEqual(key.Key.key_z, get_key(e));
|
||||
|
||||
const got_mods = get_mods(e);
|
||||
try testing.expect(got_mods.alt);
|
||||
try testing.expect(got_mods.super);
|
||||
|
||||
const got_consumed = get_consumed_mods(e);
|
||||
try testing.expect(got_consumed.alt);
|
||||
try testing.expect(!got_consumed.super);
|
||||
|
||||
try testing.expect(get_composing(e));
|
||||
|
||||
var utf8_len: usize = undefined;
|
||||
const got_utf8 = get_utf8(e, &utf8_len);
|
||||
try testing.expect(got_utf8 != null);
|
||||
try testing.expectEqual(@as(usize, 4), utf8_len);
|
||||
try testing.expectEqualStrings("test", got_utf8.?[0..utf8_len]);
|
||||
|
||||
try testing.expectEqual(@as(u32, 'z'), get_unshifted_codepoint(e));
|
||||
}
|
||||
|
||||
test "complete key event" {
|
||||
const testing = std.testing;
|
||||
var e: Event = undefined;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&e,
|
||||
));
|
||||
defer free(e);
|
||||
|
||||
// Build a complete key event for shift+a
|
||||
set_action(e, .press);
|
||||
set_key(e, .key_a);
|
||||
|
||||
const mods: key.Mods = .{ .shift = true };
|
||||
set_mods(e, mods);
|
||||
|
||||
const consumed: key.Mods = .{ .shift = true };
|
||||
set_consumed_mods(e, consumed);
|
||||
|
||||
const text = "A";
|
||||
set_utf8(e, text.ptr, text.len);
|
||||
|
||||
set_unshifted_codepoint(e, 'a');
|
||||
|
||||
// Verify all fields
|
||||
try testing.expectEqual(key.Action.press, e.?.event.action);
|
||||
try testing.expectEqual(key.Key.key_a, e.?.event.key);
|
||||
try testing.expect(e.?.event.mods.shift);
|
||||
try testing.expect(e.?.event.consumed_mods.shift);
|
||||
try testing.expectEqualStrings("A", e.?.event.utf8);
|
||||
try testing.expectEqual(@as(u21, 'a'), e.?.event.unshifted_codepoint);
|
||||
|
||||
// Also test the getter
|
||||
var utf8_len: usize = undefined;
|
||||
const got_utf8 = get_utf8(e, &utf8_len);
|
||||
try testing.expect(got_utf8 != null);
|
||||
try testing.expectEqual(@as(usize, 1), utf8_len);
|
||||
try testing.expectEqualStrings("A", got_utf8.?[0..utf8_len]);
|
||||
}
|
@@ -1,4 +1,6 @@
|
||||
pub const osc = @import("osc.zig");
|
||||
pub const key_event = @import("key_event.zig");
|
||||
pub const key_encode = @import("key_encode.zig");
|
||||
|
||||
// The full C API, unexported.
|
||||
pub const osc_new = osc.new;
|
||||
@@ -9,8 +11,32 @@ pub const osc_end = osc.end;
|
||||
pub const osc_command_type = osc.commandType;
|
||||
pub const osc_command_data = osc.commandData;
|
||||
|
||||
pub const key_event_new = key_event.new;
|
||||
pub const key_event_free = key_event.free;
|
||||
pub const key_event_set_action = key_event.set_action;
|
||||
pub const key_event_get_action = key_event.get_action;
|
||||
pub const key_event_set_key = key_event.set_key;
|
||||
pub const key_event_get_key = key_event.get_key;
|
||||
pub const key_event_set_mods = key_event.set_mods;
|
||||
pub const key_event_get_mods = key_event.get_mods;
|
||||
pub const key_event_set_consumed_mods = key_event.set_consumed_mods;
|
||||
pub const key_event_get_consumed_mods = key_event.get_consumed_mods;
|
||||
pub const key_event_set_composing = key_event.set_composing;
|
||||
pub const key_event_get_composing = key_event.get_composing;
|
||||
pub const key_event_set_utf8 = key_event.set_utf8;
|
||||
pub const key_event_get_utf8 = key_event.get_utf8;
|
||||
pub const key_event_set_unshifted_codepoint = key_event.set_unshifted_codepoint;
|
||||
pub const key_event_get_unshifted_codepoint = key_event.get_unshifted_codepoint;
|
||||
|
||||
pub const key_encoder_new = key_encode.new;
|
||||
pub const key_encoder_free = key_encode.free;
|
||||
pub const key_encoder_setopt = key_encode.setopt;
|
||||
pub const key_encoder_encode = key_encode.encode;
|
||||
|
||||
test {
|
||||
_ = osc;
|
||||
_ = key_event;
|
||||
_ = key_encode;
|
||||
|
||||
// We want to make sure we run the tests for the C allocator interface.
|
||||
_ = @import("../../lib/allocator.zig");
|
||||
|
Reference in New Issue
Block a user