Merge pull request #1253 from jcollie/enquiry

implement enquiry/answerback
This commit is contained in:
Mitchell Hashimoto
2024-01-10 09:29:10 -08:00
committed by GitHub
3 changed files with 109 additions and 109 deletions

View File

@ -10,12 +10,20 @@ operator.
## Implementation Details
- ghostty always sends `""`
The answerback can be configured in the config file using the `enquiry-response`
configuration setting or on the command line using the `--enquiry-response`
parameter. The default is to send an empty string (`""`).
## TODO
- Make the answerback configurable
- Implement method for changing the answerback on-the-fly. This could be part of
a larger configuration editor or as a stand-alone method.
## References
- https://vt100.net/docs/vt100-ug/chapter3.html
- https://documentation.help/PuTTY/config-answerback.html
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Single-character-functions:ENQ.C29
- https://invisible-island.net/xterm/manpage/xterm.html#VT100-Widget-Resources:answerbackString
- https://iterm2.com/documentation-preferences-profiles-terminal.html
- https://iterm2.com/documentation-scripting.html

View File

@ -911,6 +911,10 @@ keybind: Keybinds = .{},
/// from working properly. https://github.com/vim/vim/pull/13211 fixes this.
term: []const u8 = "xterm-ghostty",
/// String to send when we receive ENQ (0x05) from the command that we are
/// running. Defaults to "" if not set.
@"enquiry-response": []const u8 = "",
/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,

View File

@ -44,10 +44,8 @@ alloc: Allocator,
/// This is the pty fd created for the subcommand.
subprocess: Subprocess,
/// The number of milliseconds below which we consider a process
/// exit to be abnormal. This is used to show an error message
/// when the process exits too quickly.
abnormal_runtime_threshold_ms: u32,
/// The derived configuration for this termio implementation.
config: DerivedConfig,
/// The terminal emulator internal state. This is the abstract "terminal"
/// that manages input, grid updating, etc. and is renderer-agnostic. It
@ -70,30 +68,6 @@ surface_mailbox: apprt.surface.Mailbox,
/// The cached grid size whenever a resize is called.
grid_size: renderer.GridSize,
/// The default cursor style. We need to know this so that we can set
/// it when a CSI q with default is called.
default_cursor_style: terminal.Cursor.Style,
default_cursor_blink: ?bool,
default_cursor_color: ?terminal.color.RGB,
/// Actual cursor color
cursor_color: ?terminal.color.RGB,
/// Default foreground color as set by the config file
default_foreground_color: terminal.color.RGB,
/// Default background color as set by the config file
default_background_color: terminal.color.RGB,
/// Actual foreground color
foreground_color: terminal.color.RGB,
/// Actual background color
background_color: terminal.color.RGB,
/// The OSC 10/11 reply style.
osc_color_report_format: configpkg.Config.OSCColorReportFormat,
/// The data associated with the currently running thread.
data: ?*EventData,
@ -101,6 +75,8 @@ data: ?*EventData,
/// configuration. This must be exported so that we don't need to
/// pass around Config pointers which makes memory management a pain.
pub const DerivedConfig = struct {
arena: ArenaAllocator,
palette: terminal.color.Palette,
image_storage_limit: usize,
cursor_style: terminal.Cursor.Style,
@ -112,12 +88,15 @@ pub const DerivedConfig = struct {
term: []const u8,
grapheme_width_method: configpkg.Config.GraphemeWidthMethod,
abnormal_runtime_threshold_ms: u32,
enquiry_response: []const u8,
pub fn init(
alloc_gpa: Allocator,
config: *const configpkg.Config,
) !DerivedConfig {
_ = alloc_gpa;
var arena = ArenaAllocator.init(alloc_gpa);
errdefer arena.deinit();
const alloc = arena.allocator();
return .{
.palette = config.palette.value,
@ -128,24 +107,25 @@ pub const DerivedConfig = struct {
.foreground = config.foreground,
.background = config.background,
.osc_color_report_format = config.@"osc-color-report-format",
.term = config.term,
.term = try alloc.dupe(u8, config.term),
.grapheme_width_method = config.@"grapheme-width-method",
.abnormal_runtime_threshold_ms = config.@"abnormal-command-exit-runtime",
.enquiry_response = try alloc.dupe(u8, config.@"enquiry-response"),
// This has to be last so that we copy AFTER the arena allocations
// above happen (Zig assigns in order).
.arena = arena,
};
}
pub fn deinit(self: *DerivedConfig) void {
_ = self;
self.arena.deinit();
}
};
/// Initialize the exec implementation. This will also start the child
/// process.
pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
// Clean up our derived config because we don't need it after this.
var config = opts.config;
defer config.deinit();
// Create our terminal
var term = try terminal.Terminal.init(
alloc,
@ -188,36 +168,20 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
.alloc = alloc,
.terminal = term,
.subprocess = subprocess,
.config = opts.config,
.renderer_state = opts.renderer_state,
.renderer_wakeup = opts.renderer_wakeup,
.renderer_mailbox = opts.renderer_mailbox,
.surface_mailbox = opts.surface_mailbox,
.grid_size = opts.grid_size,
.default_cursor_style = opts.config.cursor_style,
.default_cursor_blink = opts.config.cursor_blink,
.default_cursor_color = if (opts.config.cursor_color) |col|
col.toTerminalRGB()
else
null,
.cursor_color = if (opts.config.cursor_color) |col|
col.toTerminalRGB()
else
null,
.default_foreground_color = config.foreground.toTerminalRGB(),
.default_background_color = config.background.toTerminalRGB(),
.foreground_color = config.foreground.toTerminalRGB(),
.background_color = config.background.toTerminalRGB(),
.osc_color_report_format = config.osc_color_report_format,
.data = null,
.abnormal_runtime_threshold_ms = opts.config.abnormal_runtime_threshold_ms,
};
}
pub fn deinit(self: *Exec) void {
self.subprocess.deinit();
// Clean up our other members
self.terminal.deinit(self.alloc);
self.config.deinit();
}
pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
@ -283,22 +247,13 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
.data_stream = stream,
.loop = &thread.loop,
.terminal_stream = .{
.handler = .{
.alloc = self.alloc,
.ev = ev_data_ptr,
.terminal = &self.terminal,
.grid_size = &self.grid_size,
.default_cursor_style = self.default_cursor_style,
.default_cursor_blink = self.default_cursor_blink,
.default_cursor_color = self.default_cursor_color,
.cursor_color = self.cursor_color,
.default_foreground_color = self.default_foreground_color,
.default_background_color = self.default_background_color,
.foreground_color = self.foreground_color,
.background_color = self.background_color,
.osc_color_report_format = self.osc_color_report_format,
},
.handler = StreamHandler.init(
self.alloc,
ev_data_ptr,
&self.grid_size,
&self.terminal,
&self.config,
),
.parser = .{
.osc_parser = .{
// Populate the OSC parser allocator (optional) because
@ -307,7 +262,7 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
},
},
},
.abnormal_runtime_threshold_ms = self.abnormal_runtime_threshold_ms,
.abnormal_runtime_threshold_ms = self.config.abnormal_runtime_threshold_ms,
};
errdefer ev_data_ptr.deinit(self.alloc);
@ -391,7 +346,21 @@ pub fn threadExit(self: *Exec, data: ThreadData) void {
/// Update the configuration.
pub fn changeConfig(self: *Exec, config: *DerivedConfig) !void {
defer config.deinit();
// The remainder of this function is modifying terminal state or
// the read thread data, all of which requires holding the renderer
// state lock.
self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock();
// Deinit our old config. We do this in the lock because the
// stream handler may be referencing the old config (i.e. enquiry resp)
self.config.deinit();
self.config = config.*;
// Update our stream handler. The stream handler uses the same
// renderer mutex so this is safe to do despite being executed
// from another thread.
if (self.data) |data| data.terminal_stream.handler.changeConfig(&self.config);
// Update the configuration that we know about.
//
@ -413,26 +382,6 @@ pub fn changeConfig(self: *Exec, config: *DerivedConfig) !void {
}
}
// Update our default cursor style
self.default_cursor_style = config.cursor_style;
self.default_cursor_blink = config.cursor_blink;
self.default_cursor_color = if (config.cursor_color) |col|
col.toTerminalRGB()
else
null;
// Update default foreground and background colors
self.default_foreground_color = config.foreground.toTerminalRGB();
self.default_background_color = config.background.toTerminalRGB();
// If we have event data, then update our active stream too
if (self.data) |data| {
data.terminal_stream.handler.changeDefaultCursor(
config.cursor_style,
config.cursor_blink,
);
}
// Set the image size limits
try self.terminal.screen.kitty_images.setLimit(
self.alloc,
@ -1693,13 +1642,66 @@ const StreamHandler = struct {
foreground_color: terminal.color.RGB,
background_color: terminal.color.RGB,
/// The response to use for ENQ requests. The memory is owned by
/// whoever owns StreamHandler.
enquiry_response: []const u8,
osc_color_report_format: configpkg.Config.OSCColorReportFormat,
pub fn init(
alloc: Allocator,
ev: *EventData,
grid_size: *renderer.GridSize,
t: *terminal.Terminal,
config: *const DerivedConfig,
) StreamHandler {
const default_cursor_color = if (config.cursor_color) |col|
col.toTerminalRGB()
else
null;
return .{
.alloc = alloc,
.ev = ev,
.grid_size = grid_size,
.terminal = t,
.osc_color_report_format = config.osc_color_report_format,
.enquiry_response = config.enquiry_response,
.default_foreground_color = config.foreground.toTerminalRGB(),
.default_background_color = config.background.toTerminalRGB(),
.default_cursor_style = config.cursor_style,
.default_cursor_blink = config.cursor_blink,
.default_cursor_color = default_cursor_color,
.cursor_color = default_cursor_color,
.foreground_color = config.foreground.toTerminalRGB(),
.background_color = config.background.toTerminalRGB(),
};
}
pub fn deinit(self: *StreamHandler) void {
self.apc.deinit();
self.dcs.deinit();
}
/// Change the configuration for this handler.
pub fn changeConfig(self: *StreamHandler, config: *DerivedConfig) void {
self.osc_color_report_format = config.osc_color_report_format;
self.enquiry_response = config.enquiry_response;
self.default_foreground_color = config.foreground.toTerminalRGB();
self.default_background_color = config.background.toTerminalRGB();
self.default_cursor_style = config.cursor_style;
self.default_cursor_blink = config.cursor_blink;
self.default_cursor_color = if (config.cursor_color) |col|
col.toTerminalRGB()
else
null;
// If our cursor is the default, then we update it immediately.
if (self.default_cursor) self.setCursorStyle(.default) catch |err| {
log.warn("failed to set default cursor style: {}", .{err});
};
}
inline fn queueRender(self: *StreamHandler) !void {
try self.ev.queueRender();
}
@ -1749,21 +1751,6 @@ const StreamHandler = struct {
self.writer_messaged = true;
}
pub fn changeDefaultCursor(
self: *StreamHandler,
style: terminal.Cursor.Style,
blink: ?bool,
) void {
self.default_cursor_style = style;
self.default_cursor_blink = blink;
// If our cursor is the default, then we update it immediately.
if (self.default_cursor) self.setCursorStyle(.default) catch |err| {
log.warn("failed to set default cursor style: {}", .{err});
return;
};
}
pub fn dcsHook(self: *StreamHandler, dcs: terminal.DCS) !void {
self.dcs.hook(self.alloc, dcs);
}
@ -2390,7 +2377,8 @@ const StreamHandler = struct {
}
pub fn enquiry(self: *StreamHandler) !void {
self.messageWriter(.{ .write_stable = "" });
log.debug("sending enquiry response={s}", .{self.enquiry_response});
self.messageWriter(try termio.Message.writeReq(self.alloc, self.enquiry_response));
}
pub fn scrollDown(self: *StreamHandler, count: usize) !void {