More work on the dnd kitten

This commit is contained in:
Kovid Goyal 2026-05-01 12:37:35 +05:30
parent 614a32c790
commit 197115cb95
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
8 changed files with 77 additions and 14 deletions

View file

@ -33,7 +33,8 @@ func (dnd *dnd) on_potential_drag_start(cell_x, cell_y int) (err error) {
s := dnd.drag_sources[mt]
if len(s.data) > 0 && len(s.data)+total_preloaded_data_sz < 64*1024*1024 {
total_preloaded_data_sz += len(s.data)
dnd.lp.QueueDnDData(DC{Type: 'p', X: i, Operation: actions, Payload: utils.UnsafeStringToBytes(strings.Join(mimes, " "))})
dnd.lp.QueueDnDData(DC{Type: 'p', X: i, Operation: actions, Payload: s.data})
dnd.lp.QueueDnDData(DC{Type: 'p', X: i, Operation: actions})
}
}
// TODO: set the drag image
@ -56,3 +57,16 @@ func (dnd *dnd) on_drag_error(cmd DC) (err error) {
}
return
}
func (dnd *dnd) reset_drag() {
dnd.drag_status = drag_status{}
}
func (dnd *dnd) on_drag_event(x, y int) (err error) {
switch x {
case 4:
dnd.reset_drag()
return dnd.render_screen()
}
return
}

View file

@ -220,6 +220,8 @@ func (dnd *dnd) run_loop() (err error) {
dnd.send_test_response(utils.IfElse(dnd.drop_status.is_remote_client, "True", "False"))
case "DROP_URI_LIST":
dnd.send_test_response(strings.Join(dnd.drop_status.uri_list, "|"))
case "DRAG_ACTIVE":
dnd.send_test_response(utils.IfElse(dnd.drag_status.active, "DRAG_ACTIVE", "DRAG_INACTIVE"))
default:
dnd.send_test_response("UNKNOWN TEST COMMAND: " + string(cmd.Payload))
}
@ -246,6 +248,8 @@ func (dnd *dnd) run_loop() (err error) {
return dnd.on_potential_drag_start(cmd.X, cmd.Y)
case 'E':
return dnd.on_drag_error(cmd)
case 'e':
return dnd.on_drag_event(cmd.X, cmd.Y)
}
return nil
}

View file

@ -1424,7 +1424,7 @@ drag_start(Window *w) {
if (img.sz != (size_t)img.width * (size_t)img.height * 4u) abrt(EINVAL);
}
}
int err = start_window_drag(w);
int err = start_window_drag(w, dnd_is_test_mode());
if (err != 0) {
abrt(err);
} else {
@ -2162,6 +2162,18 @@ dnd_test_request_drag_data(PyObject *self UNUSED, PyObject *args) {
Py_RETURN_NONE;
}
static PyObject *
dnd_test_drag_finish(PyObject *self UNUSED, PyObject *args) {
unsigned long long window_id; int canceled_by_user; int errcode = 0;
if (!PyArg_ParseTuple(args, "Kp|i", &window_id, &canceled_by_user, &errcode)) return NULL;
Window *w = window_for_window_id((id_type)window_id);
if (!w) { PyErr_SetString(PyExc_ValueError, "Window not found"); return NULL; }
global_state.drag_source.was_canceled = canceled_by_user;
if (!errcode) drag_notify(w, DRAG_NOTIFY_FINISHED);
cancel_drag(w, errcode);
Py_RETURN_NONE;
}
static PyObject *
dnd_test_drag_notify(PyObject *self UNUSED, PyObject *args) {
// Call drag_notify with a specific type for testing the protocol output.
@ -2173,7 +2185,7 @@ dnd_test_drag_notify(PyObject *self UNUSED, PyObject *args) {
int type;
const char *accepted_mime = NULL;
int action = 0, was_canceled = 0;
if (!PyArg_ParseTuple(args, "Ki|sii", &window_id, &type, &accepted_mime, &action, &was_canceled)) return NULL;
if (!PyArg_ParseTuple(args, "Ki|sip", &window_id, &type, &accepted_mime, &action, &was_canceled)) return NULL;
Window *w = window_for_window_id((id_type)window_id);
if (!w) { PyErr_SetString(PyExc_ValueError, "Window not found"); return NULL; }
if (type < 0 || type > 3) { PyErr_SetString(PyExc_ValueError, "Invalid type"); return NULL; }
@ -2229,6 +2241,9 @@ dnd_test_probe_state(PyObject *self UNUSED, PyObject *args) {
if (strcmp(q, "drop_getting_data_for_mime") == 0) {
return PyUnicode_FromString(w->drop.getting_data_for_mime ? w->drop.getting_data_for_mime : "");
}
if (strcmp(q, "can_offer") == 0) {
return Py_NewRef(w->drag_source.can_offer ? Py_True : Py_False);
}
if (strcmp(q, "drag_operations") == 0) {
return PyLong_FromLong((long)w->drag_source.allowed_operations);
}
@ -2262,6 +2277,7 @@ static PyMethodDef dnd_methods[] = {
METHODB(dnd_test_force_drag_dropped, METH_VARARGS),
METHODB(dnd_test_request_drag_data, METH_VARARGS),
METHODB(dnd_test_drag_notify, METH_VARARGS),
METHODB(dnd_test_drag_finish, METH_VARARGS),
METHODB(dnd_test_probe_state, METH_VARARGS),
{NULL, NULL, 0, NULL}
};

View file

@ -3063,12 +3063,14 @@ change_drag_thumbnail(PyObject *self UNUSED, PyObject *args) {
}
int
start_window_drag(Window *w) {
start_window_drag(Window *w, bool in_test_mode) {
OSWindow *osw = os_window_for_kitty_window(w->id);
if (!osw || !osw->handle) return EINVAL;
// Deny the drag start if mouse is not still pressed and over the originating window
if (!osw->mouse_button_pressed[GLFW_MOUSE_BUTTON_LEFT]) return EPERM;
if (global_state.mouse_hover_in_window != global_state.drag_source.from_window) return EPERM;
if (!osw || (!in_test_mode && !osw->handle)) return EINVAL;
if (!in_test_mode) {
// Deny the drag start if mouse is not still pressed and over the originating window
if (!osw->mouse_button_pressed[GLFW_MOUSE_BUTTON_LEFT]) return EPERM;
if (global_state.mouse_hover_in_window != global_state.drag_source.from_window) return EPERM;
}
RAII_ALLOC(GLFWDragSourceItem, items, calloc(w->drag_source.num_mimes, sizeof(GLFWDragSourceItem)));
if (!items) return ENOMEM;
for (size_t i = 0; i < w->drag_source.num_mimes; i++) {
@ -3097,6 +3099,7 @@ start_window_drag(Window *w) {
global_state.drag_source.from_window = w->id;
global_state.drag_source.from_os_window = osw->id;
global_state.drag_source.thumbnail_idx = w->drag_source.img_idx < num_images ? (int)w->drag_source.img_idx : -1;
if (in_test_mode) return 0;
int ret = glfwStartDrag(osw->handle, items, w->drag_source.num_mimes, thumbnail.pixels ? &thumbnail : NULL, w->drag_source.allowed_operations, true);
if (ret != 0) free_drag_source();
return ret;

View file

@ -639,6 +639,6 @@ void register_mimes_for_drop(OSWindow *w, const char **mimes, size_t sz);
void request_drop_data(OSWindow *w, id_type wid, const char* mime);
void cancel_current_drag_source(void);
bool change_drag_image(int idx);
int start_window_drag(Window *w);
int start_window_drag(Window *w, bool in_test_mode);
int notify_drag_data_ready(id_type os_window_id, const char *mime_type);
BackgroundImage* background_image_for_os_window(OSWindow *w);

View file

@ -337,8 +337,10 @@ class PTY:
def __init__(
self, argv=None, rows=25, columns=80, scrollback=100, cell_width=10, cell_height=20,
cwd=None, env=None, stdin_fd=None, stdout_fd=None, needs_da1=True, window_id=0,
log_data_flow=False,
):
self.is_child = False
self.log_data_flow = log_data_flow
if isinstance(argv, str):
argv = shlex.split(argv)
self.write_buf = b''
@ -423,6 +425,8 @@ class PTY:
def write_to_child(self, data, flush=False):
if isinstance(data, str):
data = data.encode('utf-8')
if self.log_data_flow:
print('t -> c:', data)
self.write_buf += data
if flush:
self.process_input_from_child(0)
@ -446,6 +450,8 @@ class PTY:
data = os.read(self.master_fd, io.DEFAULT_BUFFER_SIZE)
bytes_read += len(data)
self.received_bytes += data
if self.log_data_flow:
print('c -> t:', data)
parse_bytes(self.screen, data)
return bytes_read

View file

@ -21,6 +21,7 @@ from kitty.fast_data_types import (
dnd_set_test_write_func,
dnd_test_cleanup_fake_window,
dnd_test_create_fake_window,
dnd_test_drag_finish,
dnd_test_fake_drop_data,
dnd_test_fake_drop_event,
dnd_test_probe_state,
@ -328,10 +329,29 @@ class TestDnDKitten(BaseTest):
f.write(os.urandom(1113))
create_fs(self.src_data_dir)
self.finish_setup(cli_args=(f'--drag=image/png:{img_drag_path}', self.src_data_dir)) # )))
with self.subTest(remote=False):
self.dnd_kitten_drag(False, img_drag_path)
self.dnd_kitten_drag(False, img_drag_path)
self.exit_kitten()
def dnd_kitten_drag(self, remote_client, img_drop_path):
# self.pty.log_data_flow = True
copy, move = self.get_button_geometry()
dnd_test_start_drag_offer(self.capture.window_id, 1, 1)
self.wait_for_state('drag_operations', 3)
self.wait_for_state('can_offer', True)
self.wait_for_state('drag_operations', 0)
def wait_for_drag_active(active=True):
self.send_dnd_command_to_kitten('DRAG_ACTIVE')
self.wait_for_responses('DRAG_ACTIVE' if active else 'DRAG_INACTIVE')
def start_drag(x, y, expected):
dnd_test_start_drag_offer(self.capture.window_id, x, y)
wait_for_drag_active()
self.wait_for_state('drag_operations', expected)
def end_drag(canceled=True):
dnd_test_drag_finish(self.capture.window_id, canceled)
wait_for_drag_active(False)
self.wait_for_state('drag_operations', 0)
start_drag(1, 1, 3)
self.assertEqual(set(self.probe_state('drag_mimes')), {'image/png', 'text/uri-list'})
end_drag()
start_drag(copy[0] + 1, copy[1] + 1, 1)
end_drag()
start_drag(move[0] + 1, move[1] + 1, 2)
end_drag()

View file

@ -672,7 +672,7 @@ func (self *Loop) DrawSizedText(text string, spec SizedText) {
func (self *Loop) QueueDnDData(cmd DndCommand) IdType {
b := strings.Builder{}
b.Grow(64)
as_base64 := cmd.Type == 'r'
as_base64 := cmd.Type == 'r' || cmd.Type == 'p'
fmt.Fprintf(&b, "\x1b]%d;t=%c", kitty.DndCode, cmd.Type)
add := func(key byte, val int) {
if val != 0 {