diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index ddfcb9c0d..9ddc2b54b 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -232,6 +232,26 @@ typedef enum GHOSTTY_ENUM_TYPED { GHOSTTY_TERMINAL_SCREEN_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyTerminalScreen; +/** + * Visual style of the terminal cursor. + * + * @ingroup terminal + */ +typedef enum GHOSTTY_ENUM_TYPED { + /** Bar cursor (DECSCUSR 5, 6). */ + GHOSTTY_TERMINAL_CURSOR_STYLE_BAR = 0, + + /** Block cursor (DECSCUSR 1, 2). */ + GHOSTTY_TERMINAL_CURSOR_STYLE_BLOCK = 1, + + /** Underline cursor (DECSCUSR 3, 4). */ + GHOSTTY_TERMINAL_CURSOR_STYLE_UNDERLINE = 2, + + /** Hollow block cursor. */ + GHOSTTY_TERMINAL_CURSOR_STYLE_BLOCK_HOLLOW = 3, + GHOSTTY_TERMINAL_CURSOR_STYLE_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, +} GhosttyTerminalCursorStyle; + /** * Scrollbar state for the terminal viewport. * @@ -608,6 +628,15 @@ typedef enum GHOSTTY_ENUM_TYPED { * Input type: GhosttySelection* */ GHOSTTY_TERMINAL_OPT_SELECTION = 21, + + /** + * Set the default cursor style used by DECSCUSR reset (CSI 0 q). + * + * A NULL value pointer resets to the built-in default block cursor. + * + * Input type: GhosttyTerminalCursorStyle* + */ + GHOSTTY_TERMINAL_OPT_DEFAULT_CURSOR_STYLE = 22, GHOSTTY_TERMINAL_OPT_MAX_VALUE = GHOSTTY_ENUM_MAX_VALUE, } GhosttyTerminalOption; diff --git a/src/terminal/c/terminal.zig b/src/terminal/c/terminal.zig index 5a5db2d6b..59fac7e70 100644 --- a/src/terminal/c/terminal.zig +++ b/src/terminal/c/terminal.zig @@ -5,6 +5,7 @@ const lib = @import("../lib.zig"); const CAllocator = lib.alloc.Allocator; pub const ZigTerminal = @import("../Terminal.zig"); const Stream = @import("../stream_terminal.zig").Stream; +const Screen = @import("../Screen.zig"); const ScreenSet = @import("../ScreenSet.zig"); const PageList = @import("../PageList.zig"); const apc = @import("../apc.zig"); @@ -326,6 +327,7 @@ pub const Option = enum(c_int) { apc_max_bytes = 19, apc_max_bytes_kitty = 20, selection = 21, + default_cursor_style = 22, /// Input type expected for setting the option. pub fn InType(comptime self: Option) type { @@ -349,6 +351,7 @@ pub const Option = enum(c_int) { => ?*const bool, .apc_max_bytes, .apc_max_bytes_kitty => ?*const usize, .selection => ?*const selection_c.CSelection, + .default_cursor_style => ?*const TerminalCursorStyle, }; } }; @@ -464,10 +467,36 @@ fn setTyped( wrapper.terminal.screens.active.clearSelection(); } }, + .default_cursor_style => { + const style = (if (value) |ptr| ptr.* else TerminalCursorStyle.block).toZig() orelse return .invalid_value; + wrapper.stream.handler.default_cursor_style = style; + if (wrapper.stream.handler.default_cursor) { + wrapper.terminal.screens.active.cursor.cursor_style = style; + } + }, } return .success; } +/// C: GhosttyTerminalCursorStyle +pub const TerminalCursorStyle = enum(c_int) { + bar = 0, + block = 1, + underline = 2, + block_hollow = 3, + _, + + fn toZig(self: TerminalCursorStyle) ?Screen.CursorStyle { + return switch (self) { + .bar => .bar, + .block => .block, + .underline => .underline, + .block_hollow => .block_hollow, + _ => null, + }; + } +}; + /// C: GhosttyDeviceAttributes pub const DeviceAttributes = Effects.CDeviceAttributes; diff --git a/src/terminal/stream_terminal.zig b/src/terminal/stream_terminal.zig index 51ef63422..560e12c2a 100644 --- a/src/terminal/stream_terminal.zig +++ b/src/terminal/stream_terminal.zig @@ -43,6 +43,10 @@ pub const Handler = struct { /// the kitty graphics protocol. apc_handler: apc.Handler = .{}, + /// Default cursor style used by DECSCUSR reset (CSI 0 q). + default_cursor: bool = true, + default_cursor_style: Screen.CursorStyle = .block, + pub const Effects = struct { /// Called when the terminal needs to write data back to the pty, /// e.g. in response to a DECRQM query. The data is only valid @@ -152,12 +156,18 @@ pub const Handler = struct { self.terminal.screens.active.cursor.x + 1, ), .cursor_style => { + self.default_cursor = false; + const blink = switch (value) { .default, .steady_block, .steady_bar, .steady_underline => false, .blinking_block, .blinking_bar, .blinking_underline => true, }; const style: Screen.CursorStyle = switch (value) { - .default, .blinking_block, .steady_block => .block, + .default => style: { + self.default_cursor = true; + break :style self.default_cursor_style; + }, + .blinking_block, .steady_block => .block, .blinking_bar, .steady_bar => .bar, .blinking_underline, .steady_underline => .underline, }; @@ -228,7 +238,11 @@ pub const Handler = struct { }, .active_status_display => self.terminal.status_display = value, .decaln => try self.terminal.decaln(), - .full_reset => self.terminal.fullReset(), + .full_reset => { + self.terminal.fullReset(); + self.default_cursor = true; + self.terminal.screens.active.cursor.cursor_style = self.default_cursor_style; + }, .start_hyperlink => try self.terminal.screens.active.startHyperlink(value.uri, value.id), .end_hyperlink => self.terminal.screens.active.endHyperlink(), .semantic_prompt => try self.terminal.semanticPrompt(value),