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
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:: 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 This will request data for the specified MIME type. The terminal must respond
with a series of escape codes of the form:: 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:: 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 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 ``EIO`` or the value ``EUNKNOWN`` for an unknown error. Unless otherwise noted,
sends a request for another MIME type before the data for the previous MIME type any error response means the drop is terminated.
is completed, the terminal *must* switch over to sending data for the new MIME
type.
Once the client program finishes reading all the dropped data it needs, it must Once the client program finishes reading all the dropped data it needs, it must
send an escape code of the form:: 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 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:: 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 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 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 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 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 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 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 permission to read the file it must reply with ``EPERM``. Terminals
must respond with ``t=R:r=request_id ; EINVAL`` if the file is not a regular file after must respond with ``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 resolving symlinks and ``ENOENT`` if the file does not exist. If an
I/O error occurs the terminal must send ``t=R:r=request_id ; EIO``. 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 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 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 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. 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 Clients may send multiple requests without waiting for any request to complete.
may respond in any order. If too many requests are received, they must deny Terminals may queue requests and respond in any order they choose, including
the request with ``t:R:r=request_id ; EMFILE`` and end the drop. 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 Reading remote directories
+++++++++++++++++++++++++++ +++++++++++++++++++++++++++
If the file is actually a directory the terminal must respond with ``t=d:x=idx:r=request_id ; payload``. If the file is actually a directory the terminal must instead respond with::
Here payload is a null byte separated list of entries in the directory that are
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 either regular files, directories or symlinks. The payload must be base64
encoded and might be chunked if the directory has a lot of entries. 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 ``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 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 OSC _dnd_code ; t=r:Y=handle:x=num ST
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 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 terminal can then free any resources associated with that directory. The
directory handle is now invalid and terminals must return ``EINVAL`` if 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 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``. cases the terminal must respond with ``ENOMEM``.
When transmitting a symlink that is inside a directory, 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 The presence of ``X=1`` indicates that the file is a symlink, not a
regular file. 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 Starting drags
@@ -351,9 +378,7 @@ Key Value Default Description
``m`` - a drop move event ``m`` - a drop move event
``M`` - a drop dropped event ``M`` - a drop dropped event
``r`` - request dropped data ``r`` - request dropped data
``R`` - report an error to the terminal program ``R`` - report an error
``s`` - request data from the URI list entry
``d`` - send directory contents
``o`` - start offering drags ``o`` - start offering drags
``O`` - stop offering drags ``O`` - stop offering drags
``p`` - present data for drag offers ``p`` - present data for drag offers
@@ -372,8 +397,6 @@ Key Value Default Description
means rejected, ``1`` means copy and means rejected, ``1`` means copy and
``2`` means move. ``2`` means move.
``r`` Positive integer ``0`` The request id
**Keys for location** **Keys for location**
----------------------------------------------------------- -----------------------------------------------------------
``x`` Integer ``0`` Cell x-coordinate origin is 0, 0 at top left of screen ``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') write_header(text, 'kitty/parse-multicell-command.h')
keymap = { keymap = {
't': ('type', flag('aAmMrRsdoOpPeE')), 't': ('type', flag('aAmMrRoOpPeE')),
'm': ('more', 'uint'), 'm': ('more', 'uint'),
'i': ('client_id', 'uint'), 'i': ('client_id', 'uint'),
'o': ('operation', 'uint'), 'o': ('operation', 'uint'),
'r': ('request_id', 'uint'),
'x': ('cell_x', 'int'), 'x': ('cell_x', 'int'),
'y': ('cell_y', 'int'), 'y': ('cell_y', 'int'),
'X': ('pixel_x', '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', more = 'm',
client_id = 'i', client_id = 'i',
operation = 'o', operation = 'o',
request_id = 'r',
cell_x = 'x', cell_x = 'x',
cell_y = 'y', cell_y = 'y',
pixel_x = 'X', pixel_x = 'X',
@@ -52,9 +51,6 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
case operation: case operation:
value_state = UINT; value_state = UINT;
break; break;
case request_id:
value_state = UINT;
break;
case cell_x: case cell_x:
value_state = INT; value_state = INT;
break; break;
@@ -91,9 +87,8 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
case type: { case type: {
g.type = parser_buf[pos++]; g.type = parser_buf[pos++];
if (g.type != 'A' && g.type != 'E' && g.type != 'M' && g.type != 'O' && 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 != 'P' && g.type != 'R' && g.type != 'a' && g.type != 'e' &&
g.type != 'e' && g.type != 'm' && g.type != 'o' && g.type != 'p' && g.type != 'm' && g.type != 'o' && g.type != 'p' && g.type != 'r') {
g.type != 'r' && g.type != 's') {
REPORT_ERROR("Malformed DnDCommand control block, unknown flag value " REPORT_ERROR("Malformed DnDCommand control block, unknown flag value "
"for type: 0x%x", "for type: 0x%x",
g.type); g.type);
@@ -160,7 +155,6 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
U(more); U(more);
U(client_id); U(client_id);
U(operation); U(operation);
U(request_id);
default: default:
break; break;
} }
@@ -212,13 +206,12 @@ static inline void parse_dnd_code(PS *self, uint8_t *parser_buf,
} }
REPORT_VA_COMMAND( 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, "type", g.type,
"more", (unsigned int)g.more, "client_id", (unsigned int)g.client_id, "more", (unsigned int)g.more, "client_id", (unsigned int)g.client_id,
"operation", (unsigned int)g.operation, "request_id", "operation", (unsigned int)g.operation,
(unsigned int)g.request_id,
"cell_x", (int)g.cell_x, "cell_y", (int)g.cell_y, "pixel_x", "cell_x", (int)g.cell_x, "cell_y", (int)g.cell_y, "pixel_x",
(int)g.pixel_x, "pixel_y", (int)g.pixel_y, (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 { typedef struct DnDCommand {
char type; char type;
unsigned more; unsigned more;
uint32_t client_id, request_id; uint32_t client_id;
size_t payload_sz; size_t payload_sz;
int32_t cell_x, cell_y, pixel_x, pixel_y; int32_t cell_x, cell_y, pixel_x, pixel_y;
uint32_t operation; uint32_t operation;