diff --git a/docs/changelog.rst b/docs/changelog.rst index 2d1ebdc80..b762031fc 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -77,7 +77,7 @@ To update |kitty|, :doc:`follow the instructions `. - Workaround for bug in less that causes colors to reset at wrapped lines (:iss:`2381`) -- X11: Allow drag and drop of text/plain in addition to text/uri-list +- X11/Wayland: Allow drag and drop of text/plain in addition to text/uri-list (:iss:`2441`) - Dont strip :code:`&` and :code:`-` from the end of URLs (:iss:`2436`) diff --git a/glfw/backend_utils.c b/glfw/backend_utils.c index ad76bbb1c..87af03f5d 100644 --- a/glfw/backend_utils.c +++ b/glfw/backend_utils.c @@ -332,54 +332,3 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func displ } return read_ok; } - -// Splits and translates a text/uri-list into separate file paths -// NOTE: This function destroys the provided string -// -char** parseUriList(char* text, int* count) -{ - const char* prefix = "file://"; - char** paths = NULL; - char* line; - - *count = 0; - - while ((line = strtok(text, "\r\n"))) - { - text = NULL; - - if (line[0] == '#') - continue; - - if (strncmp(line, prefix, strlen(prefix)) == 0) - { - line += strlen(prefix); - // TODO: Validate hostname - while (*line != '/') - line++; - } - - (*count)++; - - char* path = calloc(strlen(line) + 1, 1); - paths = realloc(paths, *count * sizeof(char*)); - paths[*count - 1] = path; - - while (*line) - { - if (line[0] == '%' && line[1] && line[2]) - { - const char digits[3] = { line[1], line[2], '\0' }; - *path = strtol(digits, NULL, 16); - line += 2; - } - else - *path = *line; - - path++; - line++; - } - } - - return paths; -} diff --git a/glfw/backend_utils.h b/glfw/backend_utils.h index cb92dc7b3..7ae4f90eb 100644 --- a/glfw/backend_utils.h +++ b/glfw/backend_utils.h @@ -94,5 +94,4 @@ int pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func); unsigned dispatchTimers(EventLoopData *eld); void finalizePollData(EventLoopData *eld); bool initPollData(EventLoopData *eld, int display_fd); -char** parseUriList(char* text, int* count); void wakeupEventLoop(EventLoopData *eld); diff --git a/glfw/glfw3.h b/glfw/glfw3.h index 3822e3754..b60b2630d 100644 --- a/glfw/glfw3.h +++ b/glfw/glfw3.h @@ -1593,19 +1593,22 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); */ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); -/*! @brief The function pointer type for path drop callbacks. +/*! @brief The function pointer type for drag and drop callbacks. * - * This is the function pointer type for path drop callbacks. A path drop + * This is the function pointer type for drop callbacks. A drop * callback function has the following signature: * @code - * void function_name(GLFWwindow* window, int path_count, const char* paths[]) + * int function_name(GLFWwindow* window, const char* mime, const char* text) * @endcode * * @param[in] window The window that received the event. - * @param[in] path_count The number of dropped paths. - * @param[in] paths The UTF-8 encoded file and/or directory path names. + * @param[in] mime The UTF-8 encoded drop mime-type + * @param[in] data The dropped data or NULL for drag enter events + * @param[in] sz The size of the dropped data + * @return For drag events should return the priority for the specified mime type. A priority of zero + * or lower means the mime type is not accepted. Highest priority will be the finally accepted mime-type. * - * @pointer_lifetime The path array and its strings are valid until the + * @pointer_lifetime The text is valid until the * callback function returns. * * @sa @ref path_drop @@ -1615,7 +1618,7 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * * @ingroup input */ -typedef void (* GLFWdropfun)(GLFWwindow*,int,const char*[]); +typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); diff --git a/glfw/input.c b/glfw/input.c index e8fd26931..0ae33ec44 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -350,10 +350,11 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered) // Notifies shared code of files or directories dropped on a window // -void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths) +int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz) { if (window->callbacks.drop) - window->callbacks.drop((GLFWwindow*) window, count, paths); + return window->callbacks.drop((GLFWwindow*) window, mime, text, sz); + return 0; } // Notifies shared code of a joystick connection or disconnection diff --git a/glfw/internal.h b/glfw/internal.h index ceb84d930..ac2190fb0 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -759,7 +759,7 @@ void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int f void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorEnter(_GLFWwindow* window, bool entered); -void _glfwInputDrop(_GLFWwindow* window, int count, const char** names); +int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz); void _glfwInputJoystick(_GLFWjoystick* js, int event); void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value); void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value); diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 077dc52e6..5d0fe8c4b 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -852,7 +852,7 @@ void _glfwPlatformTerminate(void) wl_data_source_destroy(_glfw.wl.dataSourceForClipboard); for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) { if (_glfw.wl.dataOffers[doi].id) { - wl_data_offer_destroy(_glfw.wl.dataOffers[doi].id); + destroy_data_offer(&_glfw.wl.dataOffers[doi]); } } if (_glfw.wl.dataDevice) diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index ac529daa4..4d17ac39e 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -195,28 +195,19 @@ typedef enum _GLFWWaylandOfferType typedef struct _GLFWWaylandDataOffer { - struct wl_data_offer *id; - const char *mime; + void *id; _GLFWWaylandOfferType offer_type; size_t idx; - int is_self_offer; - int has_uri_list; + bool is_self_offer; + bool is_primary; + const char *plain_text_mime, *mime_for_drop; uint32_t source_actions; uint32_t dnd_action; struct wl_surface *surface; + const char **mimes; + size_t mimes_capacity, mimes_count; } _GLFWWaylandDataOffer; -typedef struct _GLFWWaylandPrimaryOffer -{ - struct zwp_primary_selection_offer_v1 *id; - const char *mime; - _GLFWWaylandOfferType offer_type; - size_t idx; - int is_self_offer; - int has_uri_list; - struct wl_surface *surface; -} _GLFWWaylandPrimaryOffer; - // Wayland-specific global data // typedef struct _GLFWlibraryWayland @@ -287,8 +278,6 @@ typedef struct _GLFWlibraryWayland size_t dataOffersCounter; _GLFWWaylandDataOffer dataOffers[8]; char* primarySelectionString; - size_t primarySelectionOffersCounter; - _GLFWWaylandPrimaryOffer primarySelectionOffers[8]; } _GLFWlibraryWayland; // Wayland-specific per-monitor data @@ -322,3 +311,4 @@ void _glfwSetupWaylandDataDevice(void); void _glfwSetupWaylandPrimarySelectionDevice(void); void animateCursorImage(id_type timer_id, void *data); struct wl_cursor* _glfwLoadCursor(GLFWCursorShape); +void destroy_data_offer(_GLFWWaylandDataOffer*); diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 10370e8ef..166683c2c 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -42,7 +42,7 @@ #include -#define URI_LIST_MIME "text/uri-list" + static bool checkScaleChange(_GLFWwindow* window) @@ -1509,10 +1509,11 @@ static void _glfwSendPrimarySelectionText(void *data UNUSED, struct zwp_primary_ send_text(_glfw.wl.primarySelectionString, fd); } -static char* read_offer_string(int data_pipe) { +static char* read_offer_string(int data_pipe, size_t *data_sz) { wl_display_flush(_glfw.wl.display); - size_t sz = 0, capacity = 0; + size_t capacity = 0; char *buf = NULL; + *data_sz = 0; struct pollfd fds; fds.fd = data_pipe; fds.events = POLLIN; @@ -1533,20 +1534,20 @@ static char* read_offer_string(int data_pipe) { if (!ret) { bail("Wayland: Failed to read clipboard data from pipe (timed out)"); } - if (capacity <= sz || capacity - sz <= 64) { + if (capacity <= *data_sz || capacity - *data_sz <= 64) { capacity += 4096; buf = realloc(buf, capacity); if (!buf) { bail("Wayland: Failed to allocate memory to read clipboard data"); } } - ret = read(data_pipe, buf + sz, capacity - sz - 1); + ret = read(data_pipe, buf + *data_sz, capacity - *data_sz - 1); if (ret == -1) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno)); } - if (ret == 0) { close(data_pipe); buf[sz] = 0; return buf; } - sz += ret; + if (ret == 0) { close(data_pipe); buf[*data_sz] = 0; return buf; } + *data_sz += ret; start = glfwGetTime(); } bail("Wayland: Failed to read clipboard data from pipe (timed out)"); @@ -1559,15 +1560,16 @@ static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1 if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL; zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]); close(pipefd[1]); - return read_offer_string(pipefd[0]); + size_t sz = 0; + return read_offer_string(pipefd[0], &sz); } -static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) { +static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime, size_t *sz) { int pipefd[2]; if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL; wl_data_offer_receive(data_offer, mime, pipefd[1]); close(pipefd[1]); - return read_offer_string(pipefd[0]); + return read_offer_string(pipefd[0], sz); } static void data_source_canceled(void *data UNUSED, struct wl_data_source *wl_data_source) { @@ -1596,20 +1598,23 @@ static const struct zwp_primary_selection_source_v1_listener primary_selection_s .cancelled = primary_selection_source_canceled, }; +void +destroy_data_offer(_GLFWWaylandDataOffer *offer) { + if (offer->id) { + if (offer->is_primary) zwp_primary_selection_offer_v1_destroy(offer->id); + else wl_data_offer_destroy(offer->id); + } + if (offer->mimes) { + for (size_t i = 0; i < offer->mimes_count; i++) free((char*)offer->mimes[i]); + free(offer->mimes); + } + memset(offer, 0, sizeof(_GLFWWaylandDataOffer)); +} + static void prune_unclaimed_data_offers(void) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) { - wl_data_offer_destroy(_glfw.wl.dataOffers[i].id); - memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0])); - } - } -} - -static void prune_unclaimed_primary_selection_offers(void) { - for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) { - if (_glfw.wl.primarySelectionOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) { - zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[i].id); - memset(_glfw.wl.primarySelectionOffers + i, 0, sizeof(_glfw.wl.primarySelectionOffers[0])); + destroy_data_offer(&_glfw.wl.dataOffers[i]); } } } @@ -1628,25 +1633,32 @@ static void mark_selection_offer(void *data UNUSED, struct wl_data_device *data_ static void mark_primary_selection_offer(void *data UNUSED, struct zwp_primary_selection_device_v1* primary_selection_device UNUSED, struct zwp_primary_selection_offer_v1 *primary_selection_offer) { - for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) { - if (_glfw.wl.primarySelectionOffers[i].id == primary_selection_offer) { - _glfw.wl.primarySelectionOffers[i].offer_type = PRIMARY_SELECTION; - } else if (_glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) { - _glfw.wl.primarySelectionOffers[i].offer_type = EXPIRED; // previous selection offer + for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { + if (_glfw.wl.dataOffers[i].id == primary_selection_offer) { + _glfw.wl.dataOffers[i].offer_type = PRIMARY_SELECTION; + } else if (_glfw.wl.dataOffers[i].offer_type == PRIMARY_SELECTION) { + _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer } } - prune_unclaimed_primary_selection_offers(); + prune_unclaimed_data_offers(); } -static void set_offer_mimetype(struct _GLFWWaylandDataOffer* offer, const char* mime) { - if (strcmp(mime, "text/plain;charset=utf-8") == 0) - offer->mime = "text/plain;charset=utf-8"; - else if (!offer->mime && strcmp(mime, "text/plain") == 0) - offer->mime = "text/plain"; - else if (strcmp(mime, clipboard_mime()) == 0) - offer->is_self_offer = 1; - else if (strcmp(mime, URI_LIST_MIME) == 0) - offer->has_uri_list = 1; +static void +set_offer_mimetype(_GLFWWaylandDataOffer* offer, const char* mime) { + if (strcmp(mime, "text/plain;charset=utf-8") == 0) { + offer->plain_text_mime = "text/plain;charset=utf-8"; + } else if (!offer->plain_text_mime && strcmp(mime, "text/plain")) { + offer->plain_text_mime = "text/plain"; + } + if (strcmp(mime, clipboard_mime()) == 0) { + offer->is_self_offer = true; + } + if (!offer->mimes || offer->mimes_count >= offer->mimes_capacity - 1) { + offer->mimes = realloc(offer->mimes, sizeof(char*) * (offer->mimes_capacity + 64)); + if (offer->mimes) offer->mimes_capacity += 64; + else return; + } + offer->mimes[offer->mimes_count++] = _glfw_strdup(mime); } static void handle_offer_mimetype(void *data UNUSED, struct wl_data_offer* id, const char *mime) { @@ -1659,9 +1671,9 @@ static void handle_offer_mimetype(void *data UNUSED, struct wl_data_offer* id, c } static void handle_primary_selection_offer_mimetype(void *data UNUSED, struct zwp_primary_selection_offer_v1* id, const char *mime) { - for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) { - if (_glfw.wl.primarySelectionOffers[i].id == id) { - set_offer_mimetype((struct _GLFWWaylandDataOffer*)&_glfw.wl.primarySelectionOffers[i], mime); + for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { + if (_glfw.wl.dataOffers[i].id == id) { + set_offer_mimetype((_GLFWWaylandDataOffer*)&_glfw.wl.dataOffers[i], mime); break; } } @@ -1696,7 +1708,8 @@ static const struct zwp_primary_selection_offer_v1_listener primary_selection_of .offer = handle_primary_selection_offer_mimetype, }; -static void handle_data_offer(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, struct wl_data_offer *id) { +static size_t +handle_data_offer_generic(void *id, bool is_primary) { size_t smallest_idx = SIZE_MAX, pos = 0; for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) { @@ -1704,47 +1717,48 @@ static void handle_data_offer(void *data UNUSED, struct wl_data_device *wl_data_ pos = i; } if (_glfw.wl.dataOffers[i].id == NULL) { - _glfw.wl.dataOffers[i].id = id; - _glfw.wl.dataOffers[i].idx = ++_glfw.wl.dataOffersCounter; + pos = i; goto end; } } - if (_glfw.wl.dataOffers[pos].id) wl_data_offer_destroy(_glfw.wl.dataOffers[pos].id); - memset(_glfw.wl.dataOffers + pos, 0, sizeof(_glfw.wl.dataOffers[0])); - _glfw.wl.dataOffers[pos].id = id; - _glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter; + if (_glfw.wl.dataOffers[pos].id) destroy_data_offer(&_glfw.wl.dataOffers[pos]); end: + _glfw.wl.dataOffers[pos].id = id; + _glfw.wl.dataOffers[pos].is_primary = is_primary; + _glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter; + return pos; +} + +static void handle_data_offer(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, struct wl_data_offer *id) { + handle_data_offer_generic(id, false); wl_data_offer_add_listener(id, &data_offer_listener, NULL); } static void handle_primary_selection_offer(void *data UNUSED, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1 UNUSED, struct zwp_primary_selection_offer_v1 *id) { - size_t smallest_idx = SIZE_MAX, pos = 0; - for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) { - if (_glfw.wl.primarySelectionOffers[i].idx && _glfw.wl.primarySelectionOffers[i].idx < smallest_idx) { - smallest_idx = _glfw.wl.primarySelectionOffers[i].idx; - pos = i; - } - if (_glfw.wl.primarySelectionOffers[i].id == NULL) { - _glfw.wl.primarySelectionOffers[i].id = id; - _glfw.wl.primarySelectionOffers[i].idx = ++_glfw.wl.primarySelectionOffersCounter; - goto end; - } - } - if (_glfw.wl.primarySelectionOffers[pos].id) zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[pos].id); - memset(_glfw.wl.primarySelectionOffers + pos, 0, sizeof(_glfw.wl.primarySelectionOffers[0])); - _glfw.wl.primarySelectionOffers[pos].id = id; - _glfw.wl.primarySelectionOffers[pos].idx = ++_glfw.wl.primarySelectionOffersCounter; -end: + handle_data_offer_generic(id, true); zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL); } static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t serial, struct wl_surface *surface, wl_fixed_t x UNUSED, wl_fixed_t y UNUSED, struct wl_data_offer *id) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id == id) { - _glfw.wl.dataOffers[i].offer_type = DRAG_AND_DROP; - _glfw.wl.dataOffers[i].surface = surface; - const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL; - wl_data_offer_accept(id, serial, mime); + _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i; + if (d->id == id) { + d->offer_type = DRAG_AND_DROP; + d->surface = surface; + _GLFWwindow* window = _glfw.windowListHead; + int format_priority = 0; + while (window) + { + if (window->wl.surface == surface) { + for (size_t x = 0; x < d->mimes_count; x++) { + int prio = _glfwInputDrop(window, d->mimes[x], NULL, 0); + if (prio > format_priority) d->mime_for_drop = d->mimes[x]; + } + break; + } + window = window->next; + } + wl_data_offer_accept(id, serial, d->mime_for_drop); } else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer } @@ -1755,41 +1769,34 @@ static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device static void drag_leave(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { - wl_data_offer_destroy(_glfw.wl.dataOffers[i].id); - memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0])); + destroy_data_offer(&_glfw.wl.dataOffers[i]); } } } static void drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { - char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME); - if (uri_list) { + if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP && _glfw.wl.dataOffers[i].mime_for_drop) { + size_t sz = 0; + char *data = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime_for_drop, &sz); + if (data) { // We dont do finish as this requires version 3 for wl_data_device_manager // which then requires more work with calling set_actions for drag and drop to function // wl_data_offer_finish(_glfw.wl.dataOffers[i].id); - int count; - char** paths = parseUriList(uri_list, &count); _GLFWwindow* window = _glfw.windowListHead; while (window) { if (window->wl.surface == _glfw.wl.dataOffers[i].surface) { - _glfwInputDrop(window, count, (const char**) paths); + _glfwInputDrop(window, _glfw.wl.dataOffers[i].mime_for_drop, data, sz); break; } window = window->next; } - - for (int k = 0; k < count; k++) - free(paths[k]); - free(paths); - free(uri_list); + free(data); } - wl_data_offer_destroy(_glfw.wl.dataOffers[i].id); - memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0])); + destroy_data_offer(&_glfw.wl.dataOffers[i]); break; } } @@ -1894,10 +1901,12 @@ void _glfwPlatformSetClipboardString(const char* string) const char* _glfwPlatformGetClipboardString(void) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == CLIPBOARD) { - if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString; + _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i; + if (d->id && d->offer_type == CLIPBOARD && d->plain_text_mime) { + if (d->is_self_offer) return _glfw.wl.clipboardString; free(_glfw.wl.pasteString); - _glfw.wl.pasteString = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime); + size_t sz = 0; + _glfw.wl.pasteString = read_data_offer(d->id, d->plain_text_mime, &sz); return _glfw.wl.pasteString; } } @@ -1944,13 +1953,14 @@ const char* _glfwPlatformGetPrimarySelectionString(void) { if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString; - for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) { - if (_glfw.wl.primarySelectionOffers[i].id && _glfw.wl.primarySelectionOffers[i].mime && _glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) { - if (_glfw.wl.primarySelectionOffers[i].is_self_offer) { + for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { + _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i; + if (d->id && d->is_primary && d->offer_type == PRIMARY_SELECTION && d->plain_text_mime) { + if (d->is_self_offer) { return _glfw.wl.primarySelectionString; } free(_glfw.wl.pasteString); - _glfw.wl.pasteString = read_primary_selection_offer(_glfw.wl.primarySelectionOffers[i].id, _glfw.wl.primarySelectionOffers[i].mime); + _glfw.wl.pasteString = read_primary_selection_offer(d->id, d->plain_text_mime); return _glfw.wl.pasteString; } } diff --git a/glfw/x11_init.c b/glfw/x11_init.c index f83eda9a1..2408f62d0 100644 --- a/glfw/x11_init.c +++ b/glfw/x11_init.c @@ -384,9 +384,6 @@ static bool initExtensions(void) _glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False); _glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False); _glfw.x11.XdndTypeList = XInternAtom(_glfw.x11.display, "XdndTypeList", False); - _glfw.x11.text_uri_list = XInternAtom(_glfw.x11.display, "text/uri-list", False); - _glfw.x11.text_plain = XInternAtom(_glfw.x11.display, "text/plain", False); - _glfw.x11.text_plain_utf8 = XInternAtom(_glfw.x11.display, "text/plain;charset=utf-8", False); // ICCCM, EWMH and Motif window property atoms // These can be set safely even without WM support diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index b39eea612..6f59101bc 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -266,9 +266,6 @@ typedef struct _GLFWlibraryX11 Atom XdndFinished; Atom XdndSelection; Atom XdndTypeList; - Atom text_uri_list; - Atom text_plain; - Atom text_plain_utf8; // Selection (clipboard) atoms Atom TARGETS; @@ -329,7 +326,8 @@ typedef struct _GLFWlibraryX11 struct { int version; Window source; - Atom format; + char format[128]; + int format_priority; } xdnd; struct { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index 2999e45a6..7a9efbddb 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -1454,7 +1454,8 @@ static void processEvent(XEvent *event) _glfw.x11.xdnd.source = event->xclient.data.l[0]; _glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24; - _glfw.x11.xdnd.format = None; + memset(_glfw.x11.xdnd.format, 0, sizeof(_glfw.x11.xdnd.format)); + _glfw.x11.xdnd.format_priority = 0; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; @@ -1471,18 +1472,22 @@ static void processEvent(XEvent *event) count = 3; formats = (Atom*) event->xclient.data.l + 2; } + char **atom_names = calloc(count, sizeof(char**)); + if (atom_names) { + XGetAtomNames(_glfw.x11.display, formats, count, atom_names); - for (i = 0; i < count; i++) - { - if (formats[i] == _glfw.x11.text_uri_list) + for (i = 0; i < count; i++) { - _glfw.x11.xdnd.format = _glfw.x11.text_uri_list; - break; - } else if (formats[i] == _glfw.x11.text_plain_utf8 && (_glfw.x11.xdnd.format == None || _glfw.x11.xdnd.format == _glfw.x11.text_plain)) { - _glfw.x11.xdnd.format = _glfw.x11.text_plain_utf8; - } else if (formats[i] == _glfw.x11.text_plain && _glfw.x11.xdnd.format == None) { - _glfw.x11.xdnd.format = _glfw.x11.text_plain; + if (atom_names[i]) { + int prio = _glfwInputDrop(window, atom_names[i], NULL, 0); + if (prio > _glfw.x11.xdnd.format_priority) { + _glfw.x11.xdnd.format_priority = prio; + strncpy(_glfw.x11.xdnd.format, atom_names[i], arraysz(_glfw.x11.xdnd.format) - 1); + } + XFree(atom_names[i]); + } } + free(atom_names); } if (list && formats) @@ -1496,7 +1501,7 @@ static void processEvent(XEvent *event) if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; - if (_glfw.x11.xdnd.format) + if (_glfw.x11.xdnd.format_priority > 0) { if (_glfw.x11.xdnd.version >= 1) time = event->xclient.data.l[2]; @@ -1504,7 +1509,7 @@ static void processEvent(XEvent *event) // Request the chosen format from the source window XConvertSelection(_glfw.x11.display, _glfw.x11.XdndSelection, - _glfw.x11.xdnd.format, + XInternAtom(_glfw.x11.display, _glfw.x11.xdnd.format, 0), _glfw.x11.XdndSelection, window->x11.handle, time); @@ -1552,7 +1557,7 @@ static void processEvent(XEvent *event) reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[3] = 0; - if (_glfw.x11.xdnd.format) + if (_glfw.x11.xdnd.format_priority > 0) { // Reply that we are ready to copy the dragged data reply.xclient.data.l[1] = 1; // Accept with no rectangle @@ -1582,14 +1587,7 @@ static void processEvent(XEvent *event) if (result) { - int i, count; - char** paths = parseUriList(data, &count); - - _glfwInputDrop(window, count, (const char**) paths); - - for (i = 0; i < count; i++) - free(paths[i]); - free(paths); + _glfwInputDrop(window, _glfw.x11.xdnd.format, data, result); } if (data) diff --git a/kitty/boss.py b/kitty/boss.py index 448c77ed7..4fb1ee045 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -46,8 +46,9 @@ from .tabs import ( from .typing import PopenType, TypedDict from .utils import ( func_name, get_editor, get_primary_selection, is_path_in_temp_dir, - log_error, open_url, parse_address_spec, remove_socket_file, safe_print, - set_primary_selection, single_instance, startup_notification_handler + log_error, open_url, parse_address_spec, parse_uri_list, + remove_socket_file, safe_print, set_primary_selection, single_instance, + startup_notification_handler ) from .window import MatchPatternType, Window @@ -686,12 +687,15 @@ class Boss: if tm is not None: tm.update_tab_bar_data() - def on_drop(self, os_window_id: int, strings: Iterable[str]) -> None: + def on_drop(self, os_window_id: int, mime: str, data: bytes) -> None: tm = self.os_window_map.get(os_window_id) if tm is not None: w = tm.active_window if w is not None: - w.paste('\n'.join(strings)) + text = data.decode('utf-8', 'replace') + if mime == 'text/uri-list': + text = '\n'.join(parse_uri_list(text)) + w.paste(text) def on_os_window_closed(self, os_window_id: int, viewport_width: int, viewport_height: int) -> None: self.cached_values['window-size'] = viewport_width, viewport_height diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index e43660099..1159529bf 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -137,12 +137,12 @@ load_glfw(const char* path) { *(void **) (&glfwSetWindowSizeLimits_impl) = dlsym(handle, "glfwSetWindowSizeLimits"); if (glfwSetWindowSizeLimits_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeLimits with error: %s", dlerror()); - *(void **) (&glfwSetWindowAspectRatio_impl) = dlsym(handle, "glfwSetWindowAspectRatio"); - if (glfwSetWindowAspectRatio_impl == NULL) fail("Failed to load glfw function glfwSetWindowAspectRatio with error: %s", dlerror()); - *(void **) (&glfwSetWindowSizeIncrements_impl) = dlsym(handle, "glfwSetWindowSizeIncrements"); if (glfwSetWindowSizeIncrements_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeIncrements with error: %s", dlerror()); + *(void **) (&glfwSetWindowAspectRatio_impl) = dlsym(handle, "glfwSetWindowAspectRatio"); + if (glfwSetWindowAspectRatio_impl == NULL) fail("Failed to load glfw function glfwSetWindowAspectRatio with error: %s", dlerror()); + *(void **) (&glfwSetWindowSize_impl) = dlsym(handle, "glfwSetWindowSize"); if (glfwSetWindowSize_impl == NULL) fail("Failed to load glfw function glfwSetWindowSize with error: %s", dlerror()); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index 359ae3fc9..ac4629f3a 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1353,19 +1353,22 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int); */ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); -/*! @brief The function pointer type for path drop callbacks. +/*! @brief The function pointer type for drag and drop callbacks. * - * This is the function pointer type for path drop callbacks. A path drop + * This is the function pointer type for drop callbacks. A drop * callback function has the following signature: * @code - * void function_name(GLFWwindow* window, int path_count, const char* paths[]) + * int function_name(GLFWwindow* window, const char* mime, const char* text) * @endcode * * @param[in] window The window that received the event. - * @param[in] path_count The number of dropped paths. - * @param[in] paths The UTF-8 encoded file and/or directory path names. + * @param[in] mime The UTF-8 encoded drop mime-type + * @param[in] data The dropped data or NULL for drag enter events + * @param[in] sz The size of the dropped data + * @return For drag events should return the priority for the specified mime type. A priority of zero + * or lower means the mime type is not accepted. Highest priority will be the finally accepted mime-type. * - * @pointer_lifetime The path array and its strings are valid until the + * @pointer_lifetime The text is valid until the * callback function returns. * * @sa @ref path_drop @@ -1375,7 +1378,7 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); * * @ingroup input */ -typedef void (* GLFWdropfun)(GLFWwindow*,int,const char*[]); +typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); @@ -1747,14 +1750,14 @@ typedef void (*glfwSetWindowSizeLimits_func)(GLFWwindow*, int, int, int, int); glfwSetWindowSizeLimits_func glfwSetWindowSizeLimits_impl; #define glfwSetWindowSizeLimits glfwSetWindowSizeLimits_impl -typedef void (*glfwSetWindowAspectRatio_func)(GLFWwindow*, int, int); -glfwSetWindowAspectRatio_func glfwSetWindowAspectRatio_impl; -#define glfwSetWindowAspectRatio glfwSetWindowAspectRatio_impl - typedef void (*glfwSetWindowSizeIncrements_func)(GLFWwindow*, int, int); glfwSetWindowSizeIncrements_func glfwSetWindowSizeIncrements_impl; #define glfwSetWindowSizeIncrements glfwSetWindowSizeIncrements_impl +typedef void (*glfwSetWindowAspectRatio_func)(GLFWwindow*, int, int); +glfwSetWindowAspectRatio_func glfwSetWindowAspectRatio_impl; +#define glfwSetWindowAspectRatio glfwSetWindowAspectRatio_impl + typedef void (*glfwSetWindowSize_func)(GLFWwindow*, int, int); glfwSetWindowSize_func glfwSetWindowSize_impl; #define glfwSetWindowSize glfwSetWindowSize_impl diff --git a/kitty/glfw.c b/kitty/glfw.c index 83d36144d..37cc1651d 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -320,17 +320,26 @@ window_focus_callback(GLFWwindow *w, int focused) { global_state.callback_os_window = NULL; } -static void -drop_callback(GLFWwindow *w, int count, const char **strings) { - if (!set_callback_window(w)) return; - PyObject *s = PyTuple_New(count); - if (s) { - for (int i = 0; i < count; i++) PyTuple_SET_ITEM(s, i, PyUnicode_FromString(strings[i])); - WINDOW_CALLBACK(on_drop, "O", s); - Py_CLEAR(s); - request_tick_callback(); +static int +drop_callback(GLFWwindow *w, const char *mime, const char *data, size_t sz) { + if (!set_callback_window(w)) return 0; + if (!data) { + if (strcmp(mime, "text/uri-list") == 0) return 3; + if (strcmp(mime, "text/plain;charset=utf-8") == 0) return 2; + if (strcmp(mime, "text/plain") == 0) return 1; + return 0; } + WINDOW_CALLBACK(on_drop, "sy#", mime, data, (int)sz); + request_tick_callback(); + /* PyObject *s = PyTuple_New(count); */ + /* if (s) { */ + /* for (int i = 0; i < count; i++) PyTuple_SET_ITEM(s, i, PyUnicode_FromString(strings[i])); */ + /* WINDOW_CALLBACK(on_drop, "O", s); */ + /* Py_CLEAR(s); */ + /* request_tick_callback(); */ + /* } */ global_state.callback_os_window = NULL; + return 0; } // }}} diff --git a/kitty/utils.py b/kitty/utils.py index eef9197a9..2a8e2447a 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -532,3 +532,21 @@ def read_shell_environment(opts: Optional[Options] = None) -> Dict[str, str]: else: log_error('Failed to run shell to read its environment') return ans + + +def parse_uri_list(text: str) -> Generator[str, None, None]: + ' Get paths from file:// URLs ' + from urllib.parse import urlparse, unquote + for line in text.splitlines(): + if not line or line.startswith('#'): + continue + if not line.startswith('file://'): + yield line + continue + try: + purl = urlparse(line, allow_fragments=False) + except Exception: + yield line + continue + if purl.path: + yield unquote(purl.path)