From 171292a0630162930a015f0cba3efd693f90023b Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Thu, 9 Nov 2023 16:10:43 -0600 Subject: [PATCH] core: implement OSC 12 and OSC 112 to query/set/reset cursor color --- src/renderer/Metal.zig | 7 ++++++- src/renderer/OpenGL.zig | 7 ++++++- src/renderer/Thread.zig | 4 ++++ src/renderer/message.zig | 4 ++++ src/terminal/Parser.zig | 3 ++- src/terminal/osc.zig | 41 +++++++++++++++++++++++++++++++++------- src/termio/Exec.zig | 37 ++++++++++++++++++++++++++++++++++++ 7 files changed, 93 insertions(+), 10 deletions(-) diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 73157ea59..6d75400ba 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -72,6 +72,10 @@ foreground_color: terminal.color.RGB, /// changed by a terminal application background_color: terminal.color.RGB, +/// The actual cursor color. May differ from the config cursor color if changed +/// by a terminal application +cursor_color: ?terminal.color.RGB, + /// The current set of cells to render. This is rebuilt on every frame /// but we keep this around so that we don't reallocate. Each set of /// cells goes into a separate shader. @@ -264,6 +268,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !Metal { .focused = true, .foreground_color = options.config.foreground, .background_color = options.config.background, + .cursor_color = options.config.cursor_color, // Render state .cells_bg = .{}, @@ -1444,7 +1449,7 @@ fn addCursor( ), screen.cursor.x - 1 }; }; - const color = self.config.cursor_color orelse self.foreground_color; + const color = self.cursor_color orelse self.foreground_color; const alpha: u8 = if (!self.focused) 255 else alpha: { const alpha = 255 * self.config.cursor_opacity; break :alpha @intFromFloat(@ceil(alpha)); diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 828ffa090..bc8982b37 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -75,6 +75,10 @@ foreground_color: terminal.color.RGB, /// changed by a terminal application background_color: terminal.color.RGB, +/// The actual cursor color. May differ from the config cursor color if changed +/// by a terminal application +cursor_color: ?terminal.color.RGB, + /// Padding options padding: renderer.Options.Padding, @@ -320,6 +324,7 @@ pub fn init(alloc: Allocator, options: renderer.Options) !OpenGL { .focused = true, .foreground_color = options.config.foreground, .background_color = options.config.background, + .cursor_color = options.config.cursor_color, .padding = options.padding, .surface_mailbox = options.surface_mailbox, .deferred_font_size = .{ .metrics = metrics }, @@ -888,7 +893,7 @@ fn addCursor( ), screen.cursor.x - 1 }; }; - const color = self.config.cursor_color orelse self.foreground_color; + const color = self.cursor_color orelse self.foreground_color; const alpha: u8 = if (!self.focused) 255 else alpha: { const alpha = 255 * self.config.cursor_opacity; break :alpha @intFromFloat(@ceil(alpha)); diff --git a/src/renderer/Thread.zig b/src/renderer/Thread.zig index 654f370ad..17abd6325 100644 --- a/src/renderer/Thread.zig +++ b/src/renderer/Thread.zig @@ -270,6 +270,10 @@ fn drainMailbox(self: *Thread) !void { self.renderer.background_color = color; }, + .cursor_color => |color| { + self.renderer.cursor_color = color; + }, + .resize => |v| { try self.renderer.setScreenSize(v.screen_size, v.padding); }, diff --git a/src/renderer/message.zig b/src/renderer/message.zig index adb504109..3278a2c1c 100644 --- a/src/renderer/message.zig +++ b/src/renderer/message.zig @@ -29,6 +29,10 @@ pub const Message = union(enum) { /// the config file in response to an OSC 11 command. background_color: terminal.color.RGB, + /// Change the cursor color. This can be done separately from changing the + /// config file in response to an OSC 12 command. + cursor_color: ?terminal.color.RGB, + /// Changes the screen size. resize: struct { /// The full screen (drawable) size. This does NOT include padding. diff --git a/src/terminal/Parser.zig b/src/terminal/Parser.zig index 032ac1bc6..200803647 100644 --- a/src/terminal/Parser.zig +++ b/src/terminal/Parser.zig @@ -827,7 +827,8 @@ test "osc: 112 incomplete sequence" { try testing.expect(a[2] == null); const cmd = a[0].?.osc_dispatch; - try testing.expect(cmd == .reset_cursor_color); + try testing.expect(cmd == .reset_color); + try testing.expectEqual(cmd.reset_color.kind, .cursor); } } diff --git a/src/terminal/osc.zig b/src/terminal/osc.zig index cfe62b8b9..2e59eb279 100644 --- a/src/terminal/osc.zig +++ b/src/terminal/osc.zig @@ -64,10 +64,6 @@ pub const Command = union(enum) { // TODO: err option }, - /// Reset the color for the cursor. This reverts changes made with - /// change/read cursor color. - reset_cursor_color: void, - /// Set or get clipboard contents. If data is null, then the current /// clipboard contents are sent to the pty. If data is set, this /// contents is set on the clipboard. @@ -129,12 +125,14 @@ pub const Command = union(enum) { palette: u8, foreground, background, + cursor, pub fn code(self: ColorKind) []const u8 { return switch (self) { .palette => "4", .foreground => "10", .background => "11", + .cursor => "12", }; } }; @@ -218,6 +216,7 @@ pub const Parser = struct { @"1", @"10", @"11", + @"12", @"13", @"133", @"2", @@ -233,6 +232,9 @@ pub const Parser = struct { // OSC 11 is used to query or set the current background color. query_bg_color, + // OSC 12 is used to query or set the current cursor color. + query_cursor_color, + // We're in a semantic prompt OSC command but we aren't sure // what the command is yet, i.e. `133;` semantic_prompt, @@ -326,6 +328,7 @@ pub const Parser = struct { .@"1" => switch (c) { '0' => self.state = .@"10", '1' => self.state = .@"11", + '2' => self.state = .@"12", '3' => self.state = .@"13", else => self.state = .invalid, }, @@ -357,13 +360,18 @@ pub const Parser = struct { self.state = .invalid; }, '2' => { + self.command = .{ .reset_color = .{ .kind = .cursor, .value = undefined } }; self.complete = true; - self.command = .{ .reset_cursor_color = {} }; self.state = .invalid; }, else => self.state = .invalid, }, + .@"12" => switch (c) { + ';' => self.state = .query_cursor_color, + else => self.state = .invalid, + }, + .@"13" => switch (c) { '3' => self.state = .@"133", else => self.state = .invalid, @@ -534,6 +542,24 @@ pub const Parser = struct { }, }, + .query_cursor_color => switch (c) { + '?' => { + self.command = .{ .report_color = .{ .kind = .cursor } }; + self.complete = true; + self.state = .invalid; + }, + else => { + self.command = .{ .set_color = .{ + .kind = .cursor, + .value = "", + } }; + + self.state = .string; + self.temp_state = .{ .str = &self.command.set_color.value }; + self.buf_start = self.buf_idx - 1; + }, + }, + .semantic_prompt => switch (c) { 'A' => { self.state = .semantic_option_start; @@ -912,7 +938,7 @@ test "OSC: end_of_input" { try testing.expect(cmd == .end_of_input); } -test "OSC: reset_cursor_color" { +test "OSC: reset cursor color" { const testing = std.testing; var p: Parser = .{}; @@ -921,7 +947,8 @@ test "OSC: reset_cursor_color" { for (input) |ch| p.next(ch); const cmd = p.end(null).?; - try testing.expect(cmd == .reset_cursor_color); + try testing.expect(cmd == .reset_color); + try testing.expectEqual(cmd.reset_color.kind, .cursor); } test "OSC: get/set clipboard" { diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 178de96d8..5e2d3c021 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -69,6 +69,10 @@ grid_size: renderer.GridSize, /// it when a CSI q with default is called. default_cursor_style: terminal.Cursor.Style, default_cursor_blink: ?bool, +default_cursor_color: ?terminal.color.RGB, + +/// Actual cursor color +cursor_color: ?terminal.color.RGB, /// Default foreground color as set by the config file default_foreground_color: terminal.color.RGB, @@ -96,6 +100,7 @@ pub const DerivedConfig = struct { image_storage_limit: usize, cursor_style: terminal.Cursor.Style, cursor_blink: ?bool, + cursor_color: ?configpkg.Config.Color, foreground: configpkg.Config.Color, background: configpkg.Config.Color, osc_color_report_format: configpkg.Config.OSCColorReportFormat, @@ -112,6 +117,7 @@ pub const DerivedConfig = struct { .image_storage_limit = config.@"image-storage-limit", .cursor_style = config.@"cursor-style", .cursor_blink = config.@"cursor-style-blink", + .cursor_color = config.@"cursor-color", .foreground = config.foreground, .background = config.background, .osc_color_report_format = config.@"osc-color-report-format", @@ -175,6 +181,14 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec { .grid_size = opts.grid_size, .default_cursor_style = opts.config.cursor_style, .default_cursor_blink = opts.config.cursor_blink, + .default_cursor_color = if (opts.config.cursor_color) |col| + col.toTerminalRGB() + else + null, + .cursor_color = if (opts.config.cursor_color) |col| + col.toTerminalRGB() + else + null, .default_foreground_color = config.foreground.toTerminalRGB(), .default_background_color = config.background.toTerminalRGB(), .foreground_color = config.foreground.toTerminalRGB(), @@ -244,6 +258,8 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData { .grid_size = &self.grid_size, .default_cursor_style = self.default_cursor_style, .default_cursor_blink = self.default_cursor_blink, + .default_cursor_color = self.default_cursor_color, + .cursor_color = self.cursor_color, .default_foreground_color = self.default_foreground_color, .default_background_color = self.default_background_color, .foreground_color = self.foreground_color, @@ -337,6 +353,10 @@ pub fn changeConfig(self: *Exec, config: *DerivedConfig) !void { // Update our default cursor style self.default_cursor_style = config.cursor_style; self.default_cursor_blink = config.cursor_blink; + self.default_cursor_color = if (config.cursor_color) |col| + col.toTerminalRGB() + else + null; // Update default foreground and background colors self.default_foreground_color = config.foreground.toTerminalRGB(); @@ -1340,6 +1360,10 @@ const StreamHandler = struct { default_cursor: bool = true, default_cursor_style: terminal.Cursor.Style, default_cursor_blink: ?bool, + default_cursor_color: ?terminal.color.RGB, + + /// Actual cursor color. This can be changed with OSC 12. + cursor_color: ?terminal.color.RGB, /// The default foreground and background color are those set by the user's /// config file. These can be overridden by terminal applications using OSC @@ -2188,6 +2212,7 @@ const StreamHandler = struct { .palette => |i| self.terminal.color_palette.colors[i], .foreground => self.foreground_color, .background => self.background_color, + .cursor => self.cursor_color orelse self.foreground_color, }; var msg: termio.Message = .{ .write_small = .{} }; @@ -2246,6 +2271,12 @@ const StreamHandler = struct { .background_color = color, }, .{ .forever = {} }); }, + .cursor => { + self.cursor_color = color; + _ = self.ev.renderer_mailbox.push(.{ + .cursor_color = color, + }, .{ .forever = {} }); + }, } } @@ -2295,6 +2326,12 @@ const StreamHandler = struct { .background_color = self.background_color, }, .{ .forever = {} }); }, + .cursor => { + self.cursor_color = self.default_cursor_color; + _ = self.ev.renderer_mailbox.push(.{ + .cursor_color = self.cursor_color, + }, .{ .forever = {} }); + }, } } };