mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-26 17:11:40 +00:00
macos: answer ENOSYS for Kitty clipboard writes to primary (#14000)
A Kitty clipboard protocol (OSC 5522) write transaction targeting `loc=primary` replied `type=write:status=DONE` in the macOS app even though macOS has no primary selection and the data was silently discarded. The spec requires ENOSYS when the requested location is not available on the system, which the read path already answers correctly: https://sw.kovidgoyal.net/kitty/clipboard/
This commit is contained in:
@@ -75,6 +75,7 @@ typedef enum {
|
||||
typedef enum {
|
||||
GHOSTTY_CLIPBOARD_STANDARD,
|
||||
GHOSTTY_CLIPBOARD_SELECTION,
|
||||
GHOSTTY_CLIPBOARD_PRIMARY,
|
||||
} ghostty_clipboard_e;
|
||||
|
||||
// One representation of clipboard contents. The data is binary-safe with
|
||||
|
||||
@@ -132,7 +132,8 @@ extension NSPasteboard {
|
||||
return result
|
||||
}
|
||||
|
||||
/// The pasteboard for the Ghostty enum type.
|
||||
/// The pasteboard for the Ghostty enum type. Returns nil for locations
|
||||
/// macOS can't serve; callers report those as unsupported.
|
||||
static func ghostty(_ clipboard: ghostty_clipboard_e) -> NSPasteboard? {
|
||||
switch clipboard {
|
||||
case GHOSTTY_CLIPBOARD_STANDARD:
|
||||
@@ -141,6 +142,10 @@ extension NSPasteboard {
|
||||
case GHOSTTY_CLIPBOARD_SELECTION:
|
||||
return Self.ghosttySelection
|
||||
|
||||
case GHOSTTY_CLIPBOARD_PRIMARY:
|
||||
// macOS has no primary selection.
|
||||
return nil
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -702,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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -717,6 +723,14 @@ pub const Surface = struct {
|
||||
// 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);
|
||||
|
||||
@@ -3776,6 +3776,18 @@ test "kitty clipboard write result maps to response status" {
|
||||
"\x1B]5522;type=write:status=DONE\x07",
|
||||
S.responseSlice(),
|
||||
);
|
||||
|
||||
// A system without a primary selection answers a loc=primary write
|
||||
// with ENOSYS, echoing the id.
|
||||
S.reset();
|
||||
S.write_result = .unsupported;
|
||||
s.nextSlice("\x1B]5522;type=write:loc=primary:id=p1\x1B\\");
|
||||
s.nextSlice("\x1B]5522;type=wdata\x1B\\");
|
||||
try testing.expectEqual(clipboard.Location.primary, S.last_location);
|
||||
try testing.expectEqualStrings(
|
||||
"\x1B]5522;type=write:status=ENOSYS:id=p1\x1B\\",
|
||||
S.responseSlice(),
|
||||
);
|
||||
}
|
||||
|
||||
test "kitty clipboard write without clipboard effect responds ENOSYS" {
|
||||
|
||||
Reference in New Issue
Block a user