From d9df4154dbe6a21e43c96cc1537b3724a0c784ba Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 20 Mar 2026 09:04:08 -0700 Subject: [PATCH] vt: add cursor field data getters to render state API Expose the cursor fields from RenderState.Cursor through the C API via new GhosttyRenderStateData enum values. This adds getters for visual style, visibility, blink state, password input detection, and viewport position (x, y, wide tail). A new GhosttyRenderStateCursorVisualStyle enum maps the Zig cursor.Style values (bar, block, underline, block_hollow) to stable C integer constants. Viewport position getters return GHOSTTY_INVALID_VALUE when the cursor is not visible within the viewport. --- include/ghostty/vt/render.h | 47 +++++++++++++++++++++++++++++++++++++ src/terminal/c/render.zig | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/include/ghostty/vt/render.h b/include/ghostty/vt/render.h index 3823d56f5..bf79cdd7e 100644 --- a/include/ghostty/vt/render.h +++ b/include/ghostty/vt/render.h @@ -102,6 +102,25 @@ typedef enum { GHOSTTY_RENDER_STATE_DIRTY_FULL = 2, } GhosttyRenderStateDirty; +/** + * Visual style of the cursor. + * + * @ingroup render + */ +typedef enum { + /** Bar cursor (DECSCUSR 5, 6). */ + GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BAR = 0, + + /** Block cursor (DECSCUSR 1, 2). */ + GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK = 1, + + /** Underline cursor (DECSCUSR 3, 4). */ + GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_UNDERLINE = 2, + + /** Hollow block cursor. */ + GHOSTTY_RENDER_STATE_CURSOR_VISUAL_STYLE_BLOCK_HOLLOW = 3, +} GhosttyRenderStateCursorVisualStyle; + /** * Queryable data kinds for ghostty_render_state_get(). * @@ -143,6 +162,34 @@ typedef enum { /** The active 256-color palette (GhosttyColorRgb[256]). */ GHOSTTY_RENDER_STATE_DATA_COLOR_PALETTE = 9, + + /** The visual style of the cursor (GhosttyRenderStateCursorVisualStyle). */ + GHOSTTY_RENDER_STATE_DATA_CURSOR_VISUAL_STYLE = 10, + + /** Whether the cursor is visible based on terminal modes (bool). */ + GHOSTTY_RENDER_STATE_DATA_CURSOR_VISIBLE = 11, + + /** Whether the cursor should blink based on terminal modes (bool). */ + GHOSTTY_RENDER_STATE_DATA_CURSOR_BLINKING = 12, + + /** Whether the cursor is at a password input field (bool). */ + GHOSTTY_RENDER_STATE_DATA_CURSOR_PASSWORD_INPUT = 13, + + /** Whether the cursor is visible within the viewport (bool). + * If false, the cursor viewport position values are undefined. */ + GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_HAS_VALUE = 14, + + /** Cursor viewport x position in cells (uint16_t). + * Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */ + GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_X = 15, + + /** Cursor viewport y position in cells (uint16_t). + * Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */ + GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_Y = 16, + + /** Whether the cursor is on the tail of a wide character (bool). + * Only valid when CURSOR_VIEWPORT_HAS_VALUE is true. */ + GHOSTTY_RENDER_STATE_DATA_CURSOR_VIEWPORT_WIDE_TAIL = 17, } GhosttyRenderStateData; /** diff --git a/src/terminal/c/render.zig b/src/terminal/c/render.zig index 75a02b114..3feb4c9fb 100644 --- a/src/terminal/c/render.zig +++ b/src/terminal/c/render.zig @@ -5,6 +5,7 @@ const lib = @import("../../lib/main.zig"); const lib_alloc = @import("../../lib/allocator.zig"); const CAllocator = lib_alloc.Allocator; const colorpkg = @import("../color.zig"); +const cursorpkg = @import("../cursor.zig"); const page = @import("../page.zig"); const size = @import("../size.zig"); const Style = @import("../style.zig").Style; @@ -53,6 +54,23 @@ pub const RowCells = ?*RowCellsWrapper; /// C: GhosttyRenderStateDirty pub const Dirty = renderpkg.RenderState.Dirty; +/// C: GhosttyRenderStateCursorVisualStyle +pub const CursorVisualStyle = enum(c_int) { + bar = 0, + block = 1, + underline = 2, + block_hollow = 3, + + pub fn fromCursorStyle(s: cursorpkg.Style) CursorVisualStyle { + return switch (s) { + .bar => .bar, + .block => .block, + .underline => .underline, + .block_hollow => .block_hollow, + }; + } +}; + /// C: GhosttyRenderStateData pub const Data = enum(c_int) { invalid = 0, @@ -65,6 +83,14 @@ pub const Data = enum(c_int) { color_cursor = 7, color_cursor_has_value = 8, color_palette = 9, + cursor_visual_style = 10, + cursor_visible = 11, + cursor_blinking = 12, + cursor_password_input = 13, + cursor_viewport_has_value = 14, + cursor_viewport_x = 15, + cursor_viewport_y = 16, + cursor_viewport_wide_tail = 17, /// Output type expected for querying the data of the given kind. pub fn OutType(comptime self: Data) type { @@ -76,6 +102,10 @@ pub const Data = enum(c_int) { .color_background, .color_foreground, .color_cursor => colorpkg.RGB.C, .color_cursor_has_value => bool, .color_palette => [256]colorpkg.RGB.C, + .cursor_visual_style => CursorVisualStyle, + .cursor_visible, .cursor_blinking, .cursor_password_input => bool, + .cursor_viewport_has_value, .cursor_viewport_wide_tail => bool, + .cursor_viewport_x, .cursor_viewport_y => size.CellCountInt, }; } }; @@ -197,6 +227,23 @@ fn getTyped( dst.* = src.cval(); } }, + .cursor_visual_style => out.* = CursorVisualStyle.fromCursorStyle(state.state.cursor.visual_style), + .cursor_visible => out.* = state.state.cursor.visible, + .cursor_blinking => out.* = state.state.cursor.blinking, + .cursor_password_input => out.* = state.state.cursor.password_input, + .cursor_viewport_has_value => out.* = state.state.cursor.viewport != null, + .cursor_viewport_x => { + const vp = state.state.cursor.viewport orelse return .invalid_value; + out.* = vp.x; + }, + .cursor_viewport_y => { + const vp = state.state.cursor.viewport orelse return .invalid_value; + out.* = vp.y; + }, + .cursor_viewport_wide_tail => { + const vp = state.state.cursor.viewport orelse return .invalid_value; + out.* = vp.wide_tail; + }, } return .success;