mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
feat: parse ConEmu OSC9;1 (#4327)
# Description
This PR implements support for the [ConEmu OSC9;1 escape
sequence](https://conemu.github.io/en/AnsiEscapeCodes.html#OSC_Operating_system_commands).
Based on my understanding of [ConEmu's source
code](740b09c363/src/ConEmuCD/ConAnsiImpl.cpp (L705-L724)
):
- The default timeout is set to `100` milliseconds if no value is
specified.
- The timeout value is clamped to a maximum of `10000` milliseconds.
#3125
This commit is contained in:
@ -279,7 +279,7 @@ pub const VTEvent = struct {
|
|||||||
),
|
),
|
||||||
|
|
||||||
else => switch (Value) {
|
else => switch (Value) {
|
||||||
u8 => try md.put(
|
u8, u16 => try md.put(
|
||||||
key,
|
key,
|
||||||
try std.fmt.allocPrintZ(alloc, "{}", .{value}),
|
try std.fmt.allocPrintZ(alloc, "{}", .{value}),
|
||||||
),
|
),
|
||||||
|
@ -67,7 +67,7 @@ pub const Command = union(enum) {
|
|||||||
|
|
||||||
/// End of current command.
|
/// End of current command.
|
||||||
///
|
///
|
||||||
/// The exit-code need not be specified if if there are no options,
|
/// The exit-code need not be specified if there are no options,
|
||||||
/// or if the command was cancelled (no OSC "133;C"), such as by typing
|
/// or if the command was cancelled (no OSC "133;C"), such as by typing
|
||||||
/// an interrupt/cancel character (typically ctrl-C) during line-editing.
|
/// an interrupt/cancel character (typically ctrl-C) during line-editing.
|
||||||
/// Otherwise, it must be an integer code, where 0 means the command
|
/// Otherwise, it must be an integer code, where 0 means the command
|
||||||
@ -158,6 +158,11 @@ pub const Command = union(enum) {
|
|||||||
/// End a hyperlink (OSC 8)
|
/// End a hyperlink (OSC 8)
|
||||||
hyperlink_end: void,
|
hyperlink_end: void,
|
||||||
|
|
||||||
|
/// Sleep (OSC 9;1)
|
||||||
|
sleep: struct {
|
||||||
|
duration_ms: u16,
|
||||||
|
},
|
||||||
|
|
||||||
/// Show GUI message Box (OSC 9;2)
|
/// Show GUI message Box (OSC 9;2)
|
||||||
show_message_box: []const u8,
|
show_message_box: []const u8,
|
||||||
|
|
||||||
@ -362,6 +367,8 @@ pub const Parser = struct {
|
|||||||
osc_9,
|
osc_9,
|
||||||
|
|
||||||
// ConEmu specific substates
|
// ConEmu specific substates
|
||||||
|
conemu_sleep,
|
||||||
|
conemu_sleep_value,
|
||||||
conemu_message_box,
|
conemu_message_box,
|
||||||
conemu_tab,
|
conemu_tab,
|
||||||
conemu_tab_txt,
|
conemu_tab_txt,
|
||||||
@ -789,6 +796,9 @@ pub const Parser = struct {
|
|||||||
},
|
},
|
||||||
|
|
||||||
.osc_9 => switch (c) {
|
.osc_9 => switch (c) {
|
||||||
|
'1' => {
|
||||||
|
self.state = .conemu_sleep;
|
||||||
|
},
|
||||||
'2' => {
|
'2' => {
|
||||||
self.state = .conemu_message_box;
|
self.state = .conemu_message_box;
|
||||||
},
|
},
|
||||||
@ -806,6 +816,16 @@ pub const Parser = struct {
|
|||||||
else => self.showDesktopNotification(),
|
else => self.showDesktopNotification(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.conemu_sleep => switch (c) {
|
||||||
|
';' => {
|
||||||
|
self.command = .{ .sleep = .{ .duration_ms = 100 } };
|
||||||
|
self.buf_start = self.buf_idx;
|
||||||
|
self.complete = true;
|
||||||
|
self.state = .conemu_sleep_value;
|
||||||
|
},
|
||||||
|
else => self.state = .invalid,
|
||||||
|
},
|
||||||
|
|
||||||
.conemu_message_box => switch (c) {
|
.conemu_message_box => switch (c) {
|
||||||
';' => {
|
';' => {
|
||||||
self.command = .{ .show_message_box = undefined };
|
self.command = .{ .show_message_box = undefined };
|
||||||
@ -817,6 +837,10 @@ pub const Parser = struct {
|
|||||||
else => self.state = .invalid,
|
else => self.state = .invalid,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.conemu_sleep_value => switch (c) {
|
||||||
|
else => self.complete = true,
|
||||||
|
},
|
||||||
|
|
||||||
.conemu_tab => switch (c) {
|
.conemu_tab => switch (c) {
|
||||||
';' => {
|
';' => {
|
||||||
self.state = .conemu_tab_txt;
|
self.state = .conemu_tab_txt;
|
||||||
@ -1193,6 +1217,22 @@ pub const Parser = struct {
|
|||||||
self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx];
|
self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn endConEmuSleepValue(self: *Parser) void {
|
||||||
|
switch (self.command) {
|
||||||
|
.sleep => |*v| v.duration_ms = value: {
|
||||||
|
const str = self.buf[self.buf_start..self.buf_idx];
|
||||||
|
if (str.len == 0) break :value 100;
|
||||||
|
|
||||||
|
if (std.fmt.parseUnsigned(u16, str, 10)) |num| {
|
||||||
|
break :value @min(num, 10_000);
|
||||||
|
} else |_| {
|
||||||
|
break :value 100;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn endKittyColorProtocolOption(self: *Parser, kind: enum { key_only, key_and_value }, final: bool) void {
|
fn endKittyColorProtocolOption(self: *Parser, kind: enum { key_only, key_and_value }, final: bool) void {
|
||||||
if (self.temp_state.key.len == 0) {
|
if (self.temp_state.key.len == 0) {
|
||||||
log.warn("zero length key in kitty color protocol", .{});
|
log.warn("zero length key in kitty color protocol", .{});
|
||||||
@ -1271,6 +1311,7 @@ pub const Parser = struct {
|
|||||||
.semantic_option_value => self.endSemanticOptionValue(),
|
.semantic_option_value => self.endSemanticOptionValue(),
|
||||||
.hyperlink_uri => self.endHyperlink(),
|
.hyperlink_uri => self.endHyperlink(),
|
||||||
.string => self.endString(),
|
.string => self.endString(),
|
||||||
|
.conemu_sleep_value => self.endConEmuSleepValue(),
|
||||||
.allocable_string => self.endAllocableString(),
|
.allocable_string => self.endAllocableString(),
|
||||||
.kitty_color_protocol_key => self.endKittyColorProtocolOption(.key_only, true),
|
.kitty_color_protocol_key => self.endKittyColorProtocolOption(.key_only, true),
|
||||||
.kitty_color_protocol_value => self.endKittyColorProtocolOption(.key_and_value, true),
|
.kitty_color_protocol_value => self.endKittyColorProtocolOption(.key_and_value, true),
|
||||||
@ -1680,6 +1721,62 @@ test "OSC: set palette color" {
|
|||||||
try testing.expectEqualStrings(cmd.set_color.value, "rgb:aa/bb/cc");
|
try testing.expectEqualStrings(cmd.set_color.value, "rgb:aa/bb/cc");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "OSC: conemu sleep" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: Parser = .{};
|
||||||
|
|
||||||
|
const input = "9;1;420";
|
||||||
|
for (input) |ch| p.next(ch);
|
||||||
|
|
||||||
|
const cmd = p.end('\x1b').?;
|
||||||
|
|
||||||
|
try testing.expect(cmd == .sleep);
|
||||||
|
try testing.expectEqual(420, cmd.sleep.duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "OSC: conemu sleep with no value default to 100ms" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: Parser = .{};
|
||||||
|
|
||||||
|
const input = "9;1;";
|
||||||
|
for (input) |ch| p.next(ch);
|
||||||
|
|
||||||
|
const cmd = p.end('\x1b').?;
|
||||||
|
|
||||||
|
try testing.expect(cmd == .sleep);
|
||||||
|
try testing.expectEqual(100, cmd.sleep.duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "OSC: conemu sleep cannot exceed 10000ms" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: Parser = .{};
|
||||||
|
|
||||||
|
const input = "9;1;12345";
|
||||||
|
for (input) |ch| p.next(ch);
|
||||||
|
|
||||||
|
const cmd = p.end('\x1b').?;
|
||||||
|
|
||||||
|
try testing.expect(cmd == .sleep);
|
||||||
|
try testing.expectEqual(10000, cmd.sleep.duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "OSC: conemu sleep invalid input" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: Parser = .{};
|
||||||
|
|
||||||
|
const input = "9;1;foo";
|
||||||
|
for (input) |ch| p.next(ch);
|
||||||
|
|
||||||
|
const cmd = p.end('\x1b').?;
|
||||||
|
|
||||||
|
try testing.expect(cmd == .sleep);
|
||||||
|
try testing.expectEqual(100, cmd.sleep.duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
test "OSC: show desktop notification" {
|
test "OSC: show desktop notification" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
@ -1605,7 +1605,7 @@ pub fn Stream(comptime Handler: type) type {
|
|||||||
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
} else log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||||
},
|
},
|
||||||
|
|
||||||
.progress, .show_message_box, .change_conemu_tab_title => {
|
.progress, .sleep, .show_message_box, .change_conemu_tab_title => {
|
||||||
log.warn("unimplemented OSC callback: {}", .{cmd});
|
log.warn("unimplemented OSC callback: {}", .{cmd});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user