core: implement querying with OSC 4

This commit is contained in:
Gregory Anders
2023-11-06 21:40:20 -06:00
parent 6bf9c05f2a
commit f397353282
4 changed files with 88 additions and 11 deletions

View File

@ -551,15 +551,16 @@ keybind: Keybinds = .{},
@"shell-integration-features": ShellIntegrationFeatures = .{},
/// Sets the reporting format for OSC sequences that request color information.
/// Ghostty currently supports OSC 10 (foreground) and OSC 11 (background) queries,
/// and by default the reported values are scaled-up RGB values, where each component
/// are 16 bits. This is how most terminals report these values. However, some legacy
/// applications may require 8-bit, unscaled, components. We also support turning off
/// reporting alltogether. The components are lowercase hex values.
/// Ghostty currently supports OSC 10 (foreground), OSC 11 (background), and OSC
/// 4 (256 color palette) queries, and by default the reported values are
/// scaled-up RGB values, where each component are 16 bits. This is how most
/// terminals report these values. However, some legacy applications may require
/// 8-bit, unscaled, components. We also support turning off reporting
/// alltogether. The components are lowercase hex values.
///
/// Allowable values are:
///
/// * "none" - OSC 10/11 queries receive no reply
/// * "none" - OSC 4/10/11 queries receive no reply
/// * "8-bit" - Color components are return unscaled, i.e. rr/gg/bb
/// * "16-bit" - Color components are returned scaled, e.g. rrrr/gggg/bbbb
///

View File

@ -241,6 +241,25 @@ pub const VTEvent = struct {
try alloc.dupeZ(u8, @tagName(value)),
),
.Union => |u| if (u.tag_type) |Tag| {
const tag_name = @tagName(@as(Tag, value));
inline for (u.fields) |field| {
if (std.mem.eql(u8, field.name, tag_name)) {
const s = if (field.type == void)
try alloc.dupeZ(u8, tag_name)
else
try std.fmt.allocPrintZ(alloc, "{s}={}", .{
tag_name,
@field(value, field.name),
});
try md.put(key, s);
}
}
} else {
@compileError("Unions must have a tag");
},
else => switch (Value) {
u8 => try md.put(
key,

View File

@ -94,9 +94,10 @@ pub const Command = union(enum) {
value: []const u8,
},
/// OSC 10 and OSC 11 default color report.
/// OSC 4, OSC 10, and OSC 11 default color report.
report_default_color: struct {
/// OSC 10 requests the foreground color, OSC 11 the background color.
/// OSC 4 requests a palette color, OSC 10 requests the foreground
/// color, OSC 11 the background color.
kind: DefaultColorKind,
/// We must reply with the same string terminator (ST) as used in the
@ -104,14 +105,16 @@ pub const Command = union(enum) {
terminator: Terminator = .st,
},
pub const DefaultColorKind = enum {
pub const DefaultColorKind = union(enum) {
foreground,
background,
palette: u8,
pub fn code(self: DefaultColorKind) []const u8 {
return switch (self) {
.foreground => "10",
.background => "11",
.palette => "4",
};
}
};
@ -199,6 +202,7 @@ pub const Parser = struct {
@"133",
@"2",
@"22",
@"4",
@"5",
@"52",
@"7",
@ -224,6 +228,10 @@ pub const Parser = struct {
clipboard_kind,
clipboard_kind_end,
// Get/set color palette index
color_palette_index,
color_palette_index_end,
// Expect a string parameter. param_str must be set as well as
// buf_start.
string,
@ -277,6 +285,7 @@ pub const Parser = struct {
'0' => self.state = .@"0",
'1' => self.state = .@"1",
'2' => self.state = .@"2",
'4' => self.state = .@"4",
'5' => self.state = .@"5",
'7' => self.state = .@"7",
else => self.state = .invalid,
@ -348,6 +357,39 @@ pub const Parser = struct {
else => self.state = .invalid,
},
.@"4" => switch (c) {
';' => {
self.state = .color_palette_index;
self.buf_start = self.buf_idx;
},
else => self.state = .invalid,
},
.color_palette_index => switch (c) {
'0'...'9' => {},
';' => {
if (std.fmt.parseUnsigned(u8, self.buf[self.buf_start .. self.buf_idx - 1], 10)) |num| {
self.state = .color_palette_index_end;
self.temp_state = .{ .num = num };
} else |err| switch (err) {
error.Overflow => self.state = .invalid,
error.InvalidCharacter => unreachable,
}
},
else => self.state = .invalid,
},
.color_palette_index_end => switch (c) {
'?' => {
self.command = .{ .report_default_color = .{
.kind = .{ .palette = @intCast(self.temp_state.num) },
} };
self.complete = true;
},
else => self.state = .invalid,
},
.@"5" => switch (c) {
'2' => self.state = .@"52",
else => self.state = .invalid,
@ -921,3 +963,17 @@ test "OSC: report default background color" {
try testing.expectEqual(cmd.report_default_color.kind, .background);
try testing.expectEqual(cmd.report_default_color.terminator, .bel);
}
test "OSC: get palette color" {
const testing = std.testing;
var p: Parser = .{};
const input = "4;1;?";
for (input) |ch| p.next(ch);
const cmd = p.end('\x1b').?;
try testing.expect(cmd == .report_default_color);
try testing.expectEqual(cmd.report_default_color.kind, .{ .palette = 1 });
try testing.expectEqual(cmd.report_default_color.terminator, .st);
}

View File

@ -2150,8 +2150,8 @@ const StreamHandler = struct {
}
}
/// Implements OSC 10 and OSC 11, which reports default foreground and
/// background color respectively.
/// Implements OSC 4, OSC 10, and OSC 11, which reports palette color,
/// default foreground color, and background color respectively.
pub fn reportDefaultColor(
self: *StreamHandler,
kind: terminal.osc.Command.DefaultColorKind,
@ -2162,6 +2162,7 @@ const StreamHandler = struct {
const color = switch (kind) {
.foreground => self.default_foreground_color,
.background => self.default_background_color,
.palette => |i| self.terminal.color_palette[i],
};
var msg: termio.Message = .{ .write_small = .{} };