mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-18 21:40:29 +00:00
macos: support setting multiple clipboard content types
This commit is contained in:
@@ -45,6 +45,11 @@ typedef enum {
|
||||
GHOSTTY_CLIPBOARD_SELECTION,
|
||||
} ghostty_clipboard_e;
|
||||
|
||||
typedef struct {
|
||||
const char *mime;
|
||||
const char *data;
|
||||
} ghostty_clipboard_content_s;
|
||||
|
||||
typedef enum {
|
||||
GHOSTTY_CLIPBOARD_REQUEST_PASTE,
|
||||
GHOSTTY_CLIPBOARD_REQUEST_OSC_52_READ,
|
||||
@@ -855,8 +860,9 @@ typedef void (*ghostty_runtime_confirm_read_clipboard_cb)(
|
||||
void*,
|
||||
ghostty_clipboard_request_e);
|
||||
typedef void (*ghostty_runtime_write_clipboard_cb)(void*,
|
||||
const char*,
|
||||
ghostty_clipboard_e,
|
||||
const ghostty_clipboard_content_s*,
|
||||
size_t,
|
||||
bool);
|
||||
typedef void (*ghostty_runtime_close_surface_cb)(void*, bool);
|
||||
typedef bool (*ghostty_runtime_action_cb)(ghostty_app_t,
|
||||
|
||||
@@ -61,7 +61,8 @@ extension Ghostty {
|
||||
action_cb: { app, target, action in App.action(app!, target: target, action: action) },
|
||||
read_clipboard_cb: { userdata, loc, state in App.readClipboard(userdata, location: loc, state: state) },
|
||||
confirm_read_clipboard_cb: { userdata, str, state, request in App.confirmReadClipboard(userdata, string: str, state: state, request: request ) },
|
||||
write_clipboard_cb: { userdata, str, loc, confirm in App.writeClipboard(userdata, string: str, location: loc, confirm: confirm) },
|
||||
write_clipboard_cb: { userdata, loc, content, len, confirm in
|
||||
App.writeClipboard(userdata, location: loc, content: content, len: len, confirm: confirm) },
|
||||
close_surface_cb: { userdata, processAlive in App.closeSurface(userdata, processAlive: processAlive) }
|
||||
)
|
||||
|
||||
@@ -276,8 +277,9 @@ extension Ghostty {
|
||||
|
||||
static func writeClipboard(
|
||||
_ userdata: UnsafeMutableRawPointer?,
|
||||
string: UnsafePointer<CChar>?,
|
||||
location: ghostty_clipboard_e,
|
||||
content: UnsafePointer<ghostty_clipboard_content_s>?,
|
||||
len: Int,
|
||||
confirm: Bool
|
||||
) {}
|
||||
|
||||
@@ -364,23 +366,48 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e, confirm: Bool) {
|
||||
static func writeClipboard(
|
||||
_ userdata: UnsafeMutableRawPointer?,
|
||||
location: ghostty_clipboard_e,
|
||||
content: UnsafePointer<ghostty_clipboard_content_s>?,
|
||||
len: Int,
|
||||
confirm: Bool
|
||||
) {
|
||||
let surface = self.surfaceUserdata(from: userdata)
|
||||
|
||||
|
||||
guard let pasteboard = NSPasteboard.ghostty(location) else { return }
|
||||
guard let valueStr = String(cString: string!, encoding: .utf8) else { return }
|
||||
guard let content = content, len > 0 else { return }
|
||||
|
||||
// Convert the C array to Swift array
|
||||
let contentArray = (0..<len).compactMap { i in
|
||||
Ghostty.ClipboardContent.from(content: content[i])
|
||||
}
|
||||
guard !contentArray.isEmpty else { return }
|
||||
|
||||
if !confirm {
|
||||
pasteboard.declareTypes([.string], owner: nil)
|
||||
pasteboard.setString(valueStr, forType: .string)
|
||||
// Declare all types
|
||||
let types = contentArray.compactMap { item in
|
||||
NSPasteboard.PasteboardType(mimeType: item.mime)
|
||||
}
|
||||
pasteboard.declareTypes(types, owner: nil)
|
||||
|
||||
// Set data for each type
|
||||
for item in contentArray {
|
||||
guard let type = NSPasteboard.PasteboardType(mimeType: item.mime) else { continue }
|
||||
pasteboard.setString(item.data, forType: type)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// For confirmation, use the text/plain content if it exists
|
||||
guard let textPlainContent = contentArray.first(where: { $0.mime == "text/plain" }) else {
|
||||
return
|
||||
}
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.confirmClipboard,
|
||||
object: surface,
|
||||
userInfo: [
|
||||
Notification.ConfirmClipboardStrKey: valueStr,
|
||||
Notification.ConfirmClipboardStrKey: textPlainContent.data,
|
||||
Notification.ConfirmClipboardRequestKey: Ghostty.ClipboardRequest.osc_52_write(pasteboard),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -299,6 +299,23 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ClipboardContent {
|
||||
let mime: String
|
||||
let data: String
|
||||
|
||||
static func from(content: ghostty_clipboard_content_s) -> ClipboardContent? {
|
||||
guard let mimePtr = content.mime,
|
||||
let dataPtr = content.data else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ClipboardContent(
|
||||
mime: String(cString: mimePtr),
|
||||
data: String(cString: dataPtr)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// macos-icon
|
||||
enum MacOSIcon: String {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
import AppKit
|
||||
import GhosttyKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension NSPasteboard.PasteboardType {
|
||||
/// Initialize a pasteboard type from a MIME type string
|
||||
init?(mimeType: String) {
|
||||
// Explicit mappings for common MIME types
|
||||
switch mimeType {
|
||||
case "text/plain":
|
||||
self = .string
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
// Try to get UTType from MIME type
|
||||
guard let utType = UTType(mimeType: mimeType) else {
|
||||
// Fallback: use the MIME type directly as identifier
|
||||
self.init(mimeType)
|
||||
return
|
||||
}
|
||||
|
||||
// Use the UTType's identifier
|
||||
self.init(utType.identifier)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSPasteboard {
|
||||
/// The pasteboard to used for Ghostty selection.
|
||||
|
||||
33
macos/Tests/NSPasteboardTests.swift
Normal file
33
macos/Tests/NSPasteboardTests.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// NSPasteboardTests.swift
|
||||
// GhosttyTests
|
||||
//
|
||||
// Tests for NSPasteboard.PasteboardType MIME type conversion.
|
||||
//
|
||||
|
||||
import Testing
|
||||
import AppKit
|
||||
@testable import Ghostty
|
||||
|
||||
struct NSPasteboardTypeExtensionTests {
|
||||
/// Test text/plain MIME type converts to .string
|
||||
@Test func testTextPlainMimeType() async throws {
|
||||
let pasteboardType = NSPasteboard.PasteboardType(mimeType: "text/plain")
|
||||
#expect(pasteboardType != nil)
|
||||
#expect(pasteboardType == .string)
|
||||
}
|
||||
|
||||
/// Test text/html MIME type converts to .html
|
||||
@Test func testTextHtmlMimeType() async throws {
|
||||
let pasteboardType = NSPasteboard.PasteboardType(mimeType: "text/html")
|
||||
#expect(pasteboardType != nil)
|
||||
#expect(pasteboardType == .html)
|
||||
}
|
||||
|
||||
/// Test image/png MIME type
|
||||
@Test func testImagePngMimeType() async throws {
|
||||
let pasteboardType = NSPasteboard.PasteboardType(mimeType: "image/png")
|
||||
#expect(pasteboardType != nil)
|
||||
#expect(pasteboardType == .png)
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,13 @@ pub const App = struct {
|
||||
) callconv(.c) void,
|
||||
|
||||
/// Write the clipboard value.
|
||||
write_clipboard: *const fn (SurfaceUD, [*:0]const u8, c_int, bool) callconv(.c) void,
|
||||
write_clipboard: *const fn (
|
||||
SurfaceUD,
|
||||
c_int,
|
||||
[*]const CAPI.ClipboardContent,
|
||||
usize,
|
||||
bool,
|
||||
) callconv(.c) void,
|
||||
|
||||
/// Close the current surface given by this function.
|
||||
close_surface: ?*const fn (SurfaceUD, bool) callconv(.c) void = null,
|
||||
@@ -707,8 +713,12 @@ pub const Surface = struct {
|
||||
) !void {
|
||||
self.app.opts.write_clipboard(
|
||||
self.userdata,
|
||||
val.ptr,
|
||||
@intCast(@intFromEnum(clipboard_type)),
|
||||
&.{.{
|
||||
.mime = "text/plain",
|
||||
.data = val.ptr,
|
||||
}},
|
||||
1,
|
||||
confirm,
|
||||
);
|
||||
}
|
||||
@@ -1211,6 +1221,12 @@ pub const CAPI = struct {
|
||||
cell_height_px: u32,
|
||||
};
|
||||
|
||||
// ghostty_clipboard_content_s
|
||||
const ClipboardContent = extern struct {
|
||||
mime: [*:0]const u8,
|
||||
data: [*:0]const u8,
|
||||
};
|
||||
|
||||
// ghostty_text_s
|
||||
const Text = extern struct {
|
||||
tl_px_x: f64,
|
||||
|
||||
@@ -54,6 +54,11 @@ pub const Clipboard = enum(Backing) {
|
||||
};
|
||||
};
|
||||
|
||||
pub const ClipboardContent = struct {
|
||||
mime: [:0]const u8,
|
||||
data: [:0]const u8,
|
||||
};
|
||||
|
||||
pub const ClipboardRequestType = enum(u8) {
|
||||
paste,
|
||||
osc_52_read,
|
||||
|
||||
Reference in New Issue
Block a user