diff --git a/src/config/Config.zig b/src/config/Config.zig index 26425fb12..deef6777e 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -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 /// diff --git a/src/inspector/termio.zig b/src/inspector/termio.zig index 78a161069..a9a2f5cde 100644 --- a/src/inspector/termio.zig +++ b/src/inspector/termio.zig @@ -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, diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index 823f9ff67..9c5ac9f9f 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -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); +} diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index f48dbca0d..d342e02eb 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -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 = .{} };