Refactor the drop protocol

Get rid of request_id. Instead use the x, y, and Y fields to
disambiguate requests. Specify error handling a little more robustly.

Implementation needed.
This commit is contained in:
Kovid Goyal 2026-04-09 20:35:36 +05:30
parent 019158c168
commit 3ab89a2fa8
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
4 changed files with 69 additions and 54 deletions

View file

@ -104,23 +104,22 @@ it is interested in.
Requesting data is done by sending an escape code of the form::
OSC _dnd_code ; t=r:r=request_id ; MIME type ST
OSC _dnd_code ; t=r:x=idx ST
Here ``idx`` is a 1-based index into the list of MIME types sent previously.
This will request data for the specified MIME type. The terminal must respond
with a series of escape codes of the form::
OSC _dnd_code ; t=r:r=request_id ; base64 encoded data ST
OSC _dnd_code ; t=r:x=idx; base64 encoded data possibly chunked ST
End of data is indicated by an empty payload. If some error occurs while
End of data is indicated by an empty payload and ``m=0``. If some error occurs while
getting the data, the terminal must send an escape code of the form::
OSC _dnd_code ; t=R:r=request_id ; POSIX error name ST
OSC _dnd_code ; t=R:x=idx ; POSIX error name ST
Here POSIX error name is a POSIX symbolic error name such as ``ENOENT`` or
``EIO`` or the value ``EUNKNOWN`` for an unknown error. Note that if a client
sends a request for another MIME type before the data for the previous MIME type
is completed, the terminal *must* switch over to sending data for the new MIME
type.
Here ``POSIX error name`` is a POSIX symbolic error name such as ``ENOENT`` or
``EIO`` or the value ``EUNKNOWN`` for an unknown error. Unless otherwise noted,
any error response means the drop is terminated.
Once the client program finishes reading all the dropped data it needs, it must
send an escape code of the form::
@ -138,51 +137,78 @@ clients can first request the :rfc:`text/uri-list <2483>` MIME
type to get a list of dropped URIs. For every URI in the list, they can
send the terminal emulator a data request of the form::
OSC _dnd_code ; t=s:r=request_id ; text/uri-list:idx ST
OSC _dnd_code ; t=r:x=idx:y=subidx ST
Here ``idx`` is the zero based index into the array of MIME types in
Here ``idx`` is the one based index of the ``text/uri-list`` MIME type. And
``subidx`` is the one based index into the array of MIME types in
the ``text/uri-list`` entry. The terminal will then read the file and
transmit the data as for a normal MIME data request.
transmit the data as for a normal MIME data request, except it will have
``y=subidx`` as well in its response, for example::
Terminals must reply with ``t=R:r=request_id ; ENOENT`` if the index is out of bounds.
OSC _dnd_code ; t=r:x=idx:y=subidx ; base64 encoded data possibly chunked ST
Similarly, error responses are as above, except for the addition of
``y=subidx``, for example::
OSC _dnd_code ; t=R:x=idx:y=subidx ; POSIX error name ST
Terminals must reply with ``ENOENT`` if the index is out of bounds.
If the client does not first request the ``text/uri-list`` MIME type or that
MIME type is not present in the drop, the terminal must reply with
``t=R:r=request_id ; EINVAL``. Terminals must support at least ``file://`` URIs.
``EINVAL``. Terminals must support at least ``file://`` URIs.
If the client requests an entry that is not a supported URI type the
terminal must reply with ``t=R:r=request_id ; EUNKNOWN``.
terminal must reply with ``EUNKNOWN``.
Terminals must ONLY send data for regular files or directories. Symbolic links must be
resolved and the corresponding file or directory read. If the terminal does not have
permission to read the file it must reply with ``t=R:r=request_id ; EPERM``. Terminals
must respond with ``t=R:r=request_id ; EINVAL`` if the file is not a regular file after
resolving symlinks and ``t=R:r=request_id ; ENOENT`` if the file does not exist. If an
I/O error occurs the terminal must send ``t=R:r=request_id ; EIO``.
permission to read the file it must reply with ``EPERM``. Terminals
must respond with ``EINVAL`` if the file is not a regular file after
resolving symlinks and ``ENOENT`` if the file does not exist. If an
I/O error occurs the terminal must send ``EIO``.
For security reasons, terminals must reply with ``t=R:r=request_id ; EPERM`` if the drag
For security reasons, terminals must reply with ``EPERM`` if the drag
originated in the same window as the drop, this prevents malicious programs
from reading files on the computer by starting their own drag. This is a
defense in depth feature since drags can only be started by the terminal, but
it helps in case of accidental drag starts and drops into the same window.
Terminals may queue requests with different ids and respond in order, or they
may respond in any order. If too many requests are received, they must deny
the request with ``t:R:r=request_id ; EMFILE`` and end the drop.
Clients may send multiple requests without waiting for any request to complete.
Terminals may queue requests and respond in any order they choose, including
interleaving responses to different requests. However, for simplicity, this
specification recommends terminals queue requests and respond in first-in,
first-out order. Every response can be matched to a corresponding request
using the ``x``, ``y`` and ``Y`` keys. To prevent Denial of service attacks,
if too many requests are received, terminals must deny the request
with ``EMFILE`` and end the drop.
Reading remote directories
+++++++++++++++++++++++++++
If the file is actually a directory the terminal must respond with ``t=d:x=idx:r=request_id ; payload``.
Here payload is a null byte separated list of entries in the directory that are
If the file is actually a directory the terminal must instead respond with::
OSC _dnd_code ; t=r:x=idx:y=subidx:Y=handle:X=2 ; base64 encoded list of dir entries ST
The presence of ``X=2`` indicates this is a directory response not a regular
file. Here, the payload is a null byte separated list of entries in the directory that are
either regular files, directories or symlinks. The payload must be base64
encoded and might be chunked if the directory has a lot of entries.
``idx`` is an arbitrary 32 bit integer that acts as a handle to this
directory. The client can now read the files in this directory using requests of the form
``t=d:x=idx:y=num:r=request_id``, here ``num`` is the 1-based index into the list of
directory entries previously transmitted to the client, where, ``1`` will
correspond to the first entry in the directory. Once the client is done
reading a directory it should transmit ``t=d:x=idx:r=request_id`` to the terminal. The
``handle`` is an arbitrary non-zero integer that acts as a handle to this
directory. The client can now read the files in this directory using requests of the form::
OSC _dnd_code ; t=r:Y=handle:x=num ST
Here ``num`` is the 1-based index into the list of directory entries previously transmitted
to the client. The terminal will respond with an escape code of the form::
OSC _dnd_code ; t=r:Y=handle:x=num ; base64 encoded data of entry ST
In case of any errors, the terminal will respond with::
OSC _dnd_code ; t=R:Y=handle:x=num ; POSIX error name ST
Once the client is done reading a directory it should transmit ``t=r:Y=handle`` to the terminal. The
terminal can then free any resources associated with that directory. The
directory handle is now invalid and terminals must return ``EINVAL`` if the
client sends a request using an invalid directory handle. It is recommended
@ -192,12 +218,13 @@ resources are used, in order to prevent denial or service attacks. In such
cases the terminal must respond with ``ENOMEM``.
When transmitting a symlink that is inside a directory,
the terminal responds with escape code of the form::
the terminal responds with an escape code of the form::
OSC _dnd_code ; t=r:r=request_id:X=1 ; base64 encoded symlink target ST
OSC _dnd_code ; t=r:Y=handle:x=num:X=1 ; base64 encoded symlink target ST
Here, the presence of ``X=1`` indicates that the file is a symlink, not a
regular file.
The presence of ``X=1`` indicates that the file is a symlink, not a
regular file. Similarly for sub-directories it would be ``X=2`` and the payload
would be the null separated list of directory entries, as above.
Starting drags
@ -351,9 +378,7 @@ Key Value Default Description
``m`` - a drop move event
``M`` - a drop dropped event
``r`` - request dropped data
``R`` - report an error to the terminal program
``s`` - request data from the URI list entry
``d`` - send directory contents
``R`` - report an error
``o`` - start offering drags
``O`` - stop offering drags
``p`` - present data for drag offers
@ -372,8 +397,6 @@ Key Value Default Description
means rejected, ``1`` means copy and
``2`` means move.
``r`` Positive integer ``0`` The request id
**Keys for location**
-----------------------------------------------------------
``x`` Integer ``0`` Cell x-coordinate origin is 0, 0 at top left of screen

View file

@ -333,11 +333,10 @@ def parsers() -> None:
write_header(text, 'kitty/parse-multicell-command.h')
keymap = {
't': ('type', flag('aAmMrRsdoOpPeE')),
't': ('type', flag('aAmMrRoOpPeE')),
'm': ('more', 'uint'),
'i': ('client_id', 'uint'),
'o': ('operation', 'uint'),
'r': ('request_id', 'uint'),
'x': ('cell_x', 'int'),
'y': ('cell_y', 'int'),
'X': ('pixel_x', 'int'),

View file

@ -23,7 +23,6 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
more = 'm',
client_id = 'i',
operation = 'o',
request_id = 'r',
cell_x = 'x',
cell_y = 'y',
pixel_x = 'X',
@ -52,9 +51,6 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
case operation:
value_state = UINT;
break;
case request_id:
value_state = UINT;
break;
case cell_x:
value_state = INT;
break;
@ -91,9 +87,8 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
case type: {
g.type = parser_buf[pos++];
if (g.type != 'A' && g.type != 'E' && g.type != 'M' && g.type != 'O' &&
g.type != 'P' && g.type != 'R' && g.type != 'a' && g.type != 'd' &&
g.type != 'e' && g.type != 'm' && g.type != 'o' && g.type != 'p' &&
g.type != 'r' && g.type != 's') {
g.type != 'P' && g.type != 'R' && g.type != 'a' && g.type != 'e' &&
g.type != 'm' && g.type != 'o' && g.type != 'p' && g.type != 'r') {
REPORT_ERROR("Malformed DnDCommand control block, unknown flag value "
"for type: 0x%x",
g.type);
@ -160,7 +155,6 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
U(more);
U(client_id);
U(operation);
U(request_id);
default:
break;
}
@ -212,13 +206,12 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
}
REPORT_VA_COMMAND(
"K s {sc sI sI sI sI si si si si ss#}", self->window_id, "dnd_command",
"K s {sc sI sI sI si si si si ss#}", self->window_id, "dnd_command",
"type", g.type,
"more", (unsigned int)g.more, "client_id", (unsigned int)g.client_id,
"operation", (unsigned int)g.operation, "request_id",
(unsigned int)g.request_id,
"operation", (unsigned int)g.operation,
"cell_x", (int)g.cell_x, "cell_y", (int)g.cell_y, "pixel_x",
(int)g.pixel_x, "pixel_y", (int)g.pixel_y,

View file

@ -17,7 +17,7 @@ typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } Scr
typedef struct DnDCommand {
char type;
unsigned more;
uint32_t client_id, request_id;
uint32_t client_id;
size_t payload_sz;
int32_t cell_x, cell_y, pixel_x, pixel_y;
uint32_t operation;