terminal: some osc types
parent
bce1164ae6
commit
4d028dac1f
|
|
@ -1,9 +1,11 @@
|
|||
const std = @import("std");
|
||||
const enumpkg = @import("enum.zig");
|
||||
const types = @import("types.zig");
|
||||
const unionpkg = @import("union.zig");
|
||||
|
||||
pub const allocator = @import("allocator.zig");
|
||||
pub const Enum = enumpkg.Enum;
|
||||
pub const String = types.String;
|
||||
pub const Struct = @import("struct.zig").Struct;
|
||||
pub const Target = @import("target.zig").Target;
|
||||
pub const TaggedUnion = unionpkg.TaggedUnion;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
pub const String = extern struct {
|
||||
ptr: [*]const u8,
|
||||
len: usize,
|
||||
|
||||
pub fn init(zig: anytype) String {
|
||||
return switch (@TypeOf(zig)) {
|
||||
[]u8, []const u8 => .{
|
||||
.ptr = zig.ptr,
|
||||
.len = zig.len,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -113,6 +113,16 @@ pub const Action = union(Key) {
|
|||
end_hyperlink,
|
||||
active_status_display: ansi.StatusDisplay,
|
||||
decaln,
|
||||
window_title: WindowTitle,
|
||||
report_pwd: ReportPwd,
|
||||
show_desktop_notification: ShowDesktopNotification,
|
||||
progress_report: osc.Command.ProgressReport,
|
||||
start_hyperlink: StartHyperlink,
|
||||
clipboard_contents: ClipboardContents,
|
||||
prompt_start: PromptStart,
|
||||
prompt_continuation: PromptContinuation,
|
||||
end_of_command: EndOfCommand,
|
||||
mouse_shape: MouseShape,
|
||||
|
||||
pub const Key = lib.Enum(
|
||||
lib_target,
|
||||
|
|
@ -200,6 +210,16 @@ pub const Action = union(Key) {
|
|||
"end_hyperlink",
|
||||
"active_status_display",
|
||||
"decaln",
|
||||
"window_title",
|
||||
"report_pwd",
|
||||
"show_desktop_notification",
|
||||
"progress_report",
|
||||
"start_hyperlink",
|
||||
"clipboard_contents",
|
||||
"prompt_start",
|
||||
"prompt_continuation",
|
||||
"end_of_command",
|
||||
"mouse_shape",
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -288,6 +308,118 @@ pub const Action = union(Key) {
|
|||
return @intCast(self.flags.int());
|
||||
}
|
||||
};
|
||||
|
||||
pub const WindowTitle = struct {
|
||||
title: []const u8,
|
||||
|
||||
pub const C = lib.String;
|
||||
|
||||
pub fn cval(self: WindowTitle) WindowTitle.C {
|
||||
return .init(self.title);
|
||||
}
|
||||
};
|
||||
|
||||
pub const ReportPwd = struct {
|
||||
url: []const u8,
|
||||
|
||||
pub const C = lib.String;
|
||||
|
||||
pub fn cval(self: ReportPwd) ReportPwd.C {
|
||||
return .init(self.url);
|
||||
}
|
||||
};
|
||||
|
||||
pub const ShowDesktopNotification = struct {
|
||||
title: []const u8,
|
||||
body: []const u8,
|
||||
|
||||
pub const C = extern struct {
|
||||
title: lib.String,
|
||||
body: lib.String,
|
||||
};
|
||||
|
||||
pub fn cval(self: ShowDesktopNotification) ShowDesktopNotification.C {
|
||||
return .{
|
||||
.title = .init(self.title),
|
||||
.body = .init(self.body),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const StartHyperlink = struct {
|
||||
uri: []const u8,
|
||||
id: ?[]const u8,
|
||||
|
||||
pub const C = extern struct {
|
||||
uri: lib.String,
|
||||
id: lib.String,
|
||||
};
|
||||
|
||||
pub fn cval(self: StartHyperlink) StartHyperlink.C {
|
||||
return .{
|
||||
.uri = .init(self.uri),
|
||||
.id = .init(self.id orelse ""),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const ClipboardContents = struct {
|
||||
kind: u8,
|
||||
data: []const u8,
|
||||
|
||||
pub const C = extern struct {
|
||||
kind: u8,
|
||||
data: lib.String,
|
||||
};
|
||||
|
||||
pub fn cval(self: ClipboardContents) ClipboardContents.C {
|
||||
return .{
|
||||
.kind = self.kind,
|
||||
.data = .init(self.data),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const PromptStart = struct {
|
||||
aid: ?[]const u8,
|
||||
redraw: bool,
|
||||
|
||||
pub const C = extern struct {
|
||||
aid: lib.String,
|
||||
redraw: bool,
|
||||
};
|
||||
|
||||
pub fn cval(self: PromptStart) PromptStart.C {
|
||||
return .{
|
||||
.aid = .init(self.aid orelse ""),
|
||||
.redraw = self.redraw,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const PromptContinuation = struct {
|
||||
aid: ?[]const u8,
|
||||
|
||||
pub const C = lib.String;
|
||||
|
||||
pub fn cval(self: PromptContinuation) PromptContinuation.C {
|
||||
return .init(self.aid orelse "");
|
||||
}
|
||||
};
|
||||
|
||||
pub const EndOfCommand = struct {
|
||||
exit_code: ?u8,
|
||||
|
||||
pub const C = extern struct {
|
||||
exit_code: i16,
|
||||
};
|
||||
|
||||
pub fn cval(self: EndOfCommand) EndOfCommand.C {
|
||||
return .{
|
||||
.exit_code = if (self.exit_code) |code| @intCast(code) else -1,
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// Returns a type that can process a stream of tty control characters.
|
||||
|
|
@ -1710,15 +1842,12 @@ pub fn Stream(comptime Handler: type) type {
|
|||
inline fn oscDispatch(self: *Self, cmd: osc.Command) !void {
|
||||
switch (cmd) {
|
||||
.change_window_title => |title| {
|
||||
if (@hasDecl(T, "changeWindowTitle")) {
|
||||
if (!std.unicode.utf8ValidateSlice(title)) {
|
||||
log.warn("change title request: invalid utf-8, ignoring request", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
try self.handler.changeWindowTitle(title);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
try self.handler.vt(.window_title, .{ .title = title });
|
||||
},
|
||||
|
||||
.change_window_icon => |icon| {
|
||||
|
|
@ -1726,54 +1855,43 @@ pub fn Stream(comptime Handler: type) type {
|
|||
},
|
||||
|
||||
.clipboard_contents => |clip| {
|
||||
if (@hasDecl(T, "clipboardContents")) {
|
||||
try self.handler.clipboardContents(clip.kind, clip.data);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
try self.handler.vt(.clipboard_contents, .{
|
||||
.kind = clip.kind,
|
||||
.data = clip.data,
|
||||
});
|
||||
},
|
||||
|
||||
.prompt_start => |v| {
|
||||
if (@hasDecl(T, "promptStart")) {
|
||||
switch (v.kind) {
|
||||
.primary, .right => try self.handler.promptStart(v.aid, v.redraw),
|
||||
.continuation, .secondary => try self.handler.promptContinuation(v.aid),
|
||||
.primary, .right => try self.handler.vt(.prompt_start, .{
|
||||
.aid = v.aid,
|
||||
.redraw = v.redraw,
|
||||
}),
|
||||
.continuation, .secondary => try self.handler.vt(.prompt_continuation, .{
|
||||
.aid = v.aid,
|
||||
}),
|
||||
}
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
},
|
||||
|
||||
.prompt_end => {
|
||||
try self.handler.vt(.prompt_end, {});
|
||||
},
|
||||
.prompt_end => try self.handler.vt(.prompt_end, {}),
|
||||
|
||||
.end_of_input => {
|
||||
try self.handler.vt(.end_of_input, {});
|
||||
},
|
||||
.end_of_input => try self.handler.vt(.end_of_input, {}),
|
||||
|
||||
.end_of_command => |end| {
|
||||
if (@hasDecl(T, "endOfCommand")) {
|
||||
try self.handler.endOfCommand(end.exit_code);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
try self.handler.vt(.end_of_command, .{ .exit_code = end.exit_code });
|
||||
},
|
||||
|
||||
.report_pwd => |v| {
|
||||
if (@hasDecl(T, "reportPwd")) {
|
||||
try self.handler.reportPwd(v.value);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
try self.handler.vt(.report_pwd, .{ .url = v.value });
|
||||
},
|
||||
|
||||
.mouse_shape => |v| {
|
||||
if (@hasDecl(T, "setMouseShape")) {
|
||||
const shape = MouseShape.fromString(v.value) orelse {
|
||||
log.warn("unknown cursor shape: {s}", .{v.value});
|
||||
return;
|
||||
};
|
||||
|
||||
try self.handler.setMouseShape(shape);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
try self.handler.vt(.mouse_shape, shape);
|
||||
},
|
||||
|
||||
.color_operation => |v| {
|
||||
|
|
@ -1795,17 +1913,17 @@ pub fn Stream(comptime Handler: type) type {
|
|||
},
|
||||
|
||||
.show_desktop_notification => |v| {
|
||||
if (@hasDecl(T, "showDesktopNotification")) {
|
||||
try self.handler.showDesktopNotification(v.title, v.body);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
try self.handler.vt(.show_desktop_notification, .{
|
||||
.title = v.title,
|
||||
.body = v.body,
|
||||
});
|
||||
},
|
||||
|
||||
.hyperlink_start => |v| {
|
||||
if (@hasDecl(T, "startHyperlink")) {
|
||||
try self.handler.startHyperlink(v.uri, v.id);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
try self.handler.vt(.start_hyperlink, .{
|
||||
.uri = v.uri,
|
||||
.id = v.id,
|
||||
});
|
||||
},
|
||||
|
||||
.hyperlink_end => {
|
||||
|
|
@ -1813,10 +1931,7 @@ pub fn Stream(comptime Handler: type) type {
|
|||
},
|
||||
|
||||
.conemu_progress_report => |v| {
|
||||
if (@hasDecl(T, "handleProgressReport")) {
|
||||
try self.handler.handleProgressReport(v);
|
||||
return;
|
||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||
try self.handler.vt(.progress_report, v);
|
||||
},
|
||||
|
||||
.conemu_sleep,
|
||||
|
|
@ -2643,20 +2758,16 @@ test "stream: change window title with invalid utf-8" {
|
|||
const H = struct {
|
||||
seen: bool = false,
|
||||
|
||||
pub fn changeWindowTitle(self: *@This(), title: []const u8) !void {
|
||||
_ = title;
|
||||
|
||||
self.seen = true;
|
||||
}
|
||||
|
||||
pub fn vt(
|
||||
self: *@This(),
|
||||
comptime action: anytype,
|
||||
value: anytype,
|
||||
) !void {
|
||||
_ = self;
|
||||
_ = action;
|
||||
_ = value;
|
||||
switch (action) {
|
||||
.window_title => self.seen = true,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -306,6 +306,16 @@ pub const StreamHandler = struct {
|
|||
.end_hyperlink => try self.endHyperlink(),
|
||||
.active_status_display => self.terminal.status_display = value,
|
||||
.decaln => try self.decaln(),
|
||||
.window_title => try self.windowTitle(value.title),
|
||||
.report_pwd => try self.reportPwd(value.url),
|
||||
.show_desktop_notification => try self.showDesktopNotification(value.title, value.body),
|
||||
.progress_report => self.progressReport(value),
|
||||
.start_hyperlink => try self.startHyperlink(value.uri, value.id),
|
||||
.clipboard_contents => try self.clipboardContents(value.kind, value.data),
|
||||
.prompt_start => self.promptStart(value.aid, value.redraw),
|
||||
.prompt_continuation => self.promptContinuation(value.aid),
|
||||
.end_of_command => self.endOfCommand(value.exit_code),
|
||||
.mouse_shape => try self.setMouseShape(value),
|
||||
.dcs_hook => try self.dcsHook(value),
|
||||
.dcs_put => try self.dcsPut(value),
|
||||
.dcs_unhook => try self.dcsUnhook(),
|
||||
|
|
@ -714,7 +724,7 @@ pub const StreamHandler = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub inline fn startHyperlink(self: *StreamHandler, uri: []const u8, id: ?[]const u8) !void {
|
||||
inline fn startHyperlink(self: *StreamHandler, uri: []const u8, id: ?[]const u8) !void {
|
||||
try self.terminal.screen.startHyperlink(uri, id);
|
||||
}
|
||||
|
||||
|
|
@ -902,7 +912,7 @@ pub const StreamHandler = struct {
|
|||
//-------------------------------------------------------------------------
|
||||
// OSC
|
||||
|
||||
pub fn changeWindowTitle(self: *StreamHandler, title: []const u8) !void {
|
||||
fn windowTitle(self: *StreamHandler, title: []const u8) !void {
|
||||
var buf: [256]u8 = undefined;
|
||||
if (title.len >= buf.len) {
|
||||
log.warn("change title requested larger than our buffer size, ignoring", .{});
|
||||
|
|
@ -933,7 +943,7 @@ pub const StreamHandler = struct {
|
|||
self.surfaceMessageWriter(.{ .set_title = buf });
|
||||
}
|
||||
|
||||
pub inline fn setMouseShape(
|
||||
inline fn setMouseShape(
|
||||
self: *StreamHandler,
|
||||
shape: terminal.MouseShape,
|
||||
) !void {
|
||||
|
|
@ -945,7 +955,7 @@ pub const StreamHandler = struct {
|
|||
self.surfaceMessageWriter(.{ .set_mouse_shape = shape });
|
||||
}
|
||||
|
||||
pub fn clipboardContents(self: *StreamHandler, kind: u8, data: []const u8) !void {
|
||||
fn clipboardContents(self: *StreamHandler, kind: u8, data: []const u8) !void {
|
||||
// Note: we ignore the "kind" field and always use the standard clipboard.
|
||||
// iTerm also appears to do this but other terminals seem to only allow
|
||||
// certain. Let's investigate more.
|
||||
|
|
@ -975,13 +985,13 @@ pub const StreamHandler = struct {
|
|||
});
|
||||
}
|
||||
|
||||
pub inline fn promptStart(self: *StreamHandler, aid: ?[]const u8, redraw: bool) !void {
|
||||
inline fn promptStart(self: *StreamHandler, aid: ?[]const u8, redraw: bool) void {
|
||||
_ = aid;
|
||||
self.terminal.markSemanticPrompt(.prompt);
|
||||
self.terminal.flags.shell_redraws_prompt = redraw;
|
||||
}
|
||||
|
||||
pub inline fn promptContinuation(self: *StreamHandler, aid: ?[]const u8) !void {
|
||||
inline fn promptContinuation(self: *StreamHandler, aid: ?[]const u8) void {
|
||||
_ = aid;
|
||||
self.terminal.markSemanticPrompt(.prompt_continuation);
|
||||
}
|
||||
|
|
@ -995,11 +1005,11 @@ pub const StreamHandler = struct {
|
|||
self.surfaceMessageWriter(.start_command);
|
||||
}
|
||||
|
||||
pub inline fn endOfCommand(self: *StreamHandler, exit_code: ?u8) !void {
|
||||
inline fn endOfCommand(self: *StreamHandler, exit_code: ?u8) void {
|
||||
self.surfaceMessageWriter(.{ .stop_command = exit_code });
|
||||
}
|
||||
|
||||
pub fn reportPwd(self: *StreamHandler, url: []const u8) !void {
|
||||
fn reportPwd(self: *StreamHandler, url: []const u8) !void {
|
||||
// Special handling for the empty URL. We treat the empty URL
|
||||
// as resetting the pwd as if we never saw a pwd. I can't find any
|
||||
// other terminal that does this but it seems like a reasonable
|
||||
|
|
@ -1013,7 +1023,7 @@ pub const StreamHandler = struct {
|
|||
// If we haven't seen a title, we're using the pwd as our title.
|
||||
// Set it to blank which will reset our title behavior.
|
||||
if (!self.seen_title) {
|
||||
try self.changeWindowTitle("");
|
||||
try self.windowTitle("");
|
||||
assert(!self.seen_title);
|
||||
}
|
||||
|
||||
|
|
@ -1093,7 +1103,7 @@ pub const StreamHandler = struct {
|
|||
|
||||
// If we haven't seen a title, use our pwd as the title.
|
||||
if (!self.seen_title) {
|
||||
try self.changeWindowTitle(path);
|
||||
try self.windowTitle(path);
|
||||
self.seen_title = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1347,7 +1357,7 @@ pub const StreamHandler = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn showDesktopNotification(
|
||||
fn showDesktopNotification(
|
||||
self: *StreamHandler,
|
||||
title: []const u8,
|
||||
body: []const u8,
|
||||
|
|
@ -1500,7 +1510,7 @@ pub const StreamHandler = struct {
|
|||
}
|
||||
|
||||
/// Display a GUI progress report.
|
||||
pub fn handleProgressReport(self: *StreamHandler, report: terminal.osc.Command.ProgressReport) error{}!void {
|
||||
fn progressReport(self: *StreamHandler, report: terminal.osc.Command.ProgressReport) void {
|
||||
self.surfaceMessageWriter(.{ .progress_report = report });
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue