diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/apprt/embedded.zig | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff) | |
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/apprt/embedded.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/apprt/embedded.zig | 475 |
1 files changed, 365 insertions, 110 deletions
diff --git a/vendor/libghostty-vt/src/apprt/embedded.zig b/vendor/libghostty-vt/src/apprt/embedded.zig index 7310159c..525bc856 100644 --- a/vendor/libghostty-vt/src/apprt/embedded.zig +++ b/vendor/libghostty-vt/src/apprt/embedded.zig @@ -11,6 +11,7 @@ const Allocator = std.mem.Allocator; const objc = @import("objc"); const apprt = @import("../apprt.zig"); const font = @import("../font/main.zig"); +const global = @import("../global.zig"); const input = @import("../input.zig"); const internal_os = @import("../os/main.zig"); const renderer = @import("../renderer.zig"); @@ -51,18 +52,36 @@ pub const App = struct { /// Callback called to handle an action. action: *const fn (*App, apprt.Target.C, apprt.Action.C) callconv(.c) bool, - /// Read the clipboard value. Returns true if the clipboard request - /// was started and complete_clipboard_request may be called with the - /// given state pointer. Returns false if the clipboard request couldn't - /// be started (such as when no text is available for a paste request). - read_clipboard: *const fn (SurfaceUD, c_int, *apprt.ClipboardRequest) callconv(.c) bool, + /// Read the clipboard value. The result only reports facts about + /// the clipboard: whether the request was started (in which case + /// complete_clipboard_request or deny_clipboard_request must + /// eventually be called with the given state pointer), whether the + /// clipboard has no servable contents, or whether the clipboard + /// can't be read at all. How each non-started state is answered is + /// up to the core. + /// + /// The MIME types are exactly the representations the caller wants + /// served; the embedder should read only those. Text-like types + /// are always requested as the canonical "text/plain". The final + /// bool asks for the listing of all MIME types available on the + /// clipboard to be delivered with the completion. + read_clipboard: *const fn ( + SurfaceUD, + c_int, + *apprt.ClipboardRequest, + [*]const [*:0]const u8, + usize, + bool, + ) callconv(.c) apprt.ClipboardReadResult, /// This may be called after a read clipboard call to request - /// confirmation that the clipboard value is safe to read. The embedder - /// must call complete_clipboard_request with the given request. + /// confirmation that the clipboard value is safe to read. The + /// embedder must call complete_clipboard_request (usually with + /// the confirmation's contents, which are only borrowed for + /// this call) or deny_clipboard_request with the given request. confirm_read_clipboard: *const fn ( SurfaceUD, - [*:0]const u8, + *const CAPI.ClipboardConfirm, *apprt.ClipboardRequest, apprt.ClipboardRequestType, ) callconv(.c) void, @@ -119,7 +138,13 @@ pub const App = struct { core_app: *CoreApp, opts: Options, - keymap: input.Keymap, + + /// The keyboard layout keymap. This is lazily initialized on first + /// use because creating it requires talking to the text input + /// system (TIS on macOS), and the first such call in a process is + /// slow (multiple milliseconds). It is only needed once keyboard + /// events start flowing, at which point the system is warm. + keymap: ?input.Keymap, /// The configuration for the app. This is owned by this structure. config: Config, @@ -135,19 +160,16 @@ pub const App = struct { var config_clone = try config.clone(alloc); errdefer config_clone.deinit(); - var keymap = try input.Keymap.init(); - errdefer keymap.deinit(); - self.* = .{ .core_app = core_app, .config = config_clone, .opts = opts, - .keymap = keymap, + .keymap = null, }; } pub fn terminate(self: *App) void { - self.keymap.deinit(); + if (self.keymap) |*v| v.deinit(); self.config.deinit(); } @@ -207,8 +229,10 @@ pub const App = struct { /// This should be called whenever the keyboard layout was changed. pub fn reloadKeymap(self: *App) !void { - // Reload the keymap - try self.keymap.reload(); + // Reload the keymap. If it was never initialized we don't need + // to do anything since lazy initialization will pick up the + // current layout. + if (self.keymap) |*v| try v.reload(); } /// Loads the keyboard layout. @@ -216,13 +240,25 @@ pub const App = struct { /// Kind of expensive so this should be avoided if possible. When I say /// "kind of expensive" I mean that its not something you probably want /// to run on every keypress. - pub fn keyboardLayout(self: *const App) input.KeyboardLayout { + pub fn keyboardLayout(self: *App) input.KeyboardLayout { // We only support keyboard layout detection on macOS. if (comptime builtin.os.tag != .macos) return .unknown; + // Lazily initialize the keymap. + const keymap: *input.Keymap = keymap: { + if (self.keymap == null) { + self.keymap = input.Keymap.init() catch |err| { + log.warn("error initializing keymap err={}", .{err}); + return .unknown; + }; + } + + break :keymap &self.keymap.?; + }; + // Any layout larger than this is not something we can handle. var buf: [256]u8 = undefined; - const id = self.keymap.sourceId(&buf) catch |err| { + const id = keymap.sourceId(&buf) catch |err| { comptime assert(@TypeOf(err) == error{OutOfMemory}); return .unknown; }; @@ -333,9 +369,10 @@ pub const App = struct { _: apprt.ipc.Target, comptime action: apprt.ipc.Action.Key, _: apprt.ipc.Action.Value(action), - ) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!bool { + ) (Allocator.Error || apprt.ipc.Errors)!bool { switch (action) { .new_window => return false, + .new_tab => return false, .toggle_quick_terminal => return false, } } @@ -372,7 +409,7 @@ pub const Platform = union(PlatformTag) { /// Initialize a Platform a tag and configuration from the C ABI. pub fn init(tag_int: c_int, c_platform: C) !Platform { - const tag = try std.meta.intToEnum(PlatformTag, tag_int); + const tag = std.enums.fromInt(PlatformTag, tag_int) orelse return error.InvalidEnumTag; return switch (tag) { .macos => if (MacOS != void) macos: { const config = c_platform.macos; @@ -490,16 +527,16 @@ pub const Surface = struct { if (opts.working_directory) |c_wd| { const wd = std.mem.sliceTo(c_wd, 0); if (wd.len > 0) wd: { - var dir = std.fs.openDirAbsolute(wd, .{}) catch |err| { + var dir = std.Io.Dir.openDirAbsolute(global.io(), wd, .{}) catch |err| { log.warn( "error opening requested working directory dir={s} err={}", .{ wd, err }, ); break :wd; }; - defer dir.close(); + defer dir.close(global.io()); - const stat = dir.stat() catch |err| { + const stat = dir.stat(global.io()) catch |err| { log.warn( "failed to stat requested working directory dir={s} err={}", .{ wd, err }, @@ -665,7 +702,13 @@ pub const Surface = struct { ) bool { return switch (clipboard_type) { .standard => true, - .selection, .primary => self.app.opts.supports_selection_clipboard, + .selection => self.app.opts.supports_selection_clipboard, + + // No embedder can serve the primary selection: the embedding + // API has no option to declare support for it, and macOS (the + // only GUI embedder) has no primary selection. The selection + // flag above covers a distinct custom pasteboard, not this. + .primary => false, }; } @@ -673,7 +716,67 @@ pub const Surface = struct { self: *Surface, clipboard_type: apprt.Clipboard, state: apprt.ClipboardRequest, - ) !bool { + ) !apprt.ClipboardReadResult { + // Kitty clipboard writes carry their own contents and read + // nothing from the clipboard, so they skip the read callback + // entirely: the request is completed immediately, and a + // completion that requires confirmation diverts into the + // apprt confirmation flow just like a read. + if (state == .kitty_write) { + // A write to a location the embedder can't serve is + // rejected before the transaction completes so the caller + // answers ENOSYS without a permission prompt, matching + // reads, where the embedder reports an unsupported + // location from the read callback. The write callback only + // fires after any prompt, so it is too late to reject. + if (!self.supportsClipboard(clipboard_type)) return .unsupported; + + const alloc = self.app.core_app.alloc; + const state_ptr = try alloc.create(apprt.ClipboardRequest); + errdefer alloc.destroy(state_ptr); + state_ptr.* = state; + self.completeKittyClipboardWrite(state_ptr); + return .started; + } + + // The representations the read wants served. Text-only + // requesters ask for the canonical text type; Kitty clipboard + // reads ask for exactly what the program requested, with + // text-like aliases normalized so the embedder never has to + // know about them. + var mimes_buf: [terminal.kitty.clipboard.max_read_mimes][*:0]const u8 = undefined; + const mimes: []const [*:0]const u8 = switch (state) { + .paste, .osc_52_read => &.{"text/plain"}, + + // A mode 5522 paste event only lists types and must not read + // any clipboard data. + .list => &.{}, + + .kitty_read => |kitty| mimes: { + assert(kitty.mimes.len <= mimes_buf.len); + for (kitty.mimes, mimes_buf[0..kitty.mimes.len]) |mime, *dst| { + dst.* = if (terminal.clipboard.isTextMime(mime)) + "text/plain" + else + mime.ptr; + } + break :mimes mimes_buf[0..kitty.mimes.len]; + }, + + // Handled above. + .kitty_write => unreachable, + + // No clipboard write code paths travel through this function + .osc_52_write => unreachable, + }; + const list = switch (state) { + // Paste events need the full MIME listing without reading any + // representation. Kitty reads only ask for it when requested. + .list => true, + .kitty_read => |kitty| kitty.list, + else => false, + }; + // We need to allocate to get a pointer to store our clipboard request // so that it is stable until the read_clipboard callback and call // complete_clipboard_request. This sucks but clipboard requests aren't @@ -683,40 +786,156 @@ pub const Surface = struct { errdefer alloc.destroy(state_ptr); state_ptr.* = state; - const started = self.app.opts.read_clipboard( + const result = self.app.opts.read_clipboard( self.userdata, @intCast(@intFromEnum(clipboard_type)), state_ptr, + mimes.ptr, + mimes.len, + list, ); - if (!started) { - alloc.destroy(state_ptr); - return false; - } - return true; + // Only a started request completes later and keeps the state. + if (result != .started) alloc.destroy(state_ptr); + return result; } - fn completeClipboardRequest( + /// Complete a Kitty clipboard protocol write request. The apprt + /// contributes nothing to the completion (the authoritative + /// contents live in the request itself), but a completion that + /// requires confirmation diverts into the confirm callback, which + /// keeps the state alive for the asynchronous prompt. + fn completeKittyClipboardWrite( self: *Surface, - str: [:0]const u8, state: *apprt.ClipboardRequest, - confirmed: bool, ) void { const alloc = self.app.core_app.alloc; + const kitty = state.kitty_write; - // Attempt to complete the request, but we may request - // confirmation. self.core_surface.completeClipboardRequest( state.*, - str, - confirmed, + .{}, ) catch |err| switch (err) { + error.UnauthorizedPaste => { + // Convert the would-be contents so the permission + // prompt can display exactly what would be written. + var stack = std.heap.stackFallback(1024, alloc); + const conv_alloc = stack.get(); + const contents = conv_alloc.alloc( + CAPI.ClipboardContent, + kitty.contents.len, + ) catch |alloc_err| { + log.err("error confirming clipboard request err={}", .{alloc_err}); + self.core_surface.denyClipboardRequest(state.*); + alloc.destroy(state); + return; + }; + defer conv_alloc.free(contents); + for (kitty.contents, contents) |src, *dst| dst.* = .{ + .mime = src.mime, + .data = src.data.ptr, + .len = src.data.len, + }; + + self.app.opts.confirm_read_clipboard( + self.userdata, + &.{ + .contents = contents.ptr, + .contents_len = contents.len, + .available = null, + .available_len = 0, + .name = if (kitty.name.len > 0) kitty.name.ptr else null, + .can_remember = kitty.pw.len > 0, + }, + state, + state.*, + ); + + return; + }, + + else => log.err("error completing clipboard request err={}", .{err}), + }; + + // We don't defer this because the clipboard confirmation route + // preserves the clipboard request. + alloc.destroy(state); + } + + fn completeClipboardRequest( + self: *Surface, + complete: *const CAPI.ClipboardComplete, + state: *apprt.ClipboardRequest, + ) void { + const alloc = self.app.core_app.alloc; + + // Convert the C representations to the core types. Everything + // remains borrowed from the caller for the duration of the call. + var stack = std.heap.stackFallback(1024, alloc); + const conv_alloc = stack.get(); + + const raw_contents: []const CAPI.ClipboardContent = + if (complete.contents) |v| v[0..complete.contents_len] else &.{}; + const contents = conv_alloc.alloc( + terminal.clipboard.Content, + raw_contents.len, + ) catch |err| { + log.err("error completing clipboard request err={}", .{err}); + alloc.destroy(state); + return; + }; + defer conv_alloc.free(contents); + for (raw_contents, contents) |raw, *content| content.* = .{ + .mime = std.mem.sliceTo(raw.mime, 0), + .data = raw.data[0..raw.len], + }; + + const raw_available: []const [*:0]const u8 = + if (complete.available) |v| v[0..complete.available_len] else &.{}; + const available = conv_alloc.alloc( + []const u8, + raw_available.len, + ) catch |err| { + log.err("error completing clipboard request err={}", .{err}); + alloc.destroy(state); + return; + }; + defer conv_alloc.free(available); + for (raw_available, available) |raw, *mime| { + mime.* = std.mem.sliceTo(raw, 0); + } + + // Attempt to complete the request, but we may request + // confirmation. + self.core_surface.completeClipboardRequest(state.*, .{ + .contents = contents, + .available = available, + .confirmed = complete.confirmed, + .remember = complete.remember, + }) catch |err| switch (err) { error.UnsafePaste, error.UnauthorizedPaste, => { + // Session grant information for the permission prompt, + // carried only by Kitty clipboard protocol requests. + const name: ?[*:0]const u8, const can_remember: bool = switch (state.*) { + inline .kitty_read, .kitty_write => |kitty| .{ + if (kitty.name.len > 0) kitty.name.ptr else null, + kitty.pw.len > 0, + }, + else => .{ null, false }, + }; + self.app.opts.confirm_read_clipboard( self.userdata, - str.ptr, + &.{ + .contents = complete.contents, + .contents_len = complete.contents_len, + .available = complete.available, + .available_len = complete.available_len, + .name = name, + .can_remember = can_remember, + }, state, state.*, ); @@ -732,6 +951,14 @@ pub const Surface = struct { alloc.destroy(state); } + fn denyClipboardRequest( + self: *Surface, + state: *apprt.ClipboardRequest, + ) void { + self.core_surface.denyClipboardRequest(state.*); + self.app.core_app.alloc.destroy(state); + } + pub fn setClipboard( self: *const Surface, clipboard_type: apprt.Clipboard, @@ -744,7 +971,8 @@ pub const Surface = struct { for (contents, 0..) |content, i| { array[i] = .{ .mime = content.mime, - .data = content.data, + .data = content.data.ptr, + .len = content.data.len, }; } @@ -950,32 +1178,32 @@ pub const Surface = struct { }; } - pub fn defaultTermioEnv(self: *const Surface) !std.process.EnvMap { - const alloc = self.app.core_app.alloc; - var env = try internal_os.getEnvMap(alloc); + pub fn defaultTermioEnv(self: *const Surface) !std.process.Environ.Map { + _ = self; + var env = try global.environMap(); errdefer env.deinit(); if (comptime builtin.target.os.tag.isDarwin()) { if (env.get("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != null) { - env.remove("__XCODE_BUILT_PRODUCTS_DIR_PATHS"); - env.remove("__XPC_DYLD_LIBRARY_PATH"); - env.remove("DYLD_FRAMEWORK_PATH"); - env.remove("DYLD_INSERT_LIBRARIES"); - env.remove("DYLD_LIBRARY_PATH"); - env.remove("LD_LIBRARY_PATH"); - env.remove("SECURITYSESSIONID"); - env.remove("XPC_SERVICE_NAME"); + _ = env.orderedRemove("__XCODE_BUILT_PRODUCTS_DIR_PATHS"); + _ = env.orderedRemove("__XPC_DYLD_LIBRARY_PATH"); + _ = env.orderedRemove("DYLD_FRAMEWORK_PATH"); + _ = env.orderedRemove("DYLD_INSERT_LIBRARIES"); + _ = env.orderedRemove("DYLD_LIBRARY_PATH"); + _ = env.orderedRemove("LD_LIBRARY_PATH"); + _ = env.orderedRemove("SECURITYSESSIONID"); + _ = env.orderedRemove("XPC_SERVICE_NAME"); } // Remove this so that running `ghostty` within Ghostty works. - env.remove("GHOSTTY_MAC_LAUNCH_SOURCE"); + _ = env.orderedRemove("GHOSTTY_MAC_LAUNCH_SOURCE"); // If we were launched from the desktop then we want to // remove the LANGUAGE env var so that we don't inherit // our translation settings for Ghostty. If we aren't from // the desktop then we didn't set our LANGUAGE var so we // don't need to remove it. - if (internal_os.launchedFromDesktop()) env.remove("LANGUAGE"); + if (internal_os.launchedFromDesktop()) _ = env.orderedRemove("LANGUAGE"); } return env; @@ -1000,7 +1228,7 @@ pub const Inspector = struct { content_scale: f64 = 1, /// Our previous instant used to calculate delta time for animations. - instant: ?std.time.Instant = null, + instant: ?std.Io.Timestamp = null, const Backend = enum { metal, @@ -1229,9 +1457,9 @@ pub const Inspector = struct { const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO(); // Determine our delta time - const now = try std.time.Instant.now(); + const now: std.Io.Timestamp = .now(global.io(), .awake); io.DeltaTime = if (self.instant) |prev| delta: { - const since_ns: f64 = @floatFromInt(now.since(prev)); + const since_ns: f64 = @floatFromInt(prev.durationTo(now).toNanoseconds()); const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s); const since_s: f32 = @floatCast(since_ns / ns_per_s); break :delta @max(0.00001, since_s); @@ -1242,8 +1470,6 @@ pub const Inspector = struct { // C API pub const CAPI = struct { - const global = &@import("../global.zig").state; - /// This is the same as Surface.KeyEvent but this is the raw C API version. const KeyEvent = extern struct { action: input.Action, @@ -1284,9 +1510,48 @@ pub const CAPI = struct { }; // ghostty_clipboard_content_s + // + // One representation of clipboard contents. The data is binary-safe + // and its length is explicit; it is not sentinel-terminated. const ClipboardContent = extern struct { mime: [*:0]const u8, - data: [*:0]const u8, + data: [*]const u8, + len: usize, + }; + + // ghostty_clipboard_complete_s + // + // The payload for completing a clipboard read request. See + // Surface.CompleteClipboard for the field documentation. + const ClipboardComplete = extern struct { + contents: ?[*]const ClipboardContent, + contents_len: usize, + available: ?[*]const [*:0]const u8, + available_len: usize, + confirmed: bool, + remember: bool, + }; + + // ghostty_clipboard_confirm_s + // + // The payload of a clipboard read confirmation request: the + // would-be completion contents plus the information shown in the + // permission prompt. All memory is borrowed for the duration of + // the confirm_read_clipboard callback. + const ClipboardConfirm = extern struct { + contents: ?[*]const ClipboardContent, + contents_len: usize, + available: ?[*]const [*:0]const u8, + available_len: usize, + + /// The human friendly name of the requesting program for the + /// prompt, null when the protocol doesn't carry one. + name: ?[*:0]const u8, + + /// True when the user's decision may be remembered as a + /// session grant, reported back through the completion's + /// remember field. + can_remember: bool, }; // ghostty_text_s @@ -1300,7 +1565,7 @@ pub const CAPI = struct { pub fn deinit(self: *Text) void { if (self.text) |ptr| { - global.alloc.free(ptr[0..self.text_len :0]); + global.alloc().free(ptr[0..self.text_len :0]); } } }; @@ -1410,12 +1675,12 @@ pub const CAPI = struct { opts: *const apprt.runtime.App.Options, config: *const Config, ) !*App { - const core_app = try CoreApp.create(global.alloc); + const core_app = try CoreApp.create(global.alloc()); errdefer core_app.destroy(); // Create our runtime app - var app = try global.alloc.create(App); - errdefer global.alloc.destroy(app); + var app = try global.alloc().create(App); + errdefer global.alloc().destroy(app); try app.init(core_app, config, opts.*); errdefer app.terminate(); @@ -1438,7 +1703,7 @@ pub const CAPI = struct { export fn ghostty_app_free(v: *App) void { const core_app = v.core_app; v.terminate(); - global.alloc.destroy(v); + global.alloc().destroy(v); core_app.destroy(); } @@ -1490,7 +1755,7 @@ pub const CAPI = struct { /// Open the configuration. export fn ghostty_app_open_config(v: *App) void { - _ = v.performAction(.app, .open_config, {}) catch |err| { + _ = v.performAction(.app, .open_config, .new_window) catch |err| { log.err("error reloading config err={}", .{err}); return; }; @@ -1520,13 +1785,7 @@ pub const CAPI = struct { /// Update the color scheme of the app. export fn ghostty_app_set_color_scheme(v: *App, scheme_raw: c_int) void { - const scheme = std.meta.intToEnum(apprt.ColorScheme, scheme_raw) catch { - log.warn( - "invalid color scheme to ghostty_surface_set_color_scheme value={}", - .{scheme_raw}, - ); - return; - }; + const scheme = std.enums.fromInt(apprt.ColorScheme, scheme_raw) orelse return; v.core_app.colorSchemeEvent(v, scheme) catch |err| { log.err("error setting color scheme err={}", .{err}); @@ -1612,8 +1871,8 @@ pub const CAPI = struct { result: *Text, ) bool { const core_surface = &surface.core_surface; - core_surface.renderer_state.mutex.lock(); - defer core_surface.renderer_state.mutex.unlock(); + core_surface.renderer_state.mutex.lockUncancelable(global.io()); + defer core_surface.renderer_state.mutex.unlock(global.io()); // If we don't have a selection, do nothing. const core_sel = core_surface.io.terminal.screens.active.selection orelse return false; @@ -1632,8 +1891,8 @@ pub const CAPI = struct { sel: Selection, result: *Text, ) bool { - surface.core_surface.renderer_state.mutex.lock(); - defer surface.core_surface.renderer_state.mutex.unlock(); + surface.core_surface.renderer_state.mutex.lockUncancelable(global.io()); + defer surface.core_surface.renderer_state.mutex.unlock(global.io()); const core_sel = sel.core( surface.core_surface.renderer_state.terminal.screens.active, @@ -1651,7 +1910,7 @@ pub const CAPI = struct { // Get our text directly from the core surface. const text = core_surface.dumpTextLocked( - global.alloc, + global.alloc(), core_sel, ) catch |err| { log.warn("error reading text err={}", .{err}); @@ -1730,14 +1989,7 @@ pub const CAPI = struct { /// Update the color scheme of the surface. export fn ghostty_surface_set_color_scheme(surface: *Surface, scheme_raw: c_int) void { - const scheme = std.meta.intToEnum(apprt.ColorScheme, scheme_raw) catch { - log.warn( - "invalid color scheme to ghostty_surface_set_color_scheme value={}", - .{scheme_raw}, - ); - return; - }; - + const scheme = std.enums.fromInt(apprt.ColorScheme, scheme_raw) orelse return; surface.colorSchemeCallback(scheme); } @@ -1891,17 +2143,7 @@ pub const CAPI = struct { stage_raw: u32, pressure: f64, ) void { - const stage = std.meta.intToEnum( - input.MousePressureStage, - stage_raw, - ) catch { - log.warn( - "invalid mouse pressure stage value={}", - .{stage_raw}, - ); - return; - }; - + const stage = std.enums.fromInt(input.MousePressureStage, stage_raw) orelse return; surface.mousePressureCallback(stage, pressure); } @@ -2001,20 +2243,32 @@ pub const CAPI = struct { }; } - /// Complete a clipboard read request started via the read callback. - /// This can only be called once for a given request. Once it is called - /// with a request the request pointer will be invalidated. + /// Complete a clipboard read request started via the read callback + /// with the representations that could be served and, if requested, + /// the listing of available MIME types. All memory is borrowed for + /// the duration of the call. This can only be called once for a given + /// request. Once it is called with a request the request pointer will + /// be invalidated. + /// + /// To deny a request use ghostty_surface_deny_clipboard_request + /// instead. export fn ghostty_surface_complete_clipboard_request( ptr: *Surface, - str: [*:0]const u8, + complete: *const ClipboardComplete, state: *apprt.ClipboardRequest, - confirmed: bool, ) void { - ptr.completeClipboardRequest( - std.mem.sliceTo(str, 0), - state, - confirmed, - ); + ptr.completeClipboardRequest(complete, state); + } + + /// Deny a clipboard read request started via the read callback, + /// e.g. because the user rejected a confirmation prompt. Request + /// types whose protocol expects an answer have their denial reply + /// written to the pty. The request pointer is invalidated. + export fn ghostty_surface_deny_clipboard_request( + ptr: *Surface, + state: *apprt.ClipboardRequest, + ) void { + ptr.denyClipboardRequest(state); } export fn ghostty_surface_inspector(ptr: *Surface) ?*Inspector { @@ -2134,6 +2388,7 @@ pub const CAPI = struct { export fn ghostty_surface_set_display_id(ptr: *Surface, display_id: u32) void { const surface = &ptr.core_surface; _ = surface.renderer_thread.mailbox.push( + global.io(), .{ .macos_display_id = display_id }, .{ .forever = {} }, ); @@ -2157,8 +2412,8 @@ pub const CAPI = struct { // read the font face. It should not be deferred since // we're loading the primary face. const grid = ptr.core_surface.renderer.font_grid; - grid.lock.lockShared(); - defer grid.lock.unlockShared(); + grid.lock.lockSharedUncancelable(global.io()); + defer grid.lock.unlockShared(global.io()); const collection = &grid.resolver.collection; const face = collection.getFace(.{}) catch return null; @@ -2195,8 +2450,8 @@ pub const CAPI = struct { result: *Text, ) bool { const surface = &ptr.core_surface; - surface.renderer_state.mutex.lock(); - defer surface.renderer_state.mutex.unlock(); + surface.renderer_state.mutex.lockUncancelable(global.io()); + defer surface.renderer_state.mutex.unlock(global.io()); // Get our word selection const sel = sel: { |
