mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-27 18:51:41 +02:00
Allow drop clients to use machine id as well
This commit is contained in:
@@ -38,7 +38,7 @@ the end of the last chunk.
|
|||||||
Accepting drops
|
Accepting drops
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
In order to inform the terminal emulator that the program accepts drops, it
|
In order to inform the terminal emulator that the client accepts drops, it
|
||||||
must send the following escape code::
|
must send the following escape code::
|
||||||
|
|
||||||
OSC _dnd_code ; t=a ; payload ST
|
OSC _dnd_code ; t=a ; payload ST
|
||||||
@@ -48,7 +48,17 @@ The list of MIME types is optional, it is needed if the program wants to accept
|
|||||||
exotic or private use MIME types on platforms such as macOS, where the system
|
exotic or private use MIME types on platforms such as macOS, where the system
|
||||||
does not deliver drop events unless the MIME type is registered.
|
does not deliver drop events unless the MIME type is registered.
|
||||||
|
|
||||||
When the program is done accepting drops, or at exit, it should send the escape
|
The terminal emulator may respond to this escape code with an escape code of
|
||||||
|
the form::
|
||||||
|
|
||||||
|
OSC _dnd_code ; t=a ; machine id ST
|
||||||
|
|
||||||
|
Here, the :ref:`machine id <machine_id>` is an id that identifies the machine
|
||||||
|
the terminal is running on and can be used by the client to determine whether
|
||||||
|
to request remote files from the terminal when a drop occurs.
|
||||||
|
See :ref:`below <machine_id>` for the semantics of the machine id.
|
||||||
|
|
||||||
|
When the client is done accepting drops, or at exit, it should send the escape
|
||||||
code::
|
code::
|
||||||
|
|
||||||
OSC _dnd_code ; t=A ST
|
OSC _dnd_code ; t=A ST
|
||||||
@@ -132,8 +142,12 @@ terminal emulator must then inform the OS that the drop is completed.
|
|||||||
Dropping from remote machines
|
Dropping from remote machines
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
In order to support dropping of files from remote machines,
|
In order to support dropping of files from remote machines, the client
|
||||||
clients can first request the :rfc:`text/uri-list <2483>` MIME
|
can use the :ref:`machine id <machine_id>` previously sent by the terminal.
|
||||||
|
If it is different from the id of the machine the client is running on, it
|
||||||
|
can choose to request remote files, as follows.
|
||||||
|
|
||||||
|
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::
|
||||||
|
|
||||||
|
|||||||
81
kitty/dnd.c
81
kitty/dnd.c
@@ -144,40 +144,6 @@ reset_drop(Window *w) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
drop_register_window(Window *w, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id, bool more) {
|
|
||||||
w->drop.wanted = on;
|
|
||||||
w->drop.client_id = client_id;
|
|
||||||
if (!on) { drop_free_data(w); zero_at_ptr(&w->drop); return; }
|
|
||||||
if (!payload || !payload_sz) return;
|
|
||||||
size_t sz = w->drop.registered_mimes ? strlen(w->drop.registered_mimes) : 0;
|
|
||||||
if (sz + payload_sz > MIME_LIST_SIZE_CAP) return;
|
|
||||||
w->drop.registered_mimes = realloc(w->drop.registered_mimes, sz + payload_sz + 1);
|
|
||||||
if (w->drop.registered_mimes) {
|
|
||||||
memcpy(w->drop.registered_mimes + sz, payload, payload_sz);
|
|
||||||
sz += payload_sz;
|
|
||||||
w->drop.registered_mimes[sz] = 0;
|
|
||||||
}
|
|
||||||
if (more) return;
|
|
||||||
if (w->drop.registered_mimes) {
|
|
||||||
OSWindow *osw = os_window_for_kitty_window(w->id);
|
|
||||||
if (osw) {
|
|
||||||
size_t num = 0;
|
|
||||||
RAII_ALLOC(const char*, mimes, malloc(sizeof(char*) * strlen(w->drop.registered_mimes)));
|
|
||||||
if (mimes) {
|
|
||||||
char* token = strtok(w->drop.registered_mimes, " ");
|
|
||||||
while (token != NULL) {
|
|
||||||
mimes[num++] = token;
|
|
||||||
token = strtok(NULL, " ");
|
|
||||||
}
|
|
||||||
register_mimes_for_drop(osw, mimes, num);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
free(w->drop.registered_mimes); w->drop.registered_mimes = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
string_arrays_cmp(const char **a, size_t an, const char **b, size_t bn) {
|
string_arrays_cmp(const char **a, size_t an, const char **b, size_t bn) {
|
||||||
if (an != bn) return (int)an - (int)bn;
|
if (an != bn) return (int)an - (int)bn;
|
||||||
@@ -289,6 +255,46 @@ queue_payload_to_child(id_type id, uint32_t client_id, PendingData *pending, con
|
|||||||
if (pending->count) check_for_pending_writes();
|
if (pending->count) check_for_pending_writes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
drop_register_window(Window *w, const uint8_t *payload, size_t payload_sz, bool on, uint32_t client_id, bool more) {
|
||||||
|
w->drop.wanted = on;
|
||||||
|
w->drop.client_id = client_id;
|
||||||
|
if (!on) { drop_free_data(w); zero_at_ptr(&w->drop); return; }
|
||||||
|
if (!payload || !payload_sz) return;
|
||||||
|
size_t sz = w->drop.registered_mimes ? strlen(w->drop.registered_mimes) : 0;
|
||||||
|
if (sz + payload_sz > MIME_LIST_SIZE_CAP) return;
|
||||||
|
w->drop.registered_mimes = realloc(w->drop.registered_mimes, sz + payload_sz + 1);
|
||||||
|
if (w->drop.registered_mimes) {
|
||||||
|
memcpy(w->drop.registered_mimes + sz, payload, payload_sz);
|
||||||
|
sz += payload_sz;
|
||||||
|
w->drop.registered_mimes[sz] = 0;
|
||||||
|
}
|
||||||
|
if (more) return;
|
||||||
|
if (w->drop.registered_mimes) {
|
||||||
|
OSWindow *osw = os_window_for_kitty_window(w->id);
|
||||||
|
if (osw) {
|
||||||
|
size_t num = 0;
|
||||||
|
RAII_ALLOC(const char*, mimes, malloc(sizeof(char*) * strlen(w->drop.registered_mimes)));
|
||||||
|
if (mimes) {
|
||||||
|
char* token = strtok(w->drop.registered_mimes, " ");
|
||||||
|
while (token != NULL) {
|
||||||
|
mimes[num++] = token;
|
||||||
|
token = strtok(NULL, " ");
|
||||||
|
}
|
||||||
|
register_mimes_for_drop(osw, mimes, num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(w->drop.registered_mimes); w->drop.registered_mimes = NULL;
|
||||||
|
const char* host_machine_id = machine_id();
|
||||||
|
if (host_machine_id) {
|
||||||
|
char header[32] = {0};
|
||||||
|
int n = snprintf(header, sizeof(header), "\x1b]%d;t=a", DND_CODE);
|
||||||
|
queue_payload_to_child(
|
||||||
|
w->id, w->drop.client_id, &w->drop.pending, header, n, host_machine_id, strlen(host_machine_id), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
drop_move_on_child(Window *w, const char** mimes, size_t num_mimes, bool is_drop) {
|
drop_move_on_child(Window *w, const char** mimes, size_t num_mimes, bool is_drop) {
|
||||||
if (!w->drop.hovered) {
|
if (!w->drop.hovered) {
|
||||||
@@ -866,13 +872,6 @@ do_drop_request_uri_data(Window *w, int32_t mime_idx, int32_t file_idx) {
|
|||||||
return sync;
|
return sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
drop_request_uri_data(Window *w, const char *payload, size_t payload_sz) {
|
|
||||||
(void)w; (void)payload; (void)payload_sz;
|
|
||||||
/* This function is no longer used in the new protocol; URI data requests
|
|
||||||
* are now handled through the unified t=r queue via do_drop_request_uri_data. */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle a directory request from the client.
|
/* Handle a directory request from the client.
|
||||||
* handle_id: the directory handle (Y= key).
|
* handle_id: the directory handle (Y= key).
|
||||||
* entry_num: 0 means close the handle; >=1 means read that entry (x= key, 1-based).
|
* entry_num: 0 means close the handle; >=1 means read that entry (x= key, 1-based).
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ void drop_move_on_child(Window *w, const char **mimes, size_t num_mimes, bool is
|
|||||||
void drop_left_child(Window *w);
|
void drop_left_child(Window *w);
|
||||||
void drop_free_data(Window *w);
|
void drop_free_data(Window *w);
|
||||||
void drop_send_einval(Window *w);
|
void drop_send_einval(Window *w);
|
||||||
void drop_request_uri_data(Window *w, const char *payload, size_t payload_sz);
|
|
||||||
void drop_handle_dir_request(Window *w, uint32_t handle_id, int32_t entry_num);
|
void drop_handle_dir_request(Window *w, uint32_t handle_id, int32_t entry_num);
|
||||||
void drop_enqueue_request(Window *w, int32_t cell_x, int32_t cell_y, int32_t pixel_y);
|
void drop_enqueue_request(Window *w, int32_t cell_x, int32_t cell_y, int32_t pixel_y);
|
||||||
void drop_set_status(Window *w, int operation, const char *payload, size_t payload_sz, bool more);
|
void drop_set_status(Window *w, int operation, const char *payload, size_t payload_sz, bool more);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import errno
|
|||||||
import re
|
import re
|
||||||
from base64 import standard_b64decode, standard_b64encode
|
from base64 import standard_b64decode, standard_b64encode
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from kitty.fast_data_types import (
|
from kitty.fast_data_types import (
|
||||||
DND_CODE,
|
DND_CODE,
|
||||||
@@ -16,6 +17,7 @@ from kitty.fast_data_types import (
|
|||||||
dnd_test_fake_drop_event,
|
dnd_test_fake_drop_event,
|
||||||
dnd_test_set_mouse_pos,
|
dnd_test_set_mouse_pos,
|
||||||
)
|
)
|
||||||
|
from kitty.machine_id import machine_id
|
||||||
|
|
||||||
from . import BaseTest, parse_bytes
|
from . import BaseTest, parse_bytes
|
||||||
|
|
||||||
@@ -321,6 +323,8 @@ def dnd_test_window():
|
|||||||
dnd_test_cleanup_fake_window(os_window_id)
|
dnd_test_cleanup_fake_window(os_window_id)
|
||||||
|
|
||||||
|
|
||||||
|
machine_id = partial(machine_id, 'tty-dnd-protocol-machine-id')
|
||||||
|
|
||||||
# ---- test class -------------------------------------------------------------
|
# ---- test class -------------------------------------------------------------
|
||||||
|
|
||||||
class TestDnDProtocol(BaseTest):
|
class TestDnDProtocol(BaseTest):
|
||||||
@@ -328,6 +332,14 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def _assert_no_output(self, capture: _WriteCapture, window_id: int) -> None:
|
def _assert_no_output(self, capture: _WriteCapture, window_id: int) -> None:
|
||||||
self.ae(capture.peek(window_id), b'', 'unexpected output to child')
|
self.ae(capture.peek(window_id), b'', 'unexpected output to child')
|
||||||
|
|
||||||
|
def _register_for_drops(self, screen, cap, wid, mimes='text/plain text/uri-list', client_id=0) -> None:
|
||||||
|
parse_bytes(screen, client_register(mimes, client_id=client_id))
|
||||||
|
events = self._get_events(cap, wid)
|
||||||
|
self.assertEqual(len(events), 1, events)
|
||||||
|
self.ae(events[0]['type'], 'a')
|
||||||
|
self.ae(events[0]['payload'].strip().decode(), machine_id())
|
||||||
|
|
||||||
|
|
||||||
def _get_events(self, capture: _WriteCapture, window_id: int) -> list[dict]:
|
def _get_events(self, capture: _WriteCapture, window_id: int) -> list[dict]:
|
||||||
return parse_escape_codes(capture.consume(window_id))
|
return parse_escape_codes(capture.consume(window_id))
|
||||||
|
|
||||||
@@ -336,10 +348,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
# Client registers – state is already wanted=True from fake-window creation,
|
# Client registers – state is already wanted=True from fake-window creation,
|
||||||
# but calling the escape code should not break things.
|
# but calling the escape code should not break things.
|
||||||
parse_bytes(screen, client_register('text/plain text/uri-list'))
|
self._register_for_drops(screen, cap, wid)
|
||||||
# No output expected at this point (no drop in progress).
|
|
||||||
self._assert_no_output(cap, wid)
|
|
||||||
|
|
||||||
# Client unregisters.
|
# Client unregisters.
|
||||||
parse_bytes(screen, client_unregister())
|
parse_bytes(screen, client_unregister())
|
||||||
self._assert_no_output(cap, wid)
|
self._assert_no_output(cap, wid)
|
||||||
@@ -347,7 +356,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_drop_move_sends_move_event(self) -> None:
|
def test_drop_move_sends_move_event(self) -> None:
|
||||||
"""A drop entering and moving over the window generates t=m events."""
|
"""A drop entering and moving over the window generates t=m events."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 5, 3, 100, 60)
|
dnd_test_set_mouse_pos(wid, 5, 3, 100, 60)
|
||||||
dnd_test_fake_drop_event(wid, False, ['text/plain', 'text/uri-list'])
|
dnd_test_fake_drop_event(wid, False, ['text/plain', 'text/uri-list'])
|
||||||
|
|
||||||
@@ -366,7 +375,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_drop_move_mime_always_sent(self) -> None:
|
def test_drop_move_mime_always_sent(self) -> None:
|
||||||
"""The current implementation always includes the MIME list in move events."""
|
"""The current implementation always includes the MIME list in move events."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
mimes = ['text/plain']
|
mimes = ['text/plain']
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, False, mimes)
|
dnd_test_fake_drop_event(wid, False, mimes)
|
||||||
@@ -384,7 +393,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_drop_leave_sends_leave_event(self) -> None:
|
def test_drop_leave_sends_leave_event(self) -> None:
|
||||||
"""Drop leaving sends t=m with x=-1,y=-1."""
|
"""Drop leaving sends t=m with x=-1,y=-1."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, False, ['text/plain'])
|
dnd_test_fake_drop_event(wid, False, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -400,7 +409,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_client_accepts_drop(self) -> None:
|
def test_client_accepts_drop(self) -> None:
|
||||||
"""Client sending t=m:o=1 is recorded and does not trigger extra output."""
|
"""Client sending t=m:o=1 is recorded and does not trigger extra output."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, False, ['text/plain'])
|
dnd_test_fake_drop_event(wid, False, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -414,7 +423,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
"""Complete happy-path: move → accept → drop → request → data → finish."""
|
"""Complete happy-path: move → accept → drop → request → data → finish."""
|
||||||
payload_data = b'hello world'
|
payload_data = b'hello world'
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
|
|
||||||
# Move
|
# Move
|
||||||
dnd_test_set_mouse_pos(wid, 2, 3, 16, 24)
|
dnd_test_set_mouse_pos(wid, 2, 3, 16, 24)
|
||||||
@@ -451,7 +460,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_request_unknown_mime(self) -> None:
|
def test_request_unknown_mime(self) -> None:
|
||||||
"""Requesting an out-of-range MIME index yields an error."""
|
"""Requesting an out-of-range MIME index yields an error."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -466,7 +475,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_data_error_propagation(self) -> None:
|
def test_data_error_propagation(self) -> None:
|
||||||
"""When data retrieval fails the client receives a t=R error code."""
|
"""When data retrieval fails the client receives a t=R error code."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -483,7 +492,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_data_eperm_error(self) -> None:
|
def test_data_eperm_error(self) -> None:
|
||||||
"""EPERM error is correctly forwarded to the client."""
|
"""EPERM error is correctly forwarded to the client."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -501,7 +510,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
chunk_limit = 3072
|
chunk_limit = 3072
|
||||||
big_payload = b'X' * (chunk_limit * 3) # 3 chunks expected
|
big_payload = b'X' * (chunk_limit * 3) # 3 chunks expected
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -519,7 +528,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
"""The client_id (i=…) set during registration is echoed in all replies."""
|
"""The client_id (i=…) set during registration is echoed in all replies."""
|
||||||
client_id = 42
|
client_id = 42
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain', client_id=client_id))
|
self._register_for_drops(screen, cap, wid, mimes='text/plain', client_id=client_id)
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, False, ['text/plain'])
|
dnd_test_fake_drop_event(wid, False, ['text/plain'])
|
||||||
raw = cap.consume(wid)
|
raw = cap.consume(wid)
|
||||||
@@ -530,7 +539,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_multiple_mimes_priority(self) -> None:
|
def test_multiple_mimes_priority(self) -> None:
|
||||||
"""The client can request data from any offered MIME type by index."""
|
"""The client can request data from any offered MIME type by index."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain text/uri-list'))
|
self._register_for_drops(screen, cap, wid, 'text/plain text/uri-list')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
# OS offers both types.
|
# OS offers both types.
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain', 'text/uri-list'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain', 'text/uri-list'])
|
||||||
@@ -569,7 +578,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_move_event_after_mime_change(self) -> None:
|
def test_move_event_after_mime_change(self) -> None:
|
||||||
"""When offered MIME list changes, the new list is included in the move event."""
|
"""When offered MIME list changes, the new list is included in the move event."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, False, ['text/plain'])
|
dnd_test_fake_drop_event(wid, False, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -585,7 +594,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_drop_event_has_uppercase_M(self) -> None:
|
def test_drop_event_has_uppercase_M(self) -> None:
|
||||||
"""A drop (not just a move) sends t=M (uppercase)."""
|
"""A drop (not just a move) sends t=M (uppercase)."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
events = self._get_events(cap, wid)
|
events = self._get_events(cap, wid)
|
||||||
@@ -595,7 +604,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_data_end_signal(self) -> None:
|
def test_data_end_signal(self) -> None:
|
||||||
"""The end-of-data signal is an empty payload escape code."""
|
"""The end-of-data signal is an empty payload escape code."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -613,7 +622,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_empty_data(self) -> None:
|
def test_empty_data(self) -> None:
|
||||||
"""Zero-byte payload is handled gracefully – only end signal is sent."""
|
"""Zero-byte payload is handled gracefully – only end signal is sent."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -633,7 +642,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
"""Register, drop, deliver text/uri-list data, discard move/drop events."""
|
"""Register, drop, deliver text/uri-list data, discard move/drop events."""
|
||||||
if mimes is None:
|
if mimes is None:
|
||||||
mimes = ['text/plain', 'text/uri-list']
|
mimes = ['text/plain', 'text/uri-list']
|
||||||
parse_bytes(screen, client_register('text/plain text/uri-list'))
|
self._register_for_drops(screen, cap, wid, 'text/plain text/uri-list')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, mimes)
|
dnd_test_fake_drop_event(wid, True, mimes)
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -728,7 +737,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
fpath = f.name
|
fpath = f.name
|
||||||
try:
|
try:
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain', 'text/uri-list'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain', 'text/uri-list'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -1826,7 +1835,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
"""x= key is echoed in data responses to identify which request is being answered."""
|
"""x= key is echoed in data responses to identify which request is being answered."""
|
||||||
payload_data = b'hello disambiguation'
|
payload_data = b'hello disambiguation'
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -1843,7 +1852,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_x_key_echoed_in_error_response(self) -> None:
|
def test_x_key_echoed_in_error_response(self) -> None:
|
||||||
"""x= key is echoed in error responses."""
|
"""x= key is echoed in error responses."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -1859,7 +1868,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_x_key_in_error_for_io_failure(self) -> None:
|
def test_x_key_in_error_for_io_failure(self) -> None:
|
||||||
"""x= key is echoed in I/O error responses."""
|
"""x= key is echoed in I/O error responses."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -1875,7 +1884,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_fifo_order_with_different_indices(self) -> None:
|
def test_fifo_order_with_different_indices(self) -> None:
|
||||||
"""Multiple requests with different x= values are served in FIFO order."""
|
"""Multiple requests with different x= values are served in FIFO order."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain text/html'))
|
self._register_for_drops(screen, cap, wid, 'text/plain text/html')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain', 'text/html'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain', 'text/html'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -1905,7 +1914,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_request_after_error_proceeds(self) -> None:
|
def test_request_after_error_proceeds(self) -> None:
|
||||||
"""After an error response, the next queued request is processed."""
|
"""After an error response, the next queued request is processed."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -1934,7 +1943,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_queue_overflow_returns_emfile(self) -> None:
|
def test_queue_overflow_returns_emfile(self) -> None:
|
||||||
"""Exceeding 128 queued requests returns EMFILE and ends the drop."""
|
"""Exceeding 128 queued requests returns EMFILE and ends the drop."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -2118,7 +2127,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_finish_after_queued_requests(self) -> None:
|
def test_finish_after_queued_requests(self) -> None:
|
||||||
"""A finish (empty t=r) after queued requests processes remaining then finishes."""
|
"""A finish (empty t=r) after queued requests processes remaining then finishes."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -2139,7 +2148,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
def test_multiple_sync_errors_processed_immediately(self) -> None:
|
def test_multiple_sync_errors_processed_immediately(self) -> None:
|
||||||
"""Multiple queued requests that all fail synchronously are processed immediately."""
|
"""Multiple queued requests that all fail synchronously are processed immediately."""
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
dnd_test_set_mouse_pos(wid, 0, 0, 0, 0)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
@@ -2164,7 +2173,7 @@ class TestDnDProtocol(BaseTest):
|
|||||||
"""Responses must not contain the old r= key."""
|
"""Responses must not contain the old r= key."""
|
||||||
payload_data = b'no r= key test'
|
payload_data = b'no r= key test'
|
||||||
with dnd_test_window() as (osw, wid, screen, cap):
|
with dnd_test_window() as (osw, wid, screen, cap):
|
||||||
parse_bytes(screen, client_register('text/plain'))
|
self._register_for_drops(screen, cap, wid, 'text/plain')
|
||||||
dnd_test_set_mouse_pos(wid, 2, 3, 16, 24)
|
dnd_test_set_mouse_pos(wid, 2, 3, 16, 24)
|
||||||
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
dnd_test_fake_drop_event(wid, True, ['text/plain'])
|
||||||
cap.consume(wid)
|
cap.consume(wid)
|
||||||
|
|||||||
Reference in New Issue
Block a user