Implement drag and drop of text/plain for Wayland as well

This commit is contained in:
Kovid Goyal 2020-03-19 13:00:52 +05:30
parent e827e6fa21
commit 2458c3a7c6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
17 changed files with 205 additions and 226 deletions

View File

@ -77,7 +77,7 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- Workaround for bug in less that causes colors to reset at wrapped lines - Workaround for bug in less that causes colors to reset at wrapped lines
(:iss:`2381`) (: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`) (:iss:`2441`)
- Dont strip :code:`&` and :code:`-` from the end of URLs (:iss:`2436`) - Dont strip :code:`&` and :code:`-` from the end of URLs (:iss:`2436`)

51
glfw/backend_utils.c vendored
View File

@ -332,54 +332,3 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func displ
} }
return read_ok; 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;
}

View File

@ -94,5 +94,4 @@ int pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func);
unsigned dispatchTimers(EventLoopData *eld); unsigned dispatchTimers(EventLoopData *eld);
void finalizePollData(EventLoopData *eld); void finalizePollData(EventLoopData *eld);
bool initPollData(EventLoopData *eld, int display_fd); bool initPollData(EventLoopData *eld, int display_fd);
char** parseUriList(char* text, int* count);
void wakeupEventLoop(EventLoopData *eld); void wakeupEventLoop(EventLoopData *eld);

17
glfw/glfw3.h vendored
View File

@ -1593,19 +1593,22 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
*/ */
typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); 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: * callback function has the following signature:
* @code * @code
* void function_name(GLFWwindow* window, int path_count, const char* paths[]) * int function_name(GLFWwindow* window, const char* mime, const char* text)
* @endcode * @endcode
* *
* @param[in] window The window that received the event. * @param[in] window The window that received the event.
* @param[in] path_count The number of dropped paths. * @param[in] mime The UTF-8 encoded drop mime-type
* @param[in] paths The UTF-8 encoded file and/or directory path names. * @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. * callback function returns.
* *
* @sa @ref path_drop * @sa @ref path_drop
@ -1615,7 +1618,7 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*);
* *
* @ingroup input * @ingroup input
*/ */
typedef void (* GLFWdropfun)(GLFWwindow*,int,const char*[]); typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t);
typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool);

5
glfw/input.c vendored
View File

@ -350,10 +350,11 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered)
// Notifies shared code of files or directories dropped on a window // 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) 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 // Notifies shared code of a joystick connection or disconnection

2
glfw/internal.h vendored
View File

@ -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 _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwInputCursorEnter(_GLFWwindow* window, bool entered); 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 _glfwInputJoystick(_GLFWjoystick* js, int event);
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value); void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value);
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value); void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value);

2
glfw/wl_init.c vendored
View File

@ -852,7 +852,7 @@ void _glfwPlatformTerminate(void)
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard); wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) { for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
if (_glfw.wl.dataOffers[doi].id) { 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) if (_glfw.wl.dataDevice)

24
glfw/wl_platform.h vendored
View File

@ -195,28 +195,19 @@ typedef enum _GLFWWaylandOfferType
typedef struct _GLFWWaylandDataOffer typedef struct _GLFWWaylandDataOffer
{ {
struct wl_data_offer *id; void *id;
const char *mime;
_GLFWWaylandOfferType offer_type; _GLFWWaylandOfferType offer_type;
size_t idx; size_t idx;
int is_self_offer; bool is_self_offer;
int has_uri_list; bool is_primary;
const char *plain_text_mime, *mime_for_drop;
uint32_t source_actions; uint32_t source_actions;
uint32_t dnd_action; uint32_t dnd_action;
struct wl_surface *surface; struct wl_surface *surface;
const char **mimes;
size_t mimes_capacity, mimes_count;
} _GLFWWaylandDataOffer; } _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 // Wayland-specific global data
// //
typedef struct _GLFWlibraryWayland typedef struct _GLFWlibraryWayland
@ -287,8 +278,6 @@ typedef struct _GLFWlibraryWayland
size_t dataOffersCounter; size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8]; _GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString; char* primarySelectionString;
size_t primarySelectionOffersCounter;
_GLFWWaylandPrimaryOffer primarySelectionOffers[8];
} _GLFWlibraryWayland; } _GLFWlibraryWayland;
// Wayland-specific per-monitor data // Wayland-specific per-monitor data
@ -322,3 +311,4 @@ void _glfwSetupWaylandDataDevice(void);
void _glfwSetupWaylandPrimarySelectionDevice(void); void _glfwSetupWaylandPrimarySelectionDevice(void);
void animateCursorImage(id_type timer_id, void *data); void animateCursorImage(id_type timer_id, void *data);
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape); struct wl_cursor* _glfwLoadCursor(GLFWCursorShape);
void destroy_data_offer(_GLFWWaylandDataOffer*);

190
glfw/wl_window.c vendored
View File

@ -42,7 +42,7 @@
#include <sys/mman.h> #include <sys/mman.h>
#define URI_LIST_MIME "text/uri-list"
static bool checkScaleChange(_GLFWwindow* window) static bool checkScaleChange(_GLFWwindow* window)
@ -1509,10 +1509,11 @@ static void _glfwSendPrimarySelectionText(void *data UNUSED, struct zwp_primary_
send_text(_glfw.wl.primarySelectionString, fd); 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); wl_display_flush(_glfw.wl.display);
size_t sz = 0, capacity = 0; size_t capacity = 0;
char *buf = NULL; char *buf = NULL;
*data_sz = 0;
struct pollfd fds; struct pollfd fds;
fds.fd = data_pipe; fds.fd = data_pipe;
fds.events = POLLIN; fds.events = POLLIN;
@ -1533,20 +1534,20 @@ static char* read_offer_string(int data_pipe) {
if (!ret) { if (!ret) {
bail("Wayland: Failed to read clipboard data from pipe (timed out)"); 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; capacity += 4096;
buf = realloc(buf, capacity); buf = realloc(buf, capacity);
if (!buf) { if (!buf) {
bail("Wayland: Failed to allocate memory to read clipboard data"); 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 (ret == -1) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno)); 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; } if (ret == 0) { close(data_pipe); buf[*data_sz] = 0; return buf; }
sz += ret; *data_sz += ret;
start = glfwGetTime(); start = glfwGetTime();
} }
bail("Wayland: Failed to read clipboard data from pipe (timed out)"); 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; if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]); zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
close(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]; int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL; if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
wl_data_offer_receive(data_offer, mime, pipefd[1]); wl_data_offer_receive(data_offer, mime, pipefd[1]);
close(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) { 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, .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) { static void prune_unclaimed_data_offers(void) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) { if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id); destroy_data_offer(&_glfw.wl.dataOffers[i]);
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]));
} }
} }
} }
@ -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, 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) { struct zwp_primary_selection_offer_v1 *primary_selection_offer) {
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id == primary_selection_offer) { if (_glfw.wl.dataOffers[i].id == primary_selection_offer) {
_glfw.wl.primarySelectionOffers[i].offer_type = PRIMARY_SELECTION; _glfw.wl.dataOffers[i].offer_type = PRIMARY_SELECTION;
} else if (_glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) { } else if (_glfw.wl.dataOffers[i].offer_type == PRIMARY_SELECTION) {
_glfw.wl.primarySelectionOffers[i].offer_type = EXPIRED; // previous selection offer _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) { static void
if (strcmp(mime, "text/plain;charset=utf-8") == 0) set_offer_mimetype(_GLFWWaylandDataOffer* offer, const char* mime) {
offer->mime = "text/plain;charset=utf-8"; if (strcmp(mime, "text/plain;charset=utf-8") == 0) {
else if (!offer->mime && strcmp(mime, "text/plain") == 0) offer->plain_text_mime = "text/plain;charset=utf-8";
offer->mime = "text/plain"; } else if (!offer->plain_text_mime && strcmp(mime, "text/plain")) {
else if (strcmp(mime, clipboard_mime()) == 0) offer->plain_text_mime = "text/plain";
offer->is_self_offer = 1; }
else if (strcmp(mime, URI_LIST_MIME) == 0) if (strcmp(mime, clipboard_mime()) == 0) {
offer->has_uri_list = 1; 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) { 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) { 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++) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id == id) { if (_glfw.wl.dataOffers[i].id == id) {
set_offer_mimetype((struct _GLFWWaylandDataOffer*)&_glfw.wl.primarySelectionOffers[i], mime); set_offer_mimetype((_GLFWWaylandDataOffer*)&_glfw.wl.dataOffers[i], mime);
break; break;
} }
} }
@ -1696,7 +1708,8 @@ static const struct zwp_primary_selection_offer_v1_listener primary_selection_of
.offer = handle_primary_selection_offer_mimetype, .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; size_t smallest_idx = SIZE_MAX, pos = 0;
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) { 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; pos = i;
} }
if (_glfw.wl.dataOffers[i].id == NULL) { if (_glfw.wl.dataOffers[i].id == NULL) {
_glfw.wl.dataOffers[i].id = id; pos = i;
_glfw.wl.dataOffers[i].idx = ++_glfw.wl.dataOffersCounter;
goto end; goto end;
} }
} }
if (_glfw.wl.dataOffers[pos].id) wl_data_offer_destroy(_glfw.wl.dataOffers[pos].id); if (_glfw.wl.dataOffers[pos].id) destroy_data_offer(&_glfw.wl.dataOffers[pos]);
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;
end: 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); 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) { 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; handle_data_offer_generic(id, true);
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:
zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL); 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) { 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++) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == id) { _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
_glfw.wl.dataOffers[i].offer_type = DRAG_AND_DROP; if (d->id == id) {
_glfw.wl.dataOffers[i].surface = surface; d->offer_type = DRAG_AND_DROP;
const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL; d->surface = surface;
wl_data_offer_accept(id, serial, mime); _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) { } else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer _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) { 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++) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id); destroy_data_offer(&_glfw.wl.dataOffers[i]);
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
} }
} }
} }
static void drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) { 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++) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP && _glfw.wl.dataOffers[i].mime_for_drop) {
char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME); size_t sz = 0;
if (uri_list) { 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 // 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 // which then requires more work with calling set_actions for drag and drop to function
// wl_data_offer_finish(_glfw.wl.dataOffers[i].id); // wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
int count;
char** paths = parseUriList(uri_list, &count);
_GLFWwindow* window = _glfw.windowListHead; _GLFWwindow* window = _glfw.windowListHead;
while (window) while (window)
{ {
if (window->wl.surface == _glfw.wl.dataOffers[i].surface) { 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; break;
} }
window = window->next; window = window->next;
} }
free(data);
for (int k = 0; k < count; k++)
free(paths[k]);
free(paths);
free(uri_list);
} }
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id); destroy_data_offer(&_glfw.wl.dataOffers[i]);
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
break; break;
} }
} }
@ -1894,10 +1901,12 @@ void _glfwPlatformSetClipboardString(const char* string)
const char* _glfwPlatformGetClipboardString(void) const char* _glfwPlatformGetClipboardString(void)
{ {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { 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) { _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString; if (d->id && d->offer_type == CLIPBOARD && d->plain_text_mime) {
if (d->is_self_offer) return _glfw.wl.clipboardString;
free(_glfw.wl.pasteString); 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; return _glfw.wl.pasteString;
} }
} }
@ -1944,13 +1953,14 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
{ {
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString; if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id && _glfw.wl.primarySelectionOffers[i].mime && _glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) { _GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
if (_glfw.wl.primarySelectionOffers[i].is_self_offer) { if (d->id && d->is_primary && d->offer_type == PRIMARY_SELECTION && d->plain_text_mime) {
if (d->is_self_offer) {
return _glfw.wl.primarySelectionString; return _glfw.wl.primarySelectionString;
} }
free(_glfw.wl.pasteString); 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; return _glfw.wl.pasteString;
} }
} }

3
glfw/x11_init.c vendored
View File

@ -384,9 +384,6 @@ static bool initExtensions(void)
_glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False); _glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False);
_glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False); _glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False);
_glfw.x11.XdndTypeList = XInternAtom(_glfw.x11.display, "XdndTypeList", 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 // ICCCM, EWMH and Motif window property atoms
// These can be set safely even without WM support // These can be set safely even without WM support

6
glfw/x11_platform.h vendored
View File

@ -266,9 +266,6 @@ typedef struct _GLFWlibraryX11
Atom XdndFinished; Atom XdndFinished;
Atom XdndSelection; Atom XdndSelection;
Atom XdndTypeList; Atom XdndTypeList;
Atom text_uri_list;
Atom text_plain;
Atom text_plain_utf8;
// Selection (clipboard) atoms // Selection (clipboard) atoms
Atom TARGETS; Atom TARGETS;
@ -329,7 +326,8 @@ typedef struct _GLFWlibraryX11
struct { struct {
int version; int version;
Window source; Window source;
Atom format; char format[128];
int format_priority;
} xdnd; } xdnd;
struct { struct {

40
glfw/x11_window.c vendored
View File

@ -1454,7 +1454,8 @@ static void processEvent(XEvent *event)
_glfw.x11.xdnd.source = event->xclient.data.l[0]; _glfw.x11.xdnd.source = event->xclient.data.l[0];
_glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24; _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) if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
return; return;
@ -1471,18 +1472,22 @@ static void processEvent(XEvent *event)
count = 3; count = 3;
formats = (Atom*) event->xclient.data.l + 2; 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++) for (i = 0; i < count; i++)
{
if (formats[i] == _glfw.x11.text_uri_list)
{ {
_glfw.x11.xdnd.format = _glfw.x11.text_uri_list; if (atom_names[i]) {
break; int prio = _glfwInputDrop(window, atom_names[i], NULL, 0);
} else if (formats[i] == _glfw.x11.text_plain_utf8 && (_glfw.x11.xdnd.format == None || _glfw.x11.xdnd.format == _glfw.x11.text_plain)) { if (prio > _glfw.x11.xdnd.format_priority) {
_glfw.x11.xdnd.format = _glfw.x11.text_plain_utf8; _glfw.x11.xdnd.format_priority = prio;
} else if (formats[i] == _glfw.x11.text_plain && _glfw.x11.xdnd.format == None) { strncpy(_glfw.x11.xdnd.format, atom_names[i], arraysz(_glfw.x11.xdnd.format) - 1);
_glfw.x11.xdnd.format = _glfw.x11.text_plain; }
XFree(atom_names[i]);
}
} }
free(atom_names);
} }
if (list && formats) if (list && formats)
@ -1496,7 +1501,7 @@ static void processEvent(XEvent *event)
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
return; return;
if (_glfw.x11.xdnd.format) if (_glfw.x11.xdnd.format_priority > 0)
{ {
if (_glfw.x11.xdnd.version >= 1) if (_glfw.x11.xdnd.version >= 1)
time = event->xclient.data.l[2]; time = event->xclient.data.l[2];
@ -1504,7 +1509,7 @@ static void processEvent(XEvent *event)
// Request the chosen format from the source window // Request the chosen format from the source window
XConvertSelection(_glfw.x11.display, XConvertSelection(_glfw.x11.display,
_glfw.x11.XdndSelection, _glfw.x11.XdndSelection,
_glfw.x11.xdnd.format, XInternAtom(_glfw.x11.display, _glfw.x11.xdnd.format, 0),
_glfw.x11.XdndSelection, _glfw.x11.XdndSelection,
window->x11.handle, window->x11.handle,
time); time);
@ -1552,7 +1557,7 @@ static void processEvent(XEvent *event)
reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[2] = 0; // Specify an empty rectangle
reply.xclient.data.l[3] = 0; 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 that we are ready to copy the dragged data
reply.xclient.data.l[1] = 1; // Accept with no rectangle reply.xclient.data.l[1] = 1; // Accept with no rectangle
@ -1582,14 +1587,7 @@ static void processEvent(XEvent *event)
if (result) if (result)
{ {
int i, count; _glfwInputDrop(window, _glfw.x11.xdnd.format, data, result);
char** paths = parseUriList(data, &count);
_glfwInputDrop(window, count, (const char**) paths);
for (i = 0; i < count; i++)
free(paths[i]);
free(paths);
} }
if (data) if (data)

View File

@ -46,8 +46,9 @@ from .tabs import (
from .typing import PopenType, TypedDict from .typing import PopenType, TypedDict
from .utils import ( from .utils import (
func_name, get_editor, get_primary_selection, is_path_in_temp_dir, func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
log_error, open_url, parse_address_spec, remove_socket_file, safe_print, log_error, open_url, parse_address_spec, parse_uri_list,
set_primary_selection, single_instance, startup_notification_handler remove_socket_file, safe_print, set_primary_selection, single_instance,
startup_notification_handler
) )
from .window import MatchPatternType, Window from .window import MatchPatternType, Window
@ -686,12 +687,15 @@ class Boss:
if tm is not None: if tm is not None:
tm.update_tab_bar_data() 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) tm = self.os_window_map.get(os_window_id)
if tm is not None: if tm is not None:
w = tm.active_window w = tm.active_window
if w is not None: 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: 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 self.cached_values['window-size'] = viewport_width, viewport_height

6
kitty/glfw-wrapper.c generated
View File

@ -137,12 +137,12 @@ load_glfw(const char* path) {
*(void **) (&glfwSetWindowSizeLimits_impl) = dlsym(handle, "glfwSetWindowSizeLimits"); *(void **) (&glfwSetWindowSizeLimits_impl) = dlsym(handle, "glfwSetWindowSizeLimits");
if (glfwSetWindowSizeLimits_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeLimits with error: %s", dlerror()); 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"); *(void **) (&glfwSetWindowSizeIncrements_impl) = dlsym(handle, "glfwSetWindowSizeIncrements");
if (glfwSetWindowSizeIncrements_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeIncrements with error: %s", dlerror()); 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"); *(void **) (&glfwSetWindowSize_impl) = dlsym(handle, "glfwSetWindowSize");
if (glfwSetWindowSize_impl == NULL) fail("Failed to load glfw function glfwSetWindowSize with error: %s", dlerror()); if (glfwSetWindowSize_impl == NULL) fail("Failed to load glfw function glfwSetWindowSize with error: %s", dlerror());

25
kitty/glfw-wrapper.h generated
View File

@ -1353,19 +1353,22 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
*/ */
typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*); 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: * callback function has the following signature:
* @code * @code
* void function_name(GLFWwindow* window, int path_count, const char* paths[]) * int function_name(GLFWwindow* window, const char* mime, const char* text)
* @endcode * @endcode
* *
* @param[in] window The window that received the event. * @param[in] window The window that received the event.
* @param[in] path_count The number of dropped paths. * @param[in] mime The UTF-8 encoded drop mime-type
* @param[in] paths The UTF-8 encoded file and/or directory path names. * @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. * callback function returns.
* *
* @sa @ref path_drop * @sa @ref path_drop
@ -1375,7 +1378,7 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*);
* *
* @ingroup input * @ingroup input
*/ */
typedef void (* GLFWdropfun)(GLFWwindow*,int,const char*[]); typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t);
typedef void (* GLFWliveresizefun)(GLFWwindow*, bool); typedef void (* GLFWliveresizefun)(GLFWwindow*, bool);
@ -1747,14 +1750,14 @@ typedef void (*glfwSetWindowSizeLimits_func)(GLFWwindow*, int, int, int, int);
glfwSetWindowSizeLimits_func glfwSetWindowSizeLimits_impl; glfwSetWindowSizeLimits_func glfwSetWindowSizeLimits_impl;
#define glfwSetWindowSizeLimits 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); typedef void (*glfwSetWindowSizeIncrements_func)(GLFWwindow*, int, int);
glfwSetWindowSizeIncrements_func glfwSetWindowSizeIncrements_impl; glfwSetWindowSizeIncrements_func glfwSetWindowSizeIncrements_impl;
#define glfwSetWindowSizeIncrements 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); typedef void (*glfwSetWindowSize_func)(GLFWwindow*, int, int);
glfwSetWindowSize_func glfwSetWindowSize_impl; glfwSetWindowSize_func glfwSetWindowSize_impl;
#define glfwSetWindowSize glfwSetWindowSize_impl #define glfwSetWindowSize glfwSetWindowSize_impl

View File

@ -320,17 +320,26 @@ window_focus_callback(GLFWwindow *w, int focused) {
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static void static int
drop_callback(GLFWwindow *w, int count, const char **strings) { drop_callback(GLFWwindow *w, const char *mime, const char *data, size_t sz) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return 0;
PyObject *s = PyTuple_New(count); if (!data) {
if (s) { if (strcmp(mime, "text/uri-list") == 0) return 3;
for (int i = 0; i < count; i++) PyTuple_SET_ITEM(s, i, PyUnicode_FromString(strings[i])); if (strcmp(mime, "text/plain;charset=utf-8") == 0) return 2;
WINDOW_CALLBACK(on_drop, "O", s); if (strcmp(mime, "text/plain") == 0) return 1;
Py_CLEAR(s); return 0;
request_tick_callback();
} }
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; global_state.callback_os_window = NULL;
return 0;
} }
// }}} // }}}

View File

@ -532,3 +532,21 @@ def read_shell_environment(opts: Optional[Options] = None) -> Dict[str, str]:
else: else:
log_error('Failed to run shell to read its environment') log_error('Failed to run shell to read its environment')
return ans 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)