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,
|
index: u8,
|
||||||
rgb: [3]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 {
|
pub fn eql(a: Color, b: Color) bool {
|
||||||
switch (a) {
|
switch (a) {
|
||||||
.default => return b == .default,
|
.default => return b == .default,
|
||||||
|
@ -136,4 +149,47 @@ pub const Color = union(enum) {
|
||||||
};
|
};
|
||||||
return .{ .rgb = rgb };
|
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 std = @import("std");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
const Color = @import("Cell.zig").Color;
|
||||||
const Event = @import("event.zig").Event;
|
const Event = @import("event.zig").Event;
|
||||||
const Key = @import("Key.zig");
|
const Key = @import("Key.zig");
|
||||||
const Mouse = @import("Mouse.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_buf_idx = 0;
|
||||||
seq.param_idx += 1;
|
seq.param_idx += 1;
|
||||||
switch (p) {
|
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 => {
|
52 => {
|
||||||
var payload: ?std.ArrayList(u8) = if (paste_allocator) |allocator|
|
var payload: ?std.ArrayList(u8) = if (paste_allocator) |allocator|
|
||||||
std.ArrayList(u8).init(allocator)
|
std.ArrayList(u8).init(allocator)
|
||||||
|
|
|
@ -219,6 +219,11 @@ pub fn run(
|
||||||
paste_allocator.?.free(text);
|
paste_allocator.?.free(text);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
.color_report => |report| {
|
||||||
|
if (@hasField(Event, "color_report")) {
|
||||||
|
loop.postEvent(.{ .color_report = report });
|
||||||
|
}
|
||||||
|
},
|
||||||
.cap_kitty_keyboard => {
|
.cap_kitty_keyboard => {
|
||||||
log.info("kitty keyboard capability detected", .{});
|
log.info("kitty keyboard capability detected", .{});
|
||||||
loop.vaxis.caps.kitty_keyboard = true;
|
loop.vaxis.caps.kitty_keyboard = true;
|
||||||
|
|
|
@ -858,3 +858,17 @@ pub fn requestSystemClipboard(self: Vaxis) !void {
|
||||||
);
|
);
|
||||||
try tty.flush();
|
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_clear = "\x1b_Ga=d\x1b\\";
|
||||||
pub const kitty_graphics_preamble = "\x1b_Ga=p,i={d}";
|
pub const kitty_graphics_preamble = "\x1b_Ga=p,i={d}";
|
||||||
pub const kitty_graphics_closing = ",C=1\x1b\\";
|
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 Key = @import("Key.zig");
|
||||||
pub const Mouse = @import("Mouse.zig");
|
pub const Mouse = @import("Mouse.zig");
|
||||||
|
pub const Color = @import("Cell.zig").Color;
|
||||||
|
|
||||||
/// The events that Vaxis emits internally
|
/// The events that Vaxis emits internally
|
||||||
pub const Event = union(enum) {
|
pub const Event = union(enum) {
|
||||||
|
@ -11,6 +12,7 @@ pub const Event = union(enum) {
|
||||||
paste_start, // bracketed paste start
|
paste_start, // bracketed paste start
|
||||||
paste_end, // bracketed paste end
|
paste_end, // bracketed paste end
|
||||||
paste: []const u8, // osc 52 paste, caller must free
|
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
|
// these are delivered as discovered terminal capabilities
|
||||||
cap_kitty_keyboard,
|
cap_kitty_keyboard,
|
||||||
|
|
Loading…
Reference in a new issue