mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
osc: add parsing for Kitty desktop notifications (OSC 99)
This commit is contained in:
@ -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| {
|
||||||
|
if (@hasDecl(T, "encodeForInspector"))
|
||||||
|
try v.encodeForInspector(alloc, md)
|
||||||
|
else inline for (info.fields) |field| {
|
||||||
try encodeMetadataSingle(
|
try encodeMetadataSingle(
|
||||||
alloc,
|
alloc,
|
||||||
md,
|
md,
|
||||||
field.name,
|
field.name,
|
||||||
@field(v, 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 => {
|
||||||
|
try md.put(
|
||||||
key,
|
key,
|
||||||
try alloc.dupeZ(u8, @typeName(Value)),
|
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));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
1046
src/terminal/kitty/desktop.zig
Normal file
1046
src/terminal/kitty/desktop.zig
Normal file
File diff suppressed because it is too large
Load Diff
@ -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 => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
Reference in New Issue
Block a user