vaxis: add support for color reports (OSC 4, 10, 11, 12)
Add support for querying colors (index, foreground, background, and cursor)
This commit is contained in:
parent
fba8984cf8
commit
6b9f91986c
6 changed files with 132 additions and 0 deletions
56
src/Cell.zig
56
src/Cell.zig
|
@ -105,6 +105,19 @@ pub const Color = union(enum) {
|
|||
index: u8,
|
||||
rgb: [3]u8,
|
||||
|
||||
pub const Kind = union(enum) {
|
||||
fg,
|
||||
bg,
|
||||
cursor,
|
||||
index: u8,
|
||||
};
|
||||
|
||||
/// Returned when querying a color from the terminal
|
||||
pub const Report = struct {
|
||||
kind: Kind,
|
||||
value: [3]u8,
|
||||
};
|
||||
|
||||
pub fn eql(a: Color, b: Color) bool {
|
||||
switch (a) {
|
||||
.default => return b == .default,
|
||||
|
@ -136,4 +149,47 @@ pub const Color = union(enum) {
|
|||
};
|
||||
return .{ .rgb = rgb };
|
||||
}
|
||||
|
||||
/// parse an XParseColor-style rgb specification into an rgb Color. The spec
|
||||
/// is of the form: rgb:rrrr/gggg/bbbb. Generally, the high two bits will always
|
||||
/// be the same as the low two bits.
|
||||
pub fn rgbFromSpec(spec: []const u8) !Color {
|
||||
var iter = std.mem.splitScalar(u8, spec, ':');
|
||||
const prefix = iter.next() orelse return error.InvalidColorSpec;
|
||||
if (!std.mem.eql(u8, "rgb", prefix)) return error.InvalidColorSpec;
|
||||
|
||||
const spec_str = iter.next() orelse return error.InvalidColorSpec;
|
||||
|
||||
var spec_iter = std.mem.splitScalar(u8, spec_str, '/');
|
||||
|
||||
const r_raw = spec_iter.next() orelse return error.InvalidColorSpec;
|
||||
if (r_raw.len != 4) return error.InvalidColorSpec;
|
||||
|
||||
const g_raw = spec_iter.next() orelse return error.InvalidColorSpec;
|
||||
if (g_raw.len != 4) return error.InvalidColorSpec;
|
||||
|
||||
const b_raw = spec_iter.next() orelse return error.InvalidColorSpec;
|
||||
if (b_raw.len != 4) return error.InvalidColorSpec;
|
||||
|
||||
const r = try std.fmt.parseUnsigned(u8, r_raw[2..], 16);
|
||||
const g = try std.fmt.parseUnsigned(u8, g_raw[2..], 16);
|
||||
const b = try std.fmt.parseUnsigned(u8, b_raw[2..], 16);
|
||||
|
||||
return .{
|
||||
.rgb = [_]u8{ r, g, b },
|
||||
};
|
||||
}
|
||||
|
||||
test "rgbFromSpec" {
|
||||
const spec = "rgb:aaaa/bbbb/cccc";
|
||||
const actual = try rgbFromSpec(spec);
|
||||
switch (actual) {
|
||||
.rgb => |rgb| {
|
||||
try std.testing.expectEqual(0xAA, rgb[0]);
|
||||
try std.testing.expectEqual(0xBB, rgb[1]);
|
||||
try std.testing.expectEqual(0xCC, rgb[2]);
|
||||
},
|
||||
else => try std.testing.expect(false),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const Color = @import("Cell.zig").Color;
|
||||
const Event = @import("event.zig").Event;
|
||||
const Key = @import("Key.zig");
|
||||
const Mouse = @import("Mouse.zig");
|
||||
|
@ -588,6 +589,50 @@ pub fn parse(self: *Parser, input: []const u8, paste_allocator: ?std.mem.Allocat
|
|||
seq.param_buf_idx = 0;
|
||||
seq.param_idx += 1;
|
||||
switch (p) {
|
||||
4,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
=> {
|
||||
i += 1;
|
||||
const index: ?u8 = if (p == 4) blk: {
|
||||
const index_start = i;
|
||||
const end: usize = while (i < n) : (i += 1) {
|
||||
if (input[i] == ';') {
|
||||
i += 1;
|
||||
break i - 1;
|
||||
}
|
||||
} else unreachable; // invalid input
|
||||
break :blk try std.fmt.parseUnsigned(u8, input[index_start..end], 10);
|
||||
} else null;
|
||||
const spec_start = i;
|
||||
const end: usize = while (i < n) : (i += 1) {
|
||||
if (input[i] == 0x1B) {
|
||||
// advance one more for the backslash
|
||||
i += 1;
|
||||
break i - 1;
|
||||
}
|
||||
} else return .{
|
||||
.event = null,
|
||||
.n = i,
|
||||
};
|
||||
const color = try Color.rgbFromSpec(input[spec_start..end]);
|
||||
|
||||
const event: Color.Report = .{
|
||||
.kind = switch (p) {
|
||||
4 => .{ .index = index.? },
|
||||
10 => .fg,
|
||||
11 => .bg,
|
||||
12 => .cursor,
|
||||
else => unreachable,
|
||||
},
|
||||
.value = color.rgb,
|
||||
};
|
||||
return .{
|
||||
.event = .{ .color_report = event },
|
||||
.n = i,
|
||||
};
|
||||
},
|
||||
52 => {
|
||||
var payload: ?std.ArrayList(u8) = if (paste_allocator) |allocator|
|
||||
std.ArrayList(u8).init(allocator)
|
||||
|
|
|
@ -219,6 +219,11 @@ pub fn run(
|
|||
paste_allocator.?.free(text);
|
||||
}
|
||||
},
|
||||
.color_report => |report| {
|
||||
if (@hasField(Event, "color_report")) {
|
||||
loop.postEvent(.{ .color_report = report });
|
||||
}
|
||||
},
|
||||
.cap_kitty_keyboard => {
|
||||
log.info("kitty keyboard capability detected", .{});
|
||||
loop.vaxis.caps.kitty_keyboard = true;
|
||||
|
|
|
@ -858,3 +858,17 @@ pub fn requestSystemClipboard(self: Vaxis) !void {
|
|||
);
|
||||
try tty.flush();
|
||||
}
|
||||
|
||||
/// Request a color report from the terminal. Note: not all terminals support
|
||||
/// reporting colors. It is always safe to try, but you may not receive a
|
||||
/// response.
|
||||
pub fn queryColor(self: Vaxis, kind: Cell.Color.Kind) !void {
|
||||
var tty = self.tty orelse return;
|
||||
switch (kind) {
|
||||
.fg => _ = try tty.write(ctlseqs.osc10_query),
|
||||
.bg => _ = try tty.write(ctlseqs.osc11_query),
|
||||
.cursor => _ = try tty.write(ctlseqs.osc12_query),
|
||||
.index => |idx| try tty.buffered_writer.writer().print(ctlseqs.osc4_query, .{idx}),
|
||||
}
|
||||
try tty.flush();
|
||||
}
|
||||
|
|
|
@ -113,3 +113,13 @@ pub const osc52_clipboard_request = "\x1b]52;c;?\x1b\\";
|
|||
pub const kitty_graphics_clear = "\x1b_Ga=d\x1b\\";
|
||||
pub const kitty_graphics_preamble = "\x1b_Ga=p,i={d}";
|
||||
pub const kitty_graphics_closing = ",C=1\x1b\\";
|
||||
|
||||
// Color control sequences
|
||||
pub const osc4_query = "\x1b]4;{d};?\x1b\\"; // color index {d}
|
||||
pub const osc4_reset = "\x1b]104\x1b\\"; // this resets _all_ color indexes
|
||||
pub const osc10_query = "\x1b]10;?\x1b\\"; // fg
|
||||
pub const osc10_reset = "\x1b]110\x1b\\"; // reset fg to terminal default
|
||||
pub const osc11_query = "\x1b]11;?\x1b\\"; // bg
|
||||
pub const osc11_reset = "\x1b]111\x1b\\"; // reset bg to terminal default
|
||||
pub const osc12_query = "\x1b]12;?\x1b\\"; // cursor color
|
||||
pub const osc12_reset = "\x1b]112\x1b\\"; // reset cursor to terminal default
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub const Key = @import("Key.zig");
|
||||
pub const Mouse = @import("Mouse.zig");
|
||||
pub const Color = @import("Cell.zig").Color;
|
||||
|
||||
/// The events that Vaxis emits internally
|
||||
pub const Event = union(enum) {
|
||||
|
@ -11,6 +12,7 @@ pub const Event = union(enum) {
|
|||
paste_start, // bracketed paste start
|
||||
paste_end, // bracketed paste end
|
||||
paste: []const u8, // osc 52 paste, caller must free
|
||||
color_report: Color.Report, // osc 4, 10, 11, 12 response
|
||||
|
||||
// these are delivered as discovered terminal capabilities
|
||||
cap_kitty_keyboard,
|
||||
|
|
Loading…
Reference in a new issue