From ff1ce8fa7629ba718641674298fd67439e7c34e1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 May 2026 08:41:23 +0530 Subject: [PATCH] Add detection for dnd protocol --- docs/dnd-protocol.rst | 25 ++++++++++++++++++++++++- gen/apc_parsers.py | 2 +- kitty/dnd.c | 7 +++++++ kitty/dnd.h | 1 + kitty/parse-dnd-command.h | 3 ++- kitty/screen.c | 3 +++ 6 files changed, 38 insertions(+), 3 deletions(-) diff --git a/docs/dnd-protocol.rst b/docs/dnd-protocol.rst index 2ace64061..717347f8f 100644 --- a/docs/dnd-protocol.rst +++ b/docs/dnd-protocol.rst @@ -25,7 +25,7 @@ Only the first chunk is guaranteed to have metadata other than the ``m`` key. Subsequent chunks may optionally omit all metadata except the ``m`` and ``i`` keys. While a chunked transfer is in progress it is a protocol error to for the sending side to -send any protocol related escape codes other than chunked ones. +send any protocol related escape codes other than chunked ones or query (``t=q|Q``) ones. In particular, this means that the receiving side should use the metadata from the first chunk in a chain of chunks only. @@ -438,6 +438,28 @@ inform the client of it with:: The error code for too many resources is ``EMFILE`` for IO errors is ``EIO`` and so on. +Detecting support for this protocol +------------------------------------- + +Clients can query the terminal emulator for support of this protocol +using:: + + OSC _dnd_code ; t=q:i=optional ST + +The ``i`` key is optional, if present it will be echoed back in the responses +from the terminal. A terminal supporting this protocol **must** respond with:: + + OSC _dnd_code ; t=q:i=echoed ; payload ST + +Here, ``payload`` is a colon separated list of ``key=value`` pairs. These +specify support for optional/future parts of this protocol. Currently the +payload is empty, but that might change as the protocol evolves. + +The client should send these escape codes followed by a request for the `primary device +attributes `_. If a response for the +device attributes is received before a response for the queries, then the +terminal does not support this protocol. + Multiplexers ----------------- @@ -469,6 +491,7 @@ Key Value Default Description ``e`` - a drag offer event occurred ``E`` - a drag offer data error occurred ``k`` - data for uri-list items in drag offer + ``q`` - query support for this protocol ``m`` Chunking indicator ``0`` ``0`` or ``1`` diff --git a/gen/apc_parsers.py b/gen/apc_parsers.py index 66ba7092e..51666f4a7 100755 --- a/gen/apc_parsers.py +++ b/gen/apc_parsers.py @@ -333,7 +333,7 @@ def parsers() -> None: write_header(text, 'kitty/parse-multicell-command.h') keymap = { - 't': ('type', flag('aAmMrRopPeEk')), + 't': ('type', flag('aAmMrRopPqeEk')), 'm': ('more', 'uint'), 'i': ('client_id', 'uint'), 'o': ('operation', 'uint'), diff --git a/kitty/dnd.c b/kitty/dnd.c index 2f6d49da7..086312fea 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -294,6 +294,13 @@ queue_payload_to_child(id_type id, uint32_t client_id, PendingData *pending, con if (pending->count) check_for_pending_writes(); } +void +dnd_query(Window *w, uint32_t client_id) { + char buf[64]; + ssize_t header_size = snprintf(buf, sizeof(buf), "\x1b]%d;t=q", DND_CODE); + send_payload_to_child(w->id, client_id, buf, header_size, NULL, 0, false); +} + static bool is_same_machine(const char *client_machine_id, size_t sz) { if (!sz || !client_machine_id) return true; diff --git a/kitty/dnd.h b/kitty/dnd.h index 5c51c139c..b45da1255 100644 --- a/kitty/dnd.h +++ b/kitty/dnd.h @@ -8,6 +8,7 @@ #include "state.h" +void dnd_query(Window *w, uint32_t client_id); void drop_register_window(Window *w, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id, bool more); void drop_register_machine_id(Window *w, const uint8_t *machine_id, size_t sz); diff --git a/kitty/parse-dnd-command.h b/kitty/parse-dnd-command.h index 70ae1f3c9..346a5b6fe 100644 --- a/kitty/parse-dnd-command.h +++ b/kitty/parse-dnd-command.h @@ -88,7 +88,8 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf, g.type = parser_buf[pos++]; if (g.type != 'A' && g.type != 'E' && g.type != 'M' && g.type != 'P' && g.type != 'R' && g.type != 'a' && g.type != 'e' && g.type != 'k' && - g.type != 'm' && g.type != 'o' && g.type != 'p' && g.type != 'r') { + g.type != 'm' && g.type != 'o' && g.type != 'p' && g.type != 'q' && + g.type != 'r') { REPORT_ERROR("Malformed DnDCommand control block, unknown flag value " "for type: 0x%x", g.type); diff --git a/kitty/screen.c b/kitty/screen.c index 2097a2b34..b13d9cb04 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1583,6 +1583,9 @@ screen_handle_dnd_command(Screen *self, const DnDCommand *cmd_, const uint8_t *p drag_remote_file_data( w, cmd->cell_x, cmd->cell_y, cmd->pixel_x, cmd->pixel_y, cmd->more != 0, payload, cmd->payload_sz); } break; + case 'q': { + dnd_query(w, cmd->client_id); + } break; } }