aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/apprt/embedded.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/apprt/embedded.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (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.zig475
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: {