core: report cursor postion to the app runtimes

This commit is contained in:
Jeffrey C. Ollie
2025-01-18 15:50:37 -06:00
parent 72d085525b
commit e469699278
10 changed files with 133 additions and 0 deletions

View File

@ -597,6 +597,7 @@ typedef enum {
GHOSTTY_ACTION_COLOR_CHANGE, GHOSTTY_ACTION_COLOR_CHANGE,
GHOSTTY_ACTION_RELOAD_CONFIG, GHOSTTY_ACTION_RELOAD_CONFIG,
GHOSTTY_ACTION_CONFIG_CHANGE, GHOSTTY_ACTION_CONFIG_CHANGE,
GHOSTTY_ACTION_REPORT_CURSOR_POSITION,
} ghostty_action_tag_e; } ghostty_action_tag_e;
typedef union { typedef union {

View File

@ -71,6 +71,10 @@ renderer_thread: renderer.Thread,
/// The actual thread /// The actual thread
renderer_thr: std.Thread, renderer_thr: std.Thread,
/// Cursor state. Updated at most once per frame so it may not be accurate 100%
/// of the time.
cursor: Cursor,
/// Mouse state. /// Mouse state.
mouse: Mouse, mouse: Mouse,
@ -150,6 +154,37 @@ pub const InputEffect = enum {
closed, closed,
}; };
/// Cursor state for the surface.
const Cursor = struct {
x: terminal.size.CellCountInt = 0,
y: terminal.size.CellCountInt = 0,
pub fn reportCursorPosition(self: *Cursor, x: terminal.size.CellCountInt, y: terminal.size.CellCountInt) void {
const surface: *Surface = @alignCast(@fieldParentPtr("cursor", self));
// Defer updating the stored cursor position until after the apprt has been
// informed of the new position.
defer {
self.x = x;
self.y = y;
}
surface.rt_app.performAction(
.{ .surface = surface },
.report_cursor_position,
.{
.x = x,
.y = y,
},
) catch |err| {
log.warn(
"failed to notify surface of cursor position err={}",
.{err},
);
};
}
};
/// Mouse state for the surface. /// Mouse state for the surface.
const Mouse = struct { const Mouse = struct {
/// The last tracked mouse button state by button. /// The last tracked mouse button state by button.
@ -496,6 +531,7 @@ pub fn init(
.terminal = &self.io.terminal, .terminal = &self.io.terminal,
}, },
.renderer_thr = undefined, .renderer_thr = undefined,
.cursor = .{},
.mouse = .{}, .mouse = .{},
.keyboard = .{}, .keyboard = .{},
.io = undefined, .io = undefined,
@ -943,6 +979,8 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
.present_surface => try self.presentSurface(), .present_surface => try self.presentSurface(),
.password_input => |v| try self.passwordInput(v), .password_input => |v| try self.passwordInput(v),
.report_cursor_position => |v| self.cursor.reportCursorPosition(v.x, v.y),
} }
} }

View File

@ -226,6 +226,10 @@ pub const Action = union(Key) {
/// for changes. /// for changes.
config_change: ConfigChange, config_change: ConfigChange,
/// Report the location of the cursor. This will happen at most once per
/// frame, and only if the cursor has moved since the last update.
report_cursor_position: CursorPositionReport,
/// Sync with: ghostty_action_tag_e /// Sync with: ghostty_action_tag_e
pub const Key = enum(c_int) { pub const Key = enum(c_int) {
quit, quit,
@ -266,6 +270,7 @@ pub const Action = union(Key) {
color_change, color_change,
reload_config, reload_config,
config_change, config_change,
report_cursor_position,
}; };
/// Sync with: ghostty_action_u /// Sync with: ghostty_action_u
@ -549,3 +554,9 @@ pub const ConfigChange = struct {
}; };
} }
}; };
/// Used to report the location of the cursor.
pub const CursorPositionReport = extern struct {
x: terminal.size.CellCountInt,
y: terminal.size.CellCountInt,
};

View File

@ -238,6 +238,7 @@ pub const App = struct {
.pwd, .pwd,
.config_change, .config_change,
.toggle_maximize, .toggle_maximize,
.report_cursor_position,
=> log.info("unimplemented action={}", .{action}), => log.info("unimplemented action={}", .{action}),
} }
} }

View File

@ -535,6 +535,7 @@ pub fn performAction(
.toggle_split_zoom => self.toggleSplitZoom(target), .toggle_split_zoom => self.toggleSplitZoom(target),
.toggle_window_decorations => self.toggleWindowDecorations(target), .toggle_window_decorations => self.toggleWindowDecorations(target),
.quit_timer => self.quitTimer(value), .quit_timer => self.quitTimer(value),
.report_cursor_position => self.reportCursorPosition(target, value),
// Unimplemented // Unimplemented
.close_all_windows, .close_all_windows,
@ -1898,6 +1899,17 @@ pub fn refreshContextMenu(_: *App, window: ?*c.GtkWindow, has_selection: bool) v
c.g_simple_action_set_enabled(action, if (has_selection) 1 else 0); c.g_simple_action_set_enabled(action, if (has_selection) 1 else 0);
} }
fn reportCursorPosition(
_: *const App,
target: apprt.Target,
value: apprt.action.CursorPositionReport,
) void {
switch (target) {
.app => {},
.surface => |v| v.rt_surface.reportCursorPosition(value),
}
}
fn isValidAppId(app_id: [:0]const u8) bool { fn isValidAppId(app_id: [:0]const u8) bool {
if (app_id.len > 255 or app_id.len == 0) return false; if (app_id.len > 255 or app_id.len == 0) return false;
if (app_id[0] == '.') return false; if (app_id[0] == '.') return false;

View File

@ -2189,3 +2189,8 @@ fn g_value_holds(value_: ?*c.GValue, g_type: c.GType) bool {
} }
return false; return false;
} }
pub fn reportCursorPosition(self: *Surface, position: apprt.action.CursorPositionReport) void {
_ = self;
log.debug("cursor position: {d}×{d}", .{ position.x, position.y });
}

View File

@ -81,6 +81,12 @@ pub const Message = union(enum) {
/// The terminal has reported a change in the working directory. /// The terminal has reported a change in the working directory.
pwd_change: WriteReq, pwd_change: WriteReq,
/// Report cursor position
report_cursor_position: struct {
x: terminal.size.CellCountInt,
y: terminal.size.CellCountInt,
},
pub const ReportTitleStyle = enum { pub const ReportTitleStyle = enum {
csi_21_t, csi_21_t,

View File

@ -25,6 +25,7 @@ const graphics = macos.graphics;
const fgMode = @import("cell.zig").fgMode; const fgMode = @import("cell.zig").fgMode;
const isCovering = @import("cell.zig").isCovering; const isCovering = @import("cell.zig").isCovering;
const shadertoy = @import("shadertoy.zig"); const shadertoy = @import("shadertoy.zig");
const CursorPosition = @import("cursor_position.zig").CursorPosition;
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
@ -161,6 +162,9 @@ health: std.atomic.Value(Health) = .{ .raw = .healthy },
/// Our GPU state /// Our GPU state
gpu_state: GPUState, gpu_state: GPUState,
/// The cursor position as of the last frame update
cursor_position: CursorPosition(Metal) = .{},
/// State we need for the GPU that is shared between all frames. /// State we need for the GPU that is shared between all frames.
pub const GPUState = struct { pub const GPUState = struct {
// The count of buffers we use for double/triple buffering. If // The count of buffers we use for double/triple buffering. If
@ -991,6 +995,10 @@ pub fn updateFrame(
cursor_style: ?renderer.CursorStyle, cursor_style: ?renderer.CursorStyle,
color_palette: terminal.color.Palette, color_palette: terminal.color.Palette,
viewport_pin: terminal.Pin, viewport_pin: terminal.Pin,
cursor_position: struct {
x: terminal.size.CellCountInt,
y: terminal.size.CellCountInt,
},
/// If true, rebuild the full screen. /// If true, rebuild the full screen.
full_rebuild: bool, full_rebuild: bool,
@ -1154,6 +1162,10 @@ pub fn updateFrame(
.color_palette = state.terminal.color_palette.colors, .color_palette = state.terminal.color_palette.colors,
.viewport_pin = viewport_pin, .viewport_pin = viewport_pin,
.full_rebuild = full_rebuild, .full_rebuild = full_rebuild,
.cursor_position = .{
.x = state.terminal.screen.cursor.x,
.y = state.terminal.screen.cursor.y,
},
}; };
}; };
defer { defer {
@ -1240,6 +1252,8 @@ pub fn updateFrame(
} }
} }
} }
self.cursor_position.update(critical.cursor_position.x, critical.cursor_position.y);
} }
/// Draw the frame to the screen. /// Draw the frame to the screen.

View File

@ -30,6 +30,7 @@ const custom = @import("opengl/custom.zig");
const Image = gl_image.Image; const Image = gl_image.Image;
const ImageMap = gl_image.ImageMap; const ImageMap = gl_image.ImageMap;
const ImagePlacementList = std.ArrayListUnmanaged(gl_image.Placement); const ImagePlacementList = std.ArrayListUnmanaged(gl_image.Placement);
const CursorPosition = @import("cursor_position.zig").CursorPosition;
const log = std.log.scoped(.grid); const log = std.log.scoped(.grid);
@ -146,6 +147,9 @@ image_bg_end: u32 = 0,
image_text_end: u32 = 0, image_text_end: u32 = 0,
image_virtual: bool = false, image_virtual: bool = false,
/// The cursor position as of the last frame update.
cursor_position: CursorPosition(OpenGL) = .{},
/// Deferred OpenGL operation to update the screen size. /// Deferred OpenGL operation to update the screen size.
const SetScreenSize = struct { const SetScreenSize = struct {
size: renderer.Size, size: renderer.Size,
@ -700,6 +704,10 @@ pub fn updateFrame(
screen_type: terminal.ScreenType, screen_type: terminal.ScreenType,
mouse: renderer.State.Mouse, mouse: renderer.State.Mouse,
preedit: ?renderer.State.Preedit, preedit: ?renderer.State.Preedit,
cursor_position: struct {
x: terminal.size.CellCountInt,
y: terminal.size.CellCountInt,
},
cursor_style: ?renderer.CursorStyle, cursor_style: ?renderer.CursorStyle,
color_palette: terminal.color.Palette, color_palette: terminal.color.Palette,
}; };
@ -861,6 +869,10 @@ pub fn updateFrame(
.screen_type = state.terminal.active_screen, .screen_type = state.terminal.active_screen,
.mouse = state.mouse, .mouse = state.mouse,
.preedit = preedit, .preedit = preedit,
.cursor_position = .{
.x = state.terminal.screen.cursor.x,
.y = state.terminal.screen.cursor.y,
},
.cursor_style = cursor_style, .cursor_style = cursor_style,
.color_palette = state.terminal.color_palette.colors, .color_palette = state.terminal.color_palette.colors,
}; };
@ -893,6 +905,8 @@ pub fn updateFrame(
// CoreText this triggers off-thread cleanup logic. // CoreText this triggers off-thread cleanup logic.
self.font_shaper.endFrame(); self.font_shaper.endFrame();
} }
self.cursor_position.update(critical.cursor_position.x, critical.cursor_position.y);
} }
/// This goes through the Kitty graphic placements and accumulates the /// This goes through the Kitty graphic placements and accumulates the

View File

@ -0,0 +1,31 @@
const terminal = @import("../terminal/main.zig");
pub fn CursorPosition(comptime T: type) type {
return struct {
x: terminal.size.CellCountInt = 0,
y: terminal.size.CellCountInt = 0,
pub fn update(
self: *CursorPosition(T),
x: terminal.size.CellCountInt,
y: terminal.size.CellCountInt,
) void {
const renderer: *T = @alignCast(@fieldParentPtr("cursor_position", self));
if (self.x != x or self.y != y) {
_ = renderer.surface_mailbox.push(
.{
.report_cursor_position = .{
.x = x,
.y = y,
},
},
.{ .instant = {} },
);
}
self.x = x;
self.y = y;
}
};
}