diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-04-04 19:22:04 +0300 |
|---|---|---|
| committer | Can Celik <ogulcancelik@gmail.com> | 2026-04-05 01:23:06 +0300 |
| commit | 41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch) | |
| tree | 3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/apprt/surface.zig | |
| parent | 55c138263e190a1e79d4b997bfb9397306c0ec1f (diff) | |
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/apprt/surface.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/apprt/surface.zig | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/apprt/surface.zig b/vendor/libghostty-vt/src/apprt/surface.zig new file mode 100644 index 00000000..3cb0016f --- /dev/null +++ b/vendor/libghostty-vt/src/apprt/surface.zig @@ -0,0 +1,197 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const apprt = @import("../apprt.zig"); +const build_config = @import("../build_config.zig"); +const App = @import("../App.zig"); +const Surface = @import("../Surface.zig"); +const renderer = @import("../renderer.zig"); +const terminal = @import("../terminal/main.zig"); +const Config = @import("../config.zig").Config; +const MessageData = @import("../datastruct/main.zig").MessageData; + +/// The message types that can be sent to a single surface. +pub const Message = union(enum) { + /// Represents a write request. Magic number comes from the max size + /// we want this union to be. + pub const WriteReq = MessageData(u8, 255); + + /// Set the title of the surface. + /// TODO: we should change this to a "WriteReq" style structure in + /// the termio message so that we can more efficiently send strings + /// of any length + set_title: [256]u8, + + /// Report the window title back to the terminal + report_title: ReportTitleStyle, + + /// Set the mouse shape. + set_mouse_shape: terminal.MouseShape, + + /// Read the clipboard and write to the pty. + clipboard_read: apprt.Clipboard, + + /// Write the clipboard contents. + clipboard_write: struct { + clipboard_type: apprt.Clipboard, + req: WriteReq, + }, + + /// Change the configuration to the given configuration. The pointer is + /// not valid after receiving this message so any config must be used + /// and derived immediately. + change_config: *const Config, + + /// Close the surface. This will only close the current surface that + /// receives this, not the full application. + close: void, + + /// The child process running in the surface has exited. This may trigger + /// a surface close, it may not. Additional details about the child + /// command are given in the `ChildExited` struct. + child_exited: ChildExited, + + /// Show a desktop notification. + desktop_notification: struct { + /// Desktop notification title. + title: [63:0]u8, + + /// Desktop notification body. + body: [255:0]u8, + }, + + /// Health status change for the renderer. + renderer_health: renderer.Health, + + /// Tell the surface to present itself to the user. This may require raising + /// a window and switching tabs. + present_surface: void, + + /// Notifies the surface that password input has started within + /// the terminal. This should always be followed by a false value + /// unless the surface exits. + password_input: bool, + + /// A terminal color was changed using OSC sequences. + color_change: terminal.osc.color.ColoredTarget, + + /// Notifies the surface that a tick of the timer that is timing + /// out selection scrolling has occurred. "selection scrolling" + /// is when the user has clicked and dragged the mouse outside + /// the viewport of the terminal and the terminal is scrolling + /// the viewport to follow the mouse cursor. + selection_scroll_tick: bool, + + /// The terminal has reported a change in the working directory. + pwd_change: WriteReq, + + /// The terminal encountered a bell character. + ring_bell, + + /// Report the progress of an action using a GUI element + progress_report: terminal.osc.Command.ProgressReport, + + /// A command has started in the shell, start a timer. + start_command, + + /// A command has finished in the shell, stop the timer and send out + /// notifications as appropriate. The optional u8 is the exit code + /// of the command. + stop_command: ?u8, + + /// The scrollbar state changed for the surface. + scrollbar: terminal.Scrollbar, + + /// Search progress update + search_total: ?usize, + + /// Selected search index change + search_selected: ?usize, + + pub const ReportTitleStyle = enum { + csi_21_t, + + // This enum is a placeholder for future title styles. + }; + + pub const ChildExited = extern struct { + exit_code: u32, + runtime_ms: u64, + + /// Make this a valid gobject if we're in a GTK environment. + pub const getGObjectType = switch (build_config.app_runtime) { + .gtk, + => @import("gobject").ext.defineBoxed( + ChildExited, + .{ .name = "GhosttyApprtChildExited" }, + ), + + .none => void, + }; + }; +}; + +/// A surface mailbox. +pub const Mailbox = struct { + surface: *Surface, + app: App.Mailbox, + + /// Send a message to the surface. + pub fn push( + self: Mailbox, + msg: Message, + timeout: App.Mailbox.Queue.Timeout, + ) App.Mailbox.Queue.Size { + // Surface message sending is actually implemented on the app + // thread, so we have to rewrap the message with our surface + // pointer and send it to the app thread. + return self.app.push(.{ + .surface_message = .{ + .surface = self.surface, + .message = msg, + }, + }, timeout); + } +}; + +/// Context for new surface creation to determine inheritance behavior +pub const NewSurfaceContext = enum(c_int) { + window = 0, + tab = 1, + split = 2, +}; + +pub fn shouldInheritWorkingDirectory(context: NewSurfaceContext, config: *const Config) bool { + return switch (context) { + .window => config.@"window-inherit-working-directory", + .tab => config.@"tab-inherit-working-directory", + .split => config.@"split-inherit-working-directory", + }; +} + +/// Returns a new config for a surface for the given app that should be +/// used for any new surfaces. The resulting config should be deinitialized +/// after the surface is initialized. +pub fn newConfig( + app: *const App, + config: *const Config, + context: NewSurfaceContext, +) Allocator.Error!Config { + // Create a shallow clone + var copy = config.shallowClone(app.alloc); + + // Our allocator is our config's arena + const alloc = copy._arena.?.allocator(); + + // Get our previously focused surface for some inherited values. + const prev = app.focusedSurface(); + if (prev) |p| { + if (shouldInheritWorkingDirectory(context, config)) { + if (try p.pwd(alloc)) |pwd| { + copy.@"working-directory" = .{ .path = pwd }; + } + } + } + + return copy; +} |
