mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
vt: expose device_attributes effect in the C API
Rename device_status.h to device.h and add C-compatible structs for device attributes (DA1/DA2/DA3) responses. The new header includes defines for all known conformance levels, DA1 feature codes, and DA2 device type identifiers. Add a GhosttyTerminalDeviceAttributesFn callback that C consumers can set via GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES. The callback follows the existing bool + out-pointer pattern used by color_scheme and size callbacks. When the callback is unset or returns false, the trampoline returns a default VT220 response (conformance level 62, ANSI color). The DA1 primary features use a fixed [64]uint16_t inline array with a num_features count rather than a pointer, so the entire struct is value-typed and can be safely copied without lifetime concerns.
This commit is contained in:
@@ -109,7 +109,7 @@ extern "C" {
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/build_info.h>
|
||||
#include <ghostty/vt/color.h>
|
||||
#include <ghostty/vt/device_status.h>
|
||||
#include <ghostty/vt/device.h>
|
||||
#include <ghostty/vt/focus.h>
|
||||
#include <ghostty/vt/formatter.h>
|
||||
#include <ghostty/vt/render.h>
|
||||
|
||||
150
include/ghostty/vt/device.h
Normal file
150
include/ghostty/vt/device.h
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* @file device.h
|
||||
*
|
||||
* Device types used by the terminal for device status and device attribute
|
||||
* queries.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_DEVICE_H
|
||||
#define GHOSTTY_VT_DEVICE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* DA1 conformance levels (Pp parameter). */
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT100 1
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT101 1
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT102 6
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT125 12
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT131 7
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT132 4
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT220 62
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT240 62
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT320 63
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT340 63
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT420 64
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT510 65
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT520 65
|
||||
#define GHOSTTY_DA_CONFORMANCE_VT525 65
|
||||
#define GHOSTTY_DA_CONFORMANCE_LEVEL_2 62
|
||||
#define GHOSTTY_DA_CONFORMANCE_LEVEL_3 63
|
||||
#define GHOSTTY_DA_CONFORMANCE_LEVEL_4 64
|
||||
#define GHOSTTY_DA_CONFORMANCE_LEVEL_5 65
|
||||
|
||||
/* DA1 feature codes (Ps parameters). */
|
||||
#define GHOSTTY_DA_FEATURE_COLUMNS_132 1
|
||||
#define GHOSTTY_DA_FEATURE_PRINTER 2
|
||||
#define GHOSTTY_DA_FEATURE_REGIS 3
|
||||
#define GHOSTTY_DA_FEATURE_SIXEL 4
|
||||
#define GHOSTTY_DA_FEATURE_SELECTIVE_ERASE 6
|
||||
#define GHOSTTY_DA_FEATURE_USER_DEFINED_KEYS 8
|
||||
#define GHOSTTY_DA_FEATURE_NATIONAL_REPLACEMENT 9
|
||||
#define GHOSTTY_DA_FEATURE_TECHNICAL_CHARACTERS 15
|
||||
#define GHOSTTY_DA_FEATURE_LOCATOR 16
|
||||
#define GHOSTTY_DA_FEATURE_TERMINAL_STATE 17
|
||||
#define GHOSTTY_DA_FEATURE_WINDOWING 18
|
||||
#define GHOSTTY_DA_FEATURE_HORIZONTAL_SCROLLING 21
|
||||
#define GHOSTTY_DA_FEATURE_ANSI_COLOR 22
|
||||
#define GHOSTTY_DA_FEATURE_RECTANGULAR_EDITING 28
|
||||
#define GHOSTTY_DA_FEATURE_ANSI_TEXT_LOCATOR 29
|
||||
#define GHOSTTY_DA_FEATURE_CLIPBOARD 52
|
||||
|
||||
/* DA2 device type identifiers (Pp parameter). */
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT100 0
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT220 1
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT240 2
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT330 18
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT340 19
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT320 24
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT382 32
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT420 41
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT510 61
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT520 64
|
||||
#define GHOSTTY_DA_DEVICE_TYPE_VT525 65
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Color scheme reported in response to a CSI ? 996 n query.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef enum {
|
||||
GHOSTTY_COLOR_SCHEME_LIGHT = 0,
|
||||
GHOSTTY_COLOR_SCHEME_DARK = 1,
|
||||
} GhosttyColorScheme;
|
||||
|
||||
/**
|
||||
* Primary device attributes (DA1) response data.
|
||||
*
|
||||
* Returned as part of GhosttyDeviceAttributes in response to a CSI c query.
|
||||
* The conformance_level is the Pp parameter and features contains the Ps
|
||||
* feature codes.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef struct {
|
||||
/** Conformance level (Pp parameter). E.g. 62 for VT220. */
|
||||
uint16_t conformance_level;
|
||||
|
||||
/** DA1 feature codes. Only the first num_features entries are valid. */
|
||||
uint16_t features[64];
|
||||
|
||||
/** Number of valid entries in the features array. */
|
||||
size_t num_features;
|
||||
} GhosttyDeviceAttributesPrimary;
|
||||
|
||||
/**
|
||||
* Secondary device attributes (DA2) response data.
|
||||
*
|
||||
* Returned as part of GhosttyDeviceAttributes in response to a CSI > c query.
|
||||
* Response format: CSI > Pp ; Pv ; Pc c
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef struct {
|
||||
/** Terminal type identifier (Pp). E.g. 1 for VT220. */
|
||||
uint16_t device_type;
|
||||
|
||||
/** Firmware/patch version number (Pv). */
|
||||
uint16_t firmware_version;
|
||||
|
||||
/** ROM cartridge registration number (Pc). Always 0 for emulators. */
|
||||
uint16_t rom_cartridge;
|
||||
} GhosttyDeviceAttributesSecondary;
|
||||
|
||||
/**
|
||||
* Tertiary device attributes (DA3) response data.
|
||||
*
|
||||
* Returned as part of GhosttyDeviceAttributes in response to a CSI = c query.
|
||||
* Response format: DCS ! | D...D ST (DECRPTUI).
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef struct {
|
||||
/** Unit ID encoded as 8 uppercase hex digits in the response. */
|
||||
uint32_t unit_id;
|
||||
} GhosttyDeviceAttributesTertiary;
|
||||
|
||||
/**
|
||||
* Device attributes response data for all three DA levels.
|
||||
*
|
||||
* Filled by the device_attributes callback in response to CSI c,
|
||||
* CSI > c, or CSI = c queries. The terminal uses whichever sub-struct
|
||||
* matches the request type.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef struct {
|
||||
GhosttyDeviceAttributesPrimary primary;
|
||||
GhosttyDeviceAttributesSecondary secondary;
|
||||
GhosttyDeviceAttributesTertiary tertiary;
|
||||
} GhosttyDeviceAttributes;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GHOSTTY_VT_DEVICE_H */
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @file device_status.h
|
||||
*
|
||||
* Device status types used by the terminal.
|
||||
*/
|
||||
|
||||
#ifndef GHOSTTY_VT_DEVICE_STATUS_H
|
||||
#define GHOSTTY_VT_DEVICE_STATUS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Color scheme reported in response to a CSI ? 996 n query.
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef enum {
|
||||
GHOSTTY_COLOR_SCHEME_LIGHT = 0,
|
||||
GHOSTTY_COLOR_SCHEME_DARK = 1,
|
||||
} GhosttyColorScheme;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GHOSTTY_VT_DEVICE_STATUS_H */
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
#include <ghostty/vt/allocator.h>
|
||||
#include <ghostty/vt/device_status.h>
|
||||
#include <ghostty/vt/device.h>
|
||||
#include <ghostty/vt/modes.h>
|
||||
#include <ghostty/vt/size_report.h>
|
||||
#include <ghostty/vt/grid_ref.h>
|
||||
@@ -236,6 +236,27 @@ typedef bool (*GhosttyTerminalColorSchemeFn)(GhosttyTerminal terminal,
|
||||
void* userdata,
|
||||
GhosttyColorScheme* out_scheme);
|
||||
|
||||
/**
|
||||
* Callback function type for device attributes queries (DA1/DA2/DA3).
|
||||
*
|
||||
* Called when the terminal receives a device attributes query (CSI c,
|
||||
* CSI > c, or CSI = c). Return true and fill *out_attrs with the
|
||||
* response data, or return false to silently ignore the query.
|
||||
*
|
||||
* The terminal uses whichever sub-struct (primary, secondary, tertiary)
|
||||
* matches the request type, but all three should be filled for simplicity.
|
||||
*
|
||||
* @param terminal The terminal handle
|
||||
* @param userdata The userdata pointer set via GHOSTTY_TERMINAL_OPT_USERDATA
|
||||
* @param[out] out_attrs Pointer to store the device attributes response
|
||||
* @return true if attributes were filled, false to ignore the query
|
||||
*
|
||||
* @ingroup terminal
|
||||
*/
|
||||
typedef bool (*GhosttyTerminalDeviceAttributesFn)(GhosttyTerminal terminal,
|
||||
void* userdata,
|
||||
GhosttyDeviceAttributes* out_attrs);
|
||||
|
||||
/**
|
||||
* Callback function type for size queries (XTWINOPS).
|
||||
*
|
||||
@@ -329,6 +350,16 @@ typedef enum {
|
||||
* Input type: GhosttyTerminalColorSchemeFn*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_COLOR_SCHEME = 7,
|
||||
|
||||
/**
|
||||
* Callback invoked in response to a device attributes query
|
||||
* (CSI c, CSI > c, or CSI = c). Return true and fill the out
|
||||
* pointer with response data, or return false to silently ignore.
|
||||
* Set to NULL to ignore device attributes queries.
|
||||
*
|
||||
* Input type: GhosttyTerminalDeviceAttributesFn*
|
||||
*/
|
||||
GHOSTTY_TERMINAL_OPT_DEVICE_ATTRIBUTES = 8,
|
||||
} GhosttyTerminalOption;
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ const kitty = @import("../kitty/key.zig");
|
||||
const modes = @import("../modes.zig");
|
||||
const point = @import("../point.zig");
|
||||
const size = @import("../size.zig");
|
||||
const device_attributes = @import("../device_attributes.zig");
|
||||
const device_status = @import("../device_status.zig");
|
||||
const size_report = @import("../size_report.zig");
|
||||
const cell_c = @import("cell.zig");
|
||||
@@ -40,6 +41,7 @@ const Effects = struct {
|
||||
write_pty: ?WritePtyFn = null,
|
||||
bell: ?BellFn = null,
|
||||
color_scheme: ?ColorSchemeFn = null,
|
||||
device_attributes_cb: ?DeviceAttributesFn = null,
|
||||
enquiry: ?EnquiryFn = null,
|
||||
xtversion: ?XtversionFn = null,
|
||||
title_changed: ?TitleChangedFn = null,
|
||||
@@ -75,6 +77,35 @@ const Effects = struct {
|
||||
/// or returns false to silently ignore the query.
|
||||
pub const SizeFn = *const fn (Terminal, ?*anyopaque, *size_report.Size) callconv(.c) bool;
|
||||
|
||||
/// C function pointer type for the device_attributes callback.
|
||||
/// Returns true and fills out_attrs if attributes are available,
|
||||
/// or returns false to silently ignore the query.
|
||||
pub const DeviceAttributesFn = *const fn (Terminal, ?*anyopaque, *CDeviceAttributes) callconv(.c) bool;
|
||||
|
||||
/// C-compatible device attributes struct.
|
||||
/// C: GhosttyDeviceAttributes
|
||||
pub const CDeviceAttributes = extern struct {
|
||||
primary: Primary,
|
||||
secondary: Secondary,
|
||||
tertiary: Tertiary,
|
||||
|
||||
pub const Primary = extern struct {
|
||||
conformance_level: u16,
|
||||
features: [64]u16,
|
||||
num_features: usize,
|
||||
};
|
||||
|
||||
pub const Secondary = extern struct {
|
||||
device_type: u16,
|
||||
firmware_version: u16,
|
||||
rom_cartridge: u16,
|
||||
};
|
||||
|
||||
pub const Tertiary = extern struct {
|
||||
unit_id: u32,
|
||||
};
|
||||
};
|
||||
|
||||
fn writePtyTrampoline(handler: *Handler, data: [:0]const u8) void {
|
||||
const stream_ptr: *Stream = @fieldParentPtr("handler", handler);
|
||||
const wrapper: *TerminalWrapper = @fieldParentPtr("stream", stream_ptr);
|
||||
@@ -98,6 +129,38 @@ const Effects = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn deviceAttributesTrampoline(handler: *Handler) device_attributes.Attributes {
|
||||
const stream_ptr: *Stream = @fieldParentPtr("handler", handler);
|
||||
const wrapper: *TerminalWrapper = @fieldParentPtr("stream", stream_ptr);
|
||||
const func = wrapper.effects.device_attributes_cb orelse return .{};
|
||||
|
||||
// Get our attributes from the callback.
|
||||
var c_attrs: CDeviceAttributes = undefined;
|
||||
if (!func(@ptrCast(wrapper), wrapper.effects.userdata, &c_attrs)) return .{};
|
||||
|
||||
// Note below we use a lot of enumFromInt but its always safe
|
||||
// because all our types are non-exhaustive enums.
|
||||
|
||||
const n: usize = @min(c_attrs.primary.num_features, 64);
|
||||
var features: [64]device_attributes.Primary.Feature = undefined;
|
||||
for (0..n) |i| features[i] = @enumFromInt(c_attrs.primary.features[i]);
|
||||
|
||||
return .{
|
||||
.primary = .{
|
||||
.conformance_level = @enumFromInt(c_attrs.primary.conformance_level),
|
||||
.features = features[0..n],
|
||||
},
|
||||
.secondary = .{
|
||||
.device_type = @enumFromInt(c_attrs.secondary.device_type),
|
||||
.firmware_version = c_attrs.secondary.firmware_version,
|
||||
.rom_cartridge = c_attrs.secondary.rom_cartridge,
|
||||
},
|
||||
.tertiary = .{
|
||||
.unit_id = c_attrs.tertiary.unit_id,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn enquiryTrampoline(handler: *Handler) []const u8 {
|
||||
const stream_ptr: *Stream = @fieldParentPtr("handler", handler);
|
||||
const wrapper: *TerminalWrapper = @fieldParentPtr("stream", stream_ptr);
|
||||
@@ -193,6 +256,7 @@ fn new_(
|
||||
handler.effects.write_pty = &Effects.writePtyTrampoline;
|
||||
handler.effects.bell = &Effects.bellTrampoline;
|
||||
handler.effects.color_scheme = &Effects.colorSchemeTrampoline;
|
||||
handler.effects.device_attributes = &Effects.deviceAttributesTrampoline;
|
||||
handler.effects.enquiry = &Effects.enquiryTrampoline;
|
||||
handler.effects.xtversion = &Effects.xtversionTrampoline;
|
||||
handler.effects.title_changed = &Effects.titleChangedTrampoline;
|
||||
@@ -225,6 +289,7 @@ pub const Option = enum(c_int) {
|
||||
title_changed = 5,
|
||||
size_cb = 6,
|
||||
color_scheme = 7,
|
||||
device_attributes = 8,
|
||||
|
||||
/// Input type expected for setting the option.
|
||||
pub fn InType(comptime self: Option) type {
|
||||
@@ -233,6 +298,7 @@ pub const Option = enum(c_int) {
|
||||
.write_pty => ?Effects.WritePtyFn,
|
||||
.bell => ?Effects.BellFn,
|
||||
.color_scheme => ?Effects.ColorSchemeFn,
|
||||
.device_attributes => ?Effects.DeviceAttributesFn,
|
||||
.enquiry => ?Effects.EnquiryFn,
|
||||
.xtversion => ?Effects.XtversionFn,
|
||||
.title_changed => ?Effects.TitleChangedFn,
|
||||
@@ -273,6 +339,7 @@ fn setTyped(
|
||||
.write_pty => wrapper.effects.write_pty = if (value) |v| v.* else null,
|
||||
.bell => wrapper.effects.bell = if (value) |v| v.* else null,
|
||||
.color_scheme => wrapper.effects.color_scheme = if (value) |v| v.* else null,
|
||||
.device_attributes => wrapper.effects.device_attributes_cb = if (value) |v| v.* else null,
|
||||
.enquiry => wrapper.effects.enquiry = if (value) |v| v.* else null,
|
||||
.xtversion => wrapper.effects.xtversion = if (value) |v| v.* else null,
|
||||
.title_changed => wrapper.effects.title_changed = if (value) |v| v.* else null,
|
||||
@@ -1377,6 +1444,254 @@ test "size without callback is silent" {
|
||||
vt_write(t, "\x1B[18t", 5);
|
||||
}
|
||||
|
||||
test "set device_attributes callback primary" {
|
||||
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 S = struct {
|
||||
var last_data: ?[]u8 = null;
|
||||
|
||||
fn deinit() void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = null;
|
||||
}
|
||||
|
||||
fn writePty(_: Terminal, _: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = testing.allocator.dupe(u8, ptr[0..len]) catch @panic("OOM");
|
||||
}
|
||||
|
||||
fn da(_: Terminal, _: ?*anyopaque, out: *Effects.CDeviceAttributes) callconv(.c) bool {
|
||||
out.* = .{
|
||||
.primary = .{
|
||||
.conformance_level = 64,
|
||||
.features = .{22, 52} ++ .{0} ** 62,
|
||||
.num_features = 2,
|
||||
},
|
||||
.secondary = .{
|
||||
.device_type = 1,
|
||||
.firmware_version = 10,
|
||||
.rom_cartridge = 0,
|
||||
},
|
||||
.tertiary = .{ .unit_id = 0 },
|
||||
};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
defer S.deinit();
|
||||
|
||||
const write_cb: ?Effects.WritePtyFn = &S.writePty;
|
||||
set(t, .write_pty, @ptrCast(&write_cb));
|
||||
const da_cb: ?Effects.DeviceAttributesFn = &S.da;
|
||||
set(t, .device_attributes, @ptrCast(&da_cb));
|
||||
|
||||
// CSI c — primary DA
|
||||
vt_write(t, "\x1B[c", 3);
|
||||
try testing.expect(S.last_data != null);
|
||||
try testing.expectEqualStrings("\x1b[?64;22;52c", S.last_data.?);
|
||||
}
|
||||
|
||||
test "set device_attributes callback secondary" {
|
||||
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 S = struct {
|
||||
var last_data: ?[]u8 = null;
|
||||
|
||||
fn deinit() void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = null;
|
||||
}
|
||||
|
||||
fn writePty(_: Terminal, _: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = testing.allocator.dupe(u8, ptr[0..len]) catch @panic("OOM");
|
||||
}
|
||||
|
||||
fn da(_: Terminal, _: ?*anyopaque, out: *Effects.CDeviceAttributes) callconv(.c) bool {
|
||||
out.* = .{
|
||||
.primary = .{
|
||||
.conformance_level = 62,
|
||||
.features = .{22} ++ .{0} ** 63,
|
||||
.num_features = 1,
|
||||
},
|
||||
.secondary = .{
|
||||
.device_type = 1,
|
||||
.firmware_version = 10,
|
||||
.rom_cartridge = 0,
|
||||
},
|
||||
.tertiary = .{ .unit_id = 0 },
|
||||
};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
defer S.deinit();
|
||||
|
||||
const write_cb: ?Effects.WritePtyFn = &S.writePty;
|
||||
set(t, .write_pty, @ptrCast(&write_cb));
|
||||
const da_cb: ?Effects.DeviceAttributesFn = &S.da;
|
||||
set(t, .device_attributes, @ptrCast(&da_cb));
|
||||
|
||||
// CSI > c — secondary DA
|
||||
vt_write(t, "\x1B[>c", 4);
|
||||
try testing.expect(S.last_data != null);
|
||||
try testing.expectEqualStrings("\x1b[>1;10;0c", S.last_data.?);
|
||||
}
|
||||
|
||||
test "set device_attributes callback tertiary" {
|
||||
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 S = struct {
|
||||
var last_data: ?[]u8 = null;
|
||||
|
||||
fn deinit() void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = null;
|
||||
}
|
||||
|
||||
fn writePty(_: Terminal, _: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = testing.allocator.dupe(u8, ptr[0..len]) catch @panic("OOM");
|
||||
}
|
||||
|
||||
fn da(_: Terminal, _: ?*anyopaque, out: *Effects.CDeviceAttributes) callconv(.c) bool {
|
||||
out.* = .{
|
||||
.primary = .{
|
||||
.conformance_level = 62,
|
||||
.features = .{0} ** 64,
|
||||
.num_features = 0,
|
||||
},
|
||||
.secondary = .{
|
||||
.device_type = 1,
|
||||
.firmware_version = 0,
|
||||
.rom_cartridge = 0,
|
||||
},
|
||||
.tertiary = .{ .unit_id = 0xAABBCCDD },
|
||||
};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
defer S.deinit();
|
||||
|
||||
const write_cb: ?Effects.WritePtyFn = &S.writePty;
|
||||
set(t, .write_pty, @ptrCast(&write_cb));
|
||||
const da_cb: ?Effects.DeviceAttributesFn = &S.da;
|
||||
set(t, .device_attributes, @ptrCast(&da_cb));
|
||||
|
||||
// CSI = c — tertiary DA
|
||||
vt_write(t, "\x1B[=c", 4);
|
||||
try testing.expect(S.last_data != null);
|
||||
try testing.expectEqualStrings("\x1bP!|AABBCCDD\x1b\\", S.last_data.?);
|
||||
}
|
||||
|
||||
test "device_attributes without callback uses default" {
|
||||
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 S = struct {
|
||||
var last_data: ?[]u8 = null;
|
||||
|
||||
fn deinit() void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = null;
|
||||
}
|
||||
|
||||
fn writePty(_: Terminal, _: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = testing.allocator.dupe(u8, ptr[0..len]) catch @panic("OOM");
|
||||
}
|
||||
};
|
||||
defer S.deinit();
|
||||
|
||||
const write_cb: ?Effects.WritePtyFn = &S.writePty;
|
||||
set(t, .write_pty, @ptrCast(&write_cb));
|
||||
|
||||
// Without setting a device_attributes callback, DA1 should return the default
|
||||
vt_write(t, "\x1B[c", 3);
|
||||
try testing.expect(S.last_data != null);
|
||||
try testing.expectEqualStrings("\x1b[?62;22c", S.last_data.?);
|
||||
}
|
||||
|
||||
test "device_attributes callback returns false uses default" {
|
||||
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 S = struct {
|
||||
var last_data: ?[]u8 = null;
|
||||
|
||||
fn deinit() void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = null;
|
||||
}
|
||||
|
||||
fn writePty(_: Terminal, _: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void {
|
||||
if (last_data) |d| testing.allocator.free(d);
|
||||
last_data = testing.allocator.dupe(u8, ptr[0..len]) catch @panic("OOM");
|
||||
}
|
||||
|
||||
fn da(_: Terminal, _: ?*anyopaque, _: *Effects.CDeviceAttributes) callconv(.c) bool {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
defer S.deinit();
|
||||
|
||||
const write_cb: ?Effects.WritePtyFn = &S.writePty;
|
||||
set(t, .write_pty, @ptrCast(&write_cb));
|
||||
const da_cb: ?Effects.DeviceAttributesFn = &S.da;
|
||||
set(t, .device_attributes, @ptrCast(&da_cb));
|
||||
|
||||
// Callback returns false, should use default response
|
||||
vt_write(t, "\x1B[c", 3);
|
||||
try testing.expect(S.last_data != null);
|
||||
try testing.expectEqualStrings("\x1b[?62;22c", S.last_data.?);
|
||||
}
|
||||
|
||||
test "grid_ref out of bounds" {
|
||||
var t: Terminal = null;
|
||||
try testing.expectEqual(Result.success, new(
|
||||
|
||||
Reference in New Issue
Block a user