osc: add parsing for Kitty desktop notifications (OSC 99)

This commit is contained in:
Jeffrey C. Ollie
2024-09-12 16:09:06 -05:00
parent efe91fbb72
commit 37f06ca72d
5 changed files with 1181 additions and 25 deletions

View File

@ -199,18 +199,37 @@ pub const VTEvent = struct {
void => {}, void => {},
[]const u8 => try md.put("data", try alloc.dupeZ(u8, v)), []const u8 => try md.put("data", try alloc.dupeZ(u8, v)),
else => |T| switch (@typeInfo(T)) { else => |T| switch (@typeInfo(T)) {
.Struct => |info| inline for (info.fields) |field| { .Struct => |info| {
try encodeMetadataSingle( if (@hasDecl(T, "encodeForInspector"))
alloc, try v.encodeForInspector(alloc, md)
md, else inline for (info.fields) |field| {
field.name, try encodeMetadataSingle(
@field(v, field.name), alloc,
); md,
field.name,
@field(v, field.name),
);
}
},
.Optional => {
if (v) |v1| {
try encodeMetadata(alloc, md, v1);
} else {
try md.put("data", try alloc.dupeZ(u8, "(unset)"));
}
},
.Opaque => {
try md.put("data", try alloc.dupeZ(u8, "(opaque)"));
},
.Pointer => {
try encodeMetadata(alloc, md, v.*);
}, },
else => { else => {
@compileLog(T); try md.put("data", try alloc.dupeZ(u8, "(unknown)"));
@compileError("unsupported type, see log");
}, },
}, },
} }
@ -231,6 +250,11 @@ pub const VTEvent = struct {
try md.put(key, try alloc.dupeZ(u8, "(unset)")); try md.put(key, try alloc.dupeZ(u8, "(unset)"));
}, },
.Int => try md.put(
key,
try std.fmt.allocPrintZ(alloc, "{d}", .{value}),
),
.Bool => try md.put( .Bool => try md.put(
key, key,
try alloc.dupeZ(u8, if (value) "true" else "false"), try alloc.dupeZ(u8, if (value) "true" else "false"),
@ -246,23 +270,19 @@ pub const VTEvent = struct {
const tag_name = @tagName(@as(Tag, value)); const tag_name = @tagName(@as(Tag, value));
inline for (u.fields) |field| { inline for (u.fields) |field| {
if (std.mem.eql(u8, field.name, tag_name)) { if (std.mem.eql(u8, field.name, tag_name)) {
const s = if (field.type == void) try encodeMetadataSingle(alloc, md, tag_name, @field(value, field.name));
try alloc.dupeZ(u8, tag_name)
else
try std.fmt.allocPrintZ(alloc, "{s}={}", .{
tag_name,
@field(value, field.name),
});
try md.put(key, s);
} }
} }
}, },
.Struct => try md.put( .Struct => {
key, try md.put(
try alloc.dupeZ(u8, @typeName(Value)), key,
), try alloc.dupeZ(u8, @typeName(Value)),
);
},
.Void => try md.put(key, try alloc.dupeZ(u8, "(void)")),
else => switch (Value) { else => switch (Value) {
u8 => try md.put( u8 => try md.put(
@ -272,9 +292,11 @@ pub const VTEvent = struct {
[]const u8 => try md.put(key, try alloc.dupeZ(u8, value)), []const u8 => try md.put(key, try alloc.dupeZ(u8, value)),
else => |T| { else => {
@compileLog(T); var l = std.ArrayList(u8).init(alloc);
@compileError("unsupported type, see log"); errdefer l.deinit();
try std.json.stringify(value, .{}, l.writer());
try md.put(key, try l.toOwnedSliceSentinel(0));
}, },
}, },
} }

View File

@ -2,6 +2,7 @@
const key = @import("kitty/key.zig"); const key = @import("kitty/key.zig");
pub const color = @import("kitty/color.zig"); pub const color = @import("kitty/color.zig");
pub const desktop = @import("kitty/desktop.zig");
pub const graphics = @import("kitty/graphics.zig"); pub const graphics = @import("kitty/graphics.zig");
pub const KeyFlags = key.Flags; pub const KeyFlags = key.Flags;

File diff suppressed because it is too large Load Diff

View File

@ -149,6 +149,10 @@ pub const Command = union(enum) {
body: []const u8, body: []const u8,
}, },
/// Kitty desktop notifications (OSC 99)
/// https://sw.kovidgoyal.net/kitty/desktop-notifications/
kitty_desktop_notification: ?*kitty.desktop.KittyDesktopNotification,
/// Start a hyperlink (OSC 8) /// Start a hyperlink (OSC 8)
hyperlink_start: struct { hyperlink_start: struct {
id: ?[]const u8 = null, id: ?[]const u8 = null,
@ -267,6 +271,7 @@ pub const Parser = struct {
@"777", @"777",
@"8", @"8",
@"9", @"9",
@"99",
// OSC 10 is used to query or set the current foreground color. // OSC 10 is used to query or set the current foreground color.
query_fg_color, query_fg_color,
@ -322,6 +327,11 @@ pub const Parser = struct {
// https://sw.kovidgoyal.net/kitty/color-stack/#id1 // https://sw.kovidgoyal.net/kitty/color-stack/#id1
kitty_color_protocol_key, kitty_color_protocol_key,
kitty_color_protocol_value, kitty_color_protocol_value,
// Kitty desktop notifications
// https://sw.kovidgoyal.net/kitty/desktop-notifications/
kitty_desktop_notification_metadata,
kitty_desktop_notification_payload,
}; };
/// This must be called to clean up any allocated memory. /// This must be called to clean up any allocated memory.
@ -361,6 +371,14 @@ pub const Parser = struct {
v.list.deinit(); v.list.deinit();
self.command = default; self.command = default;
}, },
.kitty_desktop_notification => |v| {
if (v) |k| {
k.deinit();
self.alloc.?.destroy(k);
}
self.command.kitty_desktop_notification = null;
self.command = default;
},
else => {}, else => {},
} }
} }
@ -734,6 +752,7 @@ pub const Parser = struct {
}, },
.@"9" => switch (c) { .@"9" => switch (c) {
'9' => self.state = .@"99",
';' => { ';' => {
self.command = .{ .show_desktop_notification = .{ self.command = .{ .show_desktop_notification = .{
.title = "", .title = "",
@ -747,6 +766,30 @@ pub const Parser = struct {
else => self.state = .invalid, else => self.state = .invalid,
}, },
.@"99" => switch (c) {
';' => kitty: {
if (self.alloc == null) {
log.warn("OSC 99 (Kitty desktop notifications) requires an allocator, but none was provided", .{});
self.state = .invalid;
break :kitty;
}
self.state = .kitty_desktop_notification_metadata;
self.buf_start = self.buf_idx;
},
else => self.state = .invalid,
},
.kitty_desktop_notification_metadata => switch (c) {
';' => {
self.temp_state = .{ .key = self.buf[self.buf_start .. self.buf_idx - 1] };
self.state = .kitty_desktop_notification_payload;
self.buf_start = self.buf_idx;
},
else => {},
},
.kitty_desktop_notification_payload => {},
.query_fg_color => switch (c) { .query_fg_color => switch (c) {
'?' => { '?' => {
self.command = .{ .report_color = .{ .kind = .foreground } }; self.command = .{ .report_color = .{ .kind = .foreground } };
@ -1084,6 +1127,40 @@ pub const Parser = struct {
/// is the final character in the OSC sequence. This is used to determine /// is the final character in the OSC sequence. This is used to determine
/// the response terminator. /// the response terminator.
pub fn end(self: *Parser, terminator_ch: ?u8) ?Command { pub fn end(self: *Parser, terminator_ch: ?u8) ?Command {
switch (self.state) {
.kitty_desktop_notification_metadata => {
self.state = .invalid;
self.complete = false;
},
.kitty_desktop_notification_payload => {
self.command = .{
.kitty_desktop_notification = k: {
const alloc = self.alloc orelse {
log.warn("kitty desktop notification requires an allocator", .{});
self.state = .invalid;
self.complete = false;
break :k null;
};
const k = alloc.create(kitty.desktop.KittyDesktopNotification) catch {
self.state = .invalid;
self.complete = false;
break :k null;
};
k.init(alloc, self) catch {
k.deinit();
alloc.destroy(k);
self.state = .invalid;
self.complete = false;
break :k null;
};
self.complete = true;
break :k k;
},
};
},
else => {},
}
if (!self.complete) { if (!self.complete) {
log.warn("invalid OSC command: {s}", .{self.buf[0..self.buf_idx]}); log.warn("invalid OSC command: {s}", .{self.buf[0..self.buf_idx]});
return null; return null;
@ -1104,6 +1181,11 @@ pub const Parser = struct {
switch (self.command) { switch (self.command) {
.report_color => |*c| c.terminator = Terminator.init(terminator_ch), .report_color => |*c| c.terminator = Terminator.init(terminator_ch),
.kitty_color_protocol => |*c| c.terminator = Terminator.init(terminator_ch), .kitty_color_protocol => |*c| c.terminator = Terminator.init(terminator_ch),
.kitty_desktop_notification => |v| {
if (v) |d| {
d.terminator = Terminator.init(terminator_ch);
}
},
else => {}, else => {},
} }

View File

@ -1407,6 +1407,11 @@ pub fn Stream(comptime Handler: type) type {
} else log.warn("unimplemented OSC callback: {}", .{cmd}); } else log.warn("unimplemented OSC callback: {}", .{cmd});
}, },
.kitty_desktop_notification => |v| {
_ = v;
log.warn("ignoring kitty desktop notification", .{});
},
.hyperlink_start => |v| { .hyperlink_start => |v| {
if (@hasDecl(T, "startHyperlink")) { if (@hasDecl(T, "startHyperlink")) {
try self.handler.startHyperlink(v.uri, v.id); try self.handler.startHyperlink(v.uri, v.id);