mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
vt: expose terminal modes to C API
Add modes.h with GhosttyModeTag (uint16_t) matching the Zig ModeTag packed struct layout, along with inline helpers for constructing and inspecting mode tags. Provide GHOSTTY_MODE_* macros for all 39 built-in modes (4 ANSI, 35 DEC), parenthesized for safety. Add ghostty_terminal_mode_get and ghostty_terminal_mode_set to terminal.h, both returning GhosttyResult so that null terminals and unknown mode tags return GHOSTTY_INVALID_VALUE. The get function writes its result through a bool out-parameter. Add a note in the Zig mode entries reminding developers to update modes.h when adding new modes.
This commit is contained in:
@@ -53,6 +53,58 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @name ANSI Mode Tags
|
||||
* Mode tags for standard ANSI modes.
|
||||
* @{
|
||||
*/
|
||||
#define GHOSTTY_MODE_KAM (ghostty_mode_tag_new(2, true)) /**< Keyboard action (disable keyboard) */
|
||||
#define GHOSTTY_MODE_INSERT (ghostty_mode_tag_new(4, true)) /**< Insert mode */
|
||||
#define GHOSTTY_MODE_SRM (ghostty_mode_tag_new(12, true)) /**< Send/receive mode */
|
||||
#define GHOSTTY_MODE_LINEFEED (ghostty_mode_tag_new(20, true)) /**< Linefeed/new line mode */
|
||||
/** @} */
|
||||
|
||||
/** @name DEC Private Mode Tags
|
||||
* Mode tags for DEC private modes (?-prefixed).
|
||||
* @{
|
||||
*/
|
||||
#define GHOSTTY_MODE_DECCKM (ghostty_mode_tag_new(1, false)) /**< Cursor keys */
|
||||
#define GHOSTTY_MODE_132_COLUMN (ghostty_mode_tag_new(3, false)) /**< 132/80 column mode */
|
||||
#define GHOSTTY_MODE_SLOW_SCROLL (ghostty_mode_tag_new(4, false)) /**< Slow scroll */
|
||||
#define GHOSTTY_MODE_REVERSE_COLORS (ghostty_mode_tag_new(5, false)) /**< Reverse video */
|
||||
#define GHOSTTY_MODE_ORIGIN (ghostty_mode_tag_new(6, false)) /**< Origin mode */
|
||||
#define GHOSTTY_MODE_WRAPAROUND (ghostty_mode_tag_new(7, false)) /**< Auto-wrap mode */
|
||||
#define GHOSTTY_MODE_AUTOREPEAT (ghostty_mode_tag_new(8, false)) /**< Auto-repeat keys */
|
||||
#define GHOSTTY_MODE_X10_MOUSE (ghostty_mode_tag_new(9, false)) /**< X10 mouse reporting */
|
||||
#define GHOSTTY_MODE_CURSOR_BLINKING (ghostty_mode_tag_new(12, false)) /**< Cursor blink */
|
||||
#define GHOSTTY_MODE_CURSOR_VISIBLE (ghostty_mode_tag_new(25, false)) /**< Cursor visible (DECTCEM) */
|
||||
#define GHOSTTY_MODE_ENABLE_MODE_3 (ghostty_mode_tag_new(40, false)) /**< Allow 132 column mode */
|
||||
#define GHOSTTY_MODE_REVERSE_WRAP (ghostty_mode_tag_new(45, false)) /**< Reverse wrap */
|
||||
#define GHOSTTY_MODE_ALT_SCREEN_LEGACY (ghostty_mode_tag_new(47, false)) /**< Alternate screen (legacy) */
|
||||
#define GHOSTTY_MODE_KEYPAD_KEYS (ghostty_mode_tag_new(66, false)) /**< Application keypad */
|
||||
#define GHOSTTY_MODE_LEFT_RIGHT_MARGIN (ghostty_mode_tag_new(69, false)) /**< Left/right margin mode */
|
||||
#define GHOSTTY_MODE_NORMAL_MOUSE (ghostty_mode_tag_new(1000, false)) /**< Normal mouse tracking */
|
||||
#define GHOSTTY_MODE_BUTTON_MOUSE (ghostty_mode_tag_new(1002, false)) /**< Button-event mouse tracking */
|
||||
#define GHOSTTY_MODE_ANY_MOUSE (ghostty_mode_tag_new(1003, false)) /**< Any-event mouse tracking */
|
||||
#define GHOSTTY_MODE_FOCUS_EVENT (ghostty_mode_tag_new(1004, false)) /**< Focus in/out events */
|
||||
#define GHOSTTY_MODE_UTF8_MOUSE (ghostty_mode_tag_new(1005, false)) /**< UTF-8 mouse format */
|
||||
#define GHOSTTY_MODE_SGR_MOUSE (ghostty_mode_tag_new(1006, false)) /**< SGR mouse format */
|
||||
#define GHOSTTY_MODE_ALT_SCROLL (ghostty_mode_tag_new(1007, false)) /**< Alternate scroll mode */
|
||||
#define GHOSTTY_MODE_URXVT_MOUSE (ghostty_mode_tag_new(1015, false)) /**< URxvt mouse format */
|
||||
#define GHOSTTY_MODE_SGR_PIXELS_MOUSE (ghostty_mode_tag_new(1016, false)) /**< SGR-Pixels mouse format */
|
||||
#define GHOSTTY_MODE_NUMLOCK_KEYPAD (ghostty_mode_tag_new(1035, false)) /**< Ignore keypad with NumLock */
|
||||
#define GHOSTTY_MODE_ALT_ESC_PREFIX (ghostty_mode_tag_new(1036, false)) /**< Alt key sends ESC prefix */
|
||||
#define GHOSTTY_MODE_ALT_SENDS_ESC (ghostty_mode_tag_new(1039, false)) /**< Alt sends escape */
|
||||
#define GHOSTTY_MODE_REVERSE_WRAP_EXT (ghostty_mode_tag_new(1045, false)) /**< Extended reverse wrap */
|
||||
#define GHOSTTY_MODE_ALT_SCREEN (ghostty_mode_tag_new(1047, false)) /**< Alternate screen */
|
||||
#define GHOSTTY_MODE_SAVE_CURSOR (ghostty_mode_tag_new(1048, false)) /**< Save cursor (DECSC) */
|
||||
#define GHOSTTY_MODE_ALT_SCREEN_SAVE (ghostty_mode_tag_new(1049, false)) /**< Alt screen + save cursor + clear */
|
||||
#define GHOSTTY_MODE_BRACKETED_PASTE (ghostty_mode_tag_new(2004, false)) /**< Bracketed paste mode */
|
||||
#define GHOSTTY_MODE_SYNC_OUTPUT (ghostty_mode_tag_new(2026, false)) /**< Synchronized output */
|
||||
#define GHOSTTY_MODE_GRAPHEME_CLUSTER (ghostty_mode_tag_new(2027, false)) /**< Grapheme cluster mode */
|
||||
#define GHOSTTY_MODE_COLOR_SCHEME_REPORT (ghostty_mode_tag_new(2031, false)) /**< Report color scheme */
|
||||
#define GHOSTTY_MODE_IN_BAND_RESIZE (ghostty_mode_tag_new(2048, false)) /**< In-band size reports */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* A packed 16-bit terminal mode tag.
|
||||
*
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
#ifndef GHOSTTY_VT_TERMINAL_H
|
||||
#define GHOSTTY_VT_TERMINAL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/modes.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -192,7 +194,42 @@ void ghostty_terminal_vt_write(GhosttyTerminal terminal,
|
||||
* @ingroup terminal
|
||||
*/
|
||||
void ghostty_terminal_scroll_viewport(GhosttyTerminal terminal,
|
||||
GhosttyTerminalScrollViewport behavior);
|
||||
GhosttyTerminalScrollViewport behavior);
|
||||
|
||||
/**
|
||||
* Get the current value of a terminal mode.
|
||||
*
|
||||
* Returns the value of the mode identified by the given mode tag.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param tag The mode tag identifying the mode to query
|
||||
* @param[out] out_value On success, set to true if the mode is set, false
|
||||
* if it is reset
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
|
||||
* is NULL or the tag does not correspond to a known mode
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GhosttyResult ghostty_terminal_mode_get(GhosttyTerminal terminal,
|
||||
GhosttyModeTag tag,
|
||||
bool* out_value);
|
||||
|
||||
/**
|
||||
* Set the value of a terminal mode.
|
||||
*
|
||||
* Sets the mode identified by the given mode tag to the specified value.
|
||||
*
|
||||
* @param terminal The terminal handle (NULL returns GHOSTTY_INVALID_VALUE)
|
||||
* @param tag The mode tag identifying the mode to set
|
||||
* @param value true to set the mode, false to reset it
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the terminal
|
||||
* is NULL or the tag does not correspond to a known mode
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
GhosttyResult ghostty_terminal_mode_set(GhosttyTerminal terminal,
|
||||
GhosttyModeTag tag,
|
||||
bool value);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
@@ -186,6 +186,8 @@ comptime {
|
||||
@export(&c.terminal_resize, .{ .name = "ghostty_terminal_resize" });
|
||||
@export(&c.terminal_vt_write, .{ .name = "ghostty_terminal_vt_write" });
|
||||
@export(&c.terminal_scroll_viewport, .{ .name = "ghostty_terminal_scroll_viewport" });
|
||||
@export(&c.terminal_mode_get, .{ .name = "ghostty_terminal_mode_get" });
|
||||
@export(&c.terminal_mode_set, .{ .name = "ghostty_terminal_mode_set" });
|
||||
|
||||
// On Wasm we need to export our allocator convenience functions.
|
||||
if (builtin.target.cpu.arch.isWasm()) {
|
||||
|
||||
@@ -90,6 +90,8 @@ pub const terminal_reset = terminal.reset;
|
||||
pub const terminal_resize = terminal.resize;
|
||||
pub const terminal_vt_write = terminal.vt_write;
|
||||
pub const terminal_scroll_viewport = terminal.scroll_viewport;
|
||||
pub const terminal_mode_get = terminal.mode_get;
|
||||
pub const terminal_mode_set = terminal.mode_set;
|
||||
|
||||
test {
|
||||
_ = color;
|
||||
|
||||
@@ -3,6 +3,7 @@ const testing = std.testing;
|
||||
const lib_alloc = @import("../../lib/allocator.zig");
|
||||
const CAllocator = lib_alloc.Allocator;
|
||||
const ZigTerminal = @import("../Terminal.zig");
|
||||
const modes = @import("../modes.zig");
|
||||
const size = @import("../size.zig");
|
||||
const Result = @import("result.zig").Result;
|
||||
|
||||
@@ -98,6 +99,30 @@ pub fn reset(terminal_: Terminal) callconv(.c) void {
|
||||
t.fullReset();
|
||||
}
|
||||
|
||||
pub fn mode_get(
|
||||
terminal_: Terminal,
|
||||
tag: modes.ModeTag.Backing,
|
||||
out_value: *bool,
|
||||
) callconv(.c) Result {
|
||||
const t = terminal_ orelse return .invalid_value;
|
||||
const mode_tag: modes.ModeTag = @bitCast(tag);
|
||||
const mode = modes.modeFromInt(mode_tag.value, mode_tag.ansi) orelse return .invalid_value;
|
||||
out_value.* = t.modes.get(mode);
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn mode_set(
|
||||
terminal_: Terminal,
|
||||
tag: modes.ModeTag.Backing,
|
||||
value: bool,
|
||||
) callconv(.c) Result {
|
||||
const t = terminal_ orelse return .invalid_value;
|
||||
const mode_tag: modes.ModeTag = @bitCast(tag);
|
||||
const mode = modes.modeFromInt(mode_tag.value, mode_tag.ansi) orelse return .invalid_value;
|
||||
t.modes.set(mode, value);
|
||||
return .success;
|
||||
}
|
||||
|
||||
pub fn free(terminal_: Terminal) callconv(.c) void {
|
||||
const t = terminal_ orelse return;
|
||||
|
||||
@@ -272,6 +297,87 @@ test "resize invalid value" {
|
||||
try testing.expectEqual(Result.invalid_value, resize(t, 80, 0));
|
||||
}
|
||||
|
||||
test "mode_get and mode_set" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
var value: bool = undefined;
|
||||
|
||||
// DEC mode 25 (cursor_visible) defaults to true
|
||||
const cursor_visible: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 25, .ansi = false });
|
||||
try testing.expectEqual(Result.success, mode_get(t, cursor_visible, &value));
|
||||
try testing.expect(value);
|
||||
|
||||
// Set it to false
|
||||
try testing.expectEqual(Result.success, mode_set(t, cursor_visible, false));
|
||||
try testing.expectEqual(Result.success, mode_get(t, cursor_visible, &value));
|
||||
try testing.expect(!value);
|
||||
|
||||
// ANSI mode 4 (insert) defaults to false
|
||||
const insert: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 4, .ansi = true });
|
||||
try testing.expectEqual(Result.success, mode_get(t, insert, &value));
|
||||
try testing.expect(!value);
|
||||
|
||||
try testing.expectEqual(Result.success, mode_set(t, insert, true));
|
||||
try testing.expectEqual(Result.success, mode_get(t, insert, &value));
|
||||
try testing.expect(value);
|
||||
}
|
||||
|
||||
test "mode_get null" {
|
||||
var value: bool = undefined;
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 25, .ansi = false });
|
||||
try testing.expectEqual(Result.invalid_value, mode_get(null, tag, &value));
|
||||
}
|
||||
|
||||
test "mode_set null" {
|
||||
const tag: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 25, .ansi = false });
|
||||
try testing.expectEqual(Result.invalid_value, mode_set(null, tag, true));
|
||||
}
|
||||
|
||||
test "mode_get unknown mode" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
var value: bool = undefined;
|
||||
const unknown: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 9999, .ansi = false });
|
||||
try testing.expectEqual(Result.invalid_value, mode_get(t, unknown, &value));
|
||||
}
|
||||
|
||||
test "mode_set unknown mode" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
&lib_alloc.test_allocator,
|
||||
&t,
|
||||
.{
|
||||
.cols = 80,
|
||||
.rows = 24,
|
||||
.max_scrollback = 0,
|
||||
},
|
||||
));
|
||||
defer free(t);
|
||||
|
||||
const unknown: modes.ModeTag.Backing = @bitCast(modes.ModeTag{ .value = 9999, .ansi = false });
|
||||
try testing.expectEqual(Result.invalid_value, mode_set(t, unknown, true));
|
||||
}
|
||||
|
||||
test "vt_write" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
@@ -245,6 +245,9 @@ const ModeEntry = struct {
|
||||
/// The full list of available entries. For documentation see how
|
||||
/// they're used within Ghostty or google their values. It is not
|
||||
/// valuable to redocument them all here.
|
||||
///
|
||||
/// NOTE: When adding a new mode entry, also add a corresponding
|
||||
/// GHOSTTY_MODE_* macro in include/ghostty/vt/modes.h.
|
||||
const entries: []const ModeEntry = &.{
|
||||
// ANSI
|
||||
.{ .name = "disable_keyboard", .value = 2, .ansi = true }, // KAM
|
||||
|
||||
Reference in New Issue
Block a user