osc: implement OSC-5522, kitty's extended version of OSC-52

OSC-5522[^1] gives the terminal application access to all offered
mime-types when reading the clipboard, and allows setting multiple
mime-types when writing to the clipboard (with different contents, if
it so wishes).

In addition to the base protocol, we also implement the _event
extension_[^2], where a paste action (e.g. ctrl+shift+v) results in an
unsolicited mime-type listing being sent to the terminal
application (as if it had issued a mime-type query itself), instead of
the clipboard content being pasted directly. The application follows
up with an explicit read request (or chooses to ignore the event).

The protocol supports "passwords", as a way of bypassing terminal
popups asking the user for approval, after the first popup has been
approved by the user. Foot doesn't implement this kind of user
approval, but all read and write requests are denied with EPERM if the
user has disabled OSC copy/pasting with the security.osc52
configuration option. In addition, if disabled, event reporting cannot
be enabled at all (i.e. 'CSI ? 5522 h' is ignored).

[^1]: https://sw.kovidgoyal.net/kitty/clipboard/
[^2]: https://rockorager.dev/misc/bracketed-paste-mime/
This commit is contained in:
Daniel Eklöf 2026-05-15 18:35:51 +02:00
parent 4bc8a39d6c
commit c366e322eb
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
13 changed files with 839 additions and 126 deletions

18
csi.c
View file

@ -576,6 +576,21 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
term_disable_size_notifications(term);
break;
case 5522:
if (enable) {
if (term_osc_paste_allowed(term))
term->kitty_clipboard.emit_events = true;
else {
static bool have_warned = false;
if (!have_warned) {
have_warned = true;
LOG_WARN("attempt to enable private mode 5522 ignored: disabled in configuration");
}
}
} else
term->kitty_clipboard.emit_events = false;
break;
case 8452:
term->sixel.cursor_right_of_graphics = enable;
break;
@ -665,6 +680,7 @@ decrqm(const struct terminal *term, unsigned param)
: decrpm(term->grapheme_shaping);
case 2031: return decrpm(term->report_theme_changes);
case 2048: return decrpm(term->size_notifications);
case 5522: return decrpm(term->kitty_clipboard.emit_events);
case 8452: return decrpm(term->sixel.cursor_right_of_graphics);
case 737769: return decrpm(term_ime_is_enabled(term));
}
@ -711,6 +727,7 @@ xtsave(struct terminal *term, unsigned param)
case 2027: term->xtsave.grapheme_shaping = term->grapheme_shaping; break;
case 2031: term->xtsave.report_theme_changes = term->report_theme_changes; break;
case 2048: term->xtsave.size_notifications = term->size_notifications; break;
case 5522: term->xtsave.kitty_clipboard = term->kitty_clipboard.emit_events; break;
case 8452: term->xtsave.sixel_cursor_right_of_graphics = term->sixel.cursor_right_of_graphics; break;
case 737769: term->xtsave.ime = term_ime_is_enabled(term); break;
}
@ -756,6 +773,7 @@ xtrestore(struct terminal *term, unsigned param)
case 2027: enable = term->xtsave.grapheme_shaping; break;
case 2031: enable = term->xtsave.report_theme_changes; break;
case 2048: enable = term->xtsave.size_notifications; break;
case 5522: enable = term->xtsave.kitty_clipboard; break;
case 8452: enable = term->xtsave.sixel_cursor_right_of_graphics; break;
case 737769: enable = term->xtsave.ime; break;