mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: async style clipboard reading
This commit is contained in:
@ -316,7 +316,7 @@ typedef const ghostty_config_t (*ghostty_runtime_reload_config_cb)(void *);
|
|||||||
typedef void (*ghostty_runtime_set_title_cb)(void *, const char *);
|
typedef void (*ghostty_runtime_set_title_cb)(void *, const char *);
|
||||||
typedef void (*ghostty_runtime_set_mouse_shape_cb)(void *, ghostty_mouse_shape_e);
|
typedef void (*ghostty_runtime_set_mouse_shape_cb)(void *, ghostty_mouse_shape_e);
|
||||||
typedef void (*ghostty_runtime_set_mouse_visibility_cb)(void *, bool);
|
typedef void (*ghostty_runtime_set_mouse_visibility_cb)(void *, bool);
|
||||||
typedef const char* (*ghostty_runtime_read_clipboard_cb)(void *, ghostty_clipboard_e);
|
typedef void (*ghostty_runtime_read_clipboard_cb)(void *, ghostty_clipboard_e, void *);
|
||||||
typedef void (*ghostty_runtime_write_clipboard_cb)(void *, const char *, ghostty_clipboard_e);
|
typedef void (*ghostty_runtime_write_clipboard_cb)(void *, const char *, ghostty_clipboard_e);
|
||||||
typedef void (*ghostty_runtime_new_split_cb)(void *, ghostty_split_direction_e, ghostty_surface_config_s);
|
typedef void (*ghostty_runtime_new_split_cb)(void *, ghostty_split_direction_e, ghostty_surface_config_s);
|
||||||
typedef void (*ghostty_runtime_new_tab_cb)(void *, ghostty_surface_config_s);
|
typedef void (*ghostty_runtime_new_tab_cb)(void *, ghostty_surface_config_s);
|
||||||
@ -393,6 +393,7 @@ void ghostty_surface_request_close(ghostty_surface_t);
|
|||||||
void ghostty_surface_split(ghostty_surface_t, ghostty_split_direction_e);
|
void ghostty_surface_split(ghostty_surface_t, ghostty_split_direction_e);
|
||||||
void ghostty_surface_split_focus(ghostty_surface_t, ghostty_split_focus_direction_e);
|
void ghostty_surface_split_focus(ghostty_surface_t, ghostty_split_focus_direction_e);
|
||||||
bool ghostty_surface_binding_action(ghostty_surface_t, const char *, uintptr_t);
|
bool ghostty_surface_binding_action(ghostty_surface_t, const char *, uintptr_t);
|
||||||
|
void ghostty_surface_complete_clipboard_request(ghostty_surface_t, const char *, uintptr_t, void *);
|
||||||
|
|
||||||
// APIs I'd like to get rid of eventually but are still needed for now.
|
// APIs I'd like to get rid of eventually but are still needed for now.
|
||||||
// Don't use these unless you know what you're doing.
|
// Don't use these unless you know what you're doing.
|
||||||
|
@ -72,9 +72,6 @@ extension Ghostty {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cached clipboard string for `read_clipboard` callback.
|
|
||||||
private var cached_clipboard_string: String? = nil
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Initialize ghostty global state. This happens once per process.
|
// Initialize ghostty global state. This happens once per process.
|
||||||
guard ghostty_init() == GHOSTTY_SUCCESS else {
|
guard ghostty_init() == GHOSTTY_SUCCESS else {
|
||||||
@ -100,7 +97,7 @@ extension Ghostty {
|
|||||||
set_title_cb: { userdata, title in AppState.setTitle(userdata, title: title) },
|
set_title_cb: { userdata, title in AppState.setTitle(userdata, title: title) },
|
||||||
set_mouse_shape_cb: { userdata, shape in AppState.setMouseShape(userdata, shape: shape) },
|
set_mouse_shape_cb: { userdata, shape in AppState.setMouseShape(userdata, shape: shape) },
|
||||||
set_mouse_visibility_cb: { userdata, visible in AppState.setMouseVisibility(userdata, visible: visible) },
|
set_mouse_visibility_cb: { userdata, visible in AppState.setMouseVisibility(userdata, visible: visible) },
|
||||||
read_clipboard_cb: { userdata, loc in AppState.readClipboard(userdata, location: loc) },
|
read_clipboard_cb: { userdata, loc, state in AppState.readClipboard(userdata, location: loc, state: state) },
|
||||||
write_clipboard_cb: { userdata, str, loc in AppState.writeClipboard(userdata, string: str, location: loc) },
|
write_clipboard_cb: { userdata, str, loc in AppState.writeClipboard(userdata, string: str, location: loc) },
|
||||||
new_split_cb: { userdata, direction, surfaceConfig in AppState.newSplit(userdata, direction: direction, config: surfaceConfig) },
|
new_split_cb: { userdata, direction, surfaceConfig in AppState.newSplit(userdata, direction: direction, config: surfaceConfig) },
|
||||||
new_tab_cb: { userdata, surfaceConfig in AppState.newTab(userdata, config: surfaceConfig) },
|
new_tab_cb: { userdata, surfaceConfig in AppState.newTab(userdata, config: surfaceConfig) },
|
||||||
@ -301,18 +298,24 @@ extension Ghostty {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e) -> UnsafePointer<CChar>? {
|
static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e, state: UnsafeMutableRawPointer?) {
|
||||||
|
// If we don't even have a surface, something went terrible wrong so we have
|
||||||
|
// to leak "state".
|
||||||
|
guard let surfaceView = self.surfaceUserdata(from: userdata) else { return }
|
||||||
|
guard let surface = surfaceView.surface else { return }
|
||||||
|
|
||||||
// We only support the standard clipboard
|
// We only support the standard clipboard
|
||||||
if (location != GHOSTTY_CLIPBOARD_STANDARD) { return nil }
|
if (location != GHOSTTY_CLIPBOARD_STANDARD) {
|
||||||
|
return completeClipboardRequest(surface, data: "", state: state)
|
||||||
|
}
|
||||||
|
|
||||||
guard let surface = self.surfaceUserdata(from: userdata) else { return nil }
|
// Get our string
|
||||||
guard let appState = self.appState(fromView: surface) else { return nil }
|
let str = NSPasteboard.general.string(forType: .string) ?? ""
|
||||||
guard let str = NSPasteboard.general.string(forType: .string) else { return nil }
|
completeClipboardRequest(surface, data: str, state: state)
|
||||||
|
}
|
||||||
|
|
||||||
// Ghostty requires we cache the string because the pointer we return has to remain
|
static private func completeClipboardRequest(_ surface: ghostty_surface_t, data: String, state: UnsafeMutableRawPointer?) {
|
||||||
// stable until the next call to readClipboard.
|
ghostty_surface_complete_clipboard_request(surface, data, UInt(data.count), state)
|
||||||
appState.cached_clipboard_string = str
|
|
||||||
return (str as NSString).utf8String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e) {
|
static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e) {
|
||||||
|
@ -58,7 +58,7 @@ pub const App = struct {
|
|||||||
/// Read the clipboard value. The return value must be preserved
|
/// Read the clipboard value. The return value must be preserved
|
||||||
/// by the host until the next call. If there is no valid clipboard
|
/// by the host until the next call. If there is no valid clipboard
|
||||||
/// value then this should return null.
|
/// value then this should return null.
|
||||||
read_clipboard: *const fn (SurfaceUD, c_int) callconv(.C) ?[*:0]const u8,
|
read_clipboard: *const fn (SurfaceUD, c_int, *apprt.ClipboardRequest) callconv(.C) void,
|
||||||
|
|
||||||
/// Write the clipboard value.
|
/// Write the clipboard value.
|
||||||
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int) callconv(.C) void,
|
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int) callconv(.C) void,
|
||||||
@ -342,15 +342,25 @@ pub const Surface = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getClipboardString(
|
pub fn clipboardRequest(
|
||||||
self: *const Surface,
|
self: *Surface,
|
||||||
clipboard_type: apprt.Clipboard,
|
clipboard_type: apprt.Clipboard,
|
||||||
) ![:0]const u8 {
|
state: apprt.ClipboardRequest,
|
||||||
const ptr = self.app.opts.read_clipboard(
|
) !void {
|
||||||
|
// 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
|
||||||
|
// high throughput so it's probably fine.
|
||||||
|
const alloc = self.app.core_app.alloc;
|
||||||
|
const state_ptr = try alloc.create(apprt.ClipboardRequest);
|
||||||
|
errdefer alloc.destroy(state_ptr);
|
||||||
|
state_ptr.* = state;
|
||||||
|
|
||||||
|
self.app.opts.read_clipboard(
|
||||||
self.opts.userdata,
|
self.opts.userdata,
|
||||||
@intCast(@intFromEnum(clipboard_type)),
|
@intCast(@intFromEnum(clipboard_type)),
|
||||||
) orelse return "";
|
state_ptr,
|
||||||
return std.mem.sliceTo(ptr, 0);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setClipboardString(
|
pub fn setClipboardString(
|
||||||
@ -982,6 +992,26 @@ pub const CAPI = struct {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Complete a clipboard read request startd 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.
|
||||||
|
export fn ghostty_surface_complete_clipboard_request(
|
||||||
|
ptr: *Surface,
|
||||||
|
str_ptr: [*]const u8,
|
||||||
|
str_len: usize,
|
||||||
|
state: *apprt.ClipboardRequest,
|
||||||
|
) void {
|
||||||
|
// The state is unusable after this
|
||||||
|
defer ptr.core_surface.app.alloc.destroy(state);
|
||||||
|
|
||||||
|
if (str_len == 0) return;
|
||||||
|
const str = str_ptr[0..str_len];
|
||||||
|
ptr.core_surface.completeClipboardRequest(state.*, str) catch |err| {
|
||||||
|
log.err("error completing clipboard request err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the window background blur on macOS to the desired value.
|
/// Sets the window background blur on macOS to the desired value.
|
||||||
/// I do this in Zig as an extern function because I don't know how to
|
/// I do this in Zig as an extern function because I don't know how to
|
||||||
/// call these functions in Swift.
|
/// call these functions in Swift.
|
||||||
|
Reference in New Issue
Block a user