diff --git a/kitty/dnd.c b/kitty/dnd.c index c1bf7d7f6..a2883aa9a 100644 --- a/kitty/dnd.c +++ b/kitty/dnd.c @@ -170,6 +170,11 @@ dnd_set_test_write_func(PyObject *func, size_t mime_list_size_cap, size_t presen REMOTE_DRAG_LIMIT = remote_drag_limit ? remote_drag_limit : DEFAULT_REMOTE_DRAG_LIMIT; } +bool +dnd_is_test_mode(void) { + return g_dnd_test_write_func != NULL; +} + static int string_arrays_cmp(const char **a, size_t an, const char **b, size_t bn) { if (an != bn) return (int)an - (int)bn; @@ -1438,7 +1443,7 @@ drag_notify(Window *w, DragNotifyType type) { default: sz += snprintf(buf + sz, sizeof(buf) - sz, "o=1"); break; } break; - case DRAG_NOTIFY_DROPPED: break; + case DRAG_NOTIFY_DROPPED: ds.state = DRAG_SOURCE_DROPPED; break; case DRAG_NOTIFY_FINISHED: sz += snprintf(buf + sz, sizeof(buf) - sz, "y=%d", global_state.drag_source.was_canceled ? 1 : 0); break; } @@ -1780,9 +1785,9 @@ toplevel_data_for_drag( bool has_more, const uint8_t *payload, size_t payload_sz ) { if (!mi.remote_items) { - mi.remote_items = calloc(ds.num_mimes, sizeof(mi.remote_items[0])); + mi.remote_items = calloc(mi.num_uris, sizeof(mi.remote_items[0])); if (!mi.remote_items) abrt(ENOMEM); - mi.num_remote_items = ds.num_mimes; + mi.num_remote_items = mi.num_uris; } if (!mi.base_dir_for_remote_items) { int fd; @@ -1796,7 +1801,7 @@ toplevel_data_for_drag( ri->started = true; ri->type = item_type; base64_init_stream_decoder(&ri->base64_state); - if (uri_item_idx > mi.num_uris) abrt(EINVAL); + if (uri_item_idx >= mi.num_uris) abrt(EINVAL); const char *uri = mi.uri_list[uri_item_idx]; char *fname = sanitized_filename_from_url(uri); if (!fname) abrt(EINVAL); @@ -1860,7 +1865,7 @@ subdir_data_for_drag( parent->fd_plus_one = fd + 1; } } - if (entry_num > parent->children_sz) abrt(EINVAL); + if (entry_num >= parent->children_sz) abrt(EINVAL); DragRemoteItem *ri = parent->children + entry_num; if (!ri->started) { ri->started = true; diff --git a/kitty/dnd.h b/kitty/dnd.h index bcf1a0210..50c5db252 100644 --- a/kitty/dnd.h +++ b/kitty/dnd.h @@ -22,6 +22,7 @@ size_t drop_update_mimes(Window *w, const char **allowed_mimes, size_t allowed_m void drop_dispatch_data(Window *w, const char *mime_type, const char *data, ssize_t sz); void drop_finish(Window *w); void dnd_set_test_write_func(PyObject*, size_t, size_t, size_t); +bool dnd_is_test_mode(void); typedef enum { DRAG_NOTIFY_ACCEPTED, DRAG_NOTIFY_ACTION_CHANGED, DRAG_NOTIFY_DROPPED, DRAG_NOTIFY_FINISHED } DragNotifyType; diff --git a/kitty/glfw.c b/kitty/glfw.c index 3ba1add98..fad111299 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -1052,6 +1052,34 @@ dnd_test_fake_drop_data(PyObject *self UNUSED, PyObject *args) { Py_RETURN_NONE; } +static PyObject * +dnd_test_force_drag_dropped(PyObject *self UNUSED, PyObject *args) { + // Force the drag source state to DROPPED for testing purposes. + // This simulates what would happen after start_window_drag() succeeds + // and the drop target receives the data. + unsigned long long window_id; + if (!PyArg_ParseTuple(args, "K", &window_id)) return NULL; + Window *w = window_for_window_id((id_type)window_id); + if (!w) { PyErr_SetString(PyExc_ValueError, "Window not found"); return NULL; } + if (w->drag_source.state != DRAG_SOURCE_BEING_BUILT) { + PyErr_SetString(PyExc_ValueError, "Drag source state is not BEING_BUILT"); + return NULL; + } + // Simulate what drag_start does on success, without calling start_window_drag + for (size_t i = 0; i < w->drag_source.num_mimes; i++) { + free(w->drag_source.items[i].optional_data); + w->drag_source.items[i].optional_data = NULL; + w->drag_source.items[i].data_size = 0; + w->drag_source.items[i].data_capacity = 0; + w->drag_source.items[i].data_decode_initialized = false; + } + for (size_t i = 0; i < arraysz(w->drag_source.images); i++) { + if (w->drag_source.images[i].data) free(w->drag_source.images[i].data); + zero_at_ptr(w->drag_source.images + i); + } + w->drag_source.state = DRAG_SOURCE_DROPPED; + Py_RETURN_NONE; +} // }}} static void @@ -1150,6 +1178,7 @@ drag_source_callback(GLFWwindow *window UNUSED, GLFWDragEvent *ev) { int notify_drag_data_ready(id_type os_window_id, const char *mime_type) { + if (dnd_is_test_mode()) return 0; // In test mode, always succeed OSWindow *w = os_window_for_id(os_window_id); GLFWDragSourceItem item = {.mime_type = mime_type}; if (w && w->handle) return glfwStartDrag(w->handle, &item, 1, NULL, -1, false); @@ -3363,6 +3392,7 @@ static PyMethodDef module_methods[] = { METHODB(dnd_test_set_mouse_pos, METH_VARARGS), METHODB(dnd_test_fake_drop_event, METH_VARARGS), METHODB(dnd_test_fake_drop_data, METH_VARARGS), + METHODB(dnd_test_force_drag_dropped, METH_VARARGS), {NULL, NULL, 0, NULL} /* Sentinel */ };