From d519553581a671633f010a6f94276ee5996c8ece Mon Sep 17 00:00:00 2001 From: Tarmack Date: Thu, 25 Oct 2018 18:22:47 +0200 Subject: [PATCH] Implement primary selection for Wayland --- glfw/glfw.py | 13 +- glfw/input.c | 16 ++ glfw/internal.h | 4 + glfw/source-info.json | 3 +- glfw/wl_init.c | 24 +- glfw/wl_platform.h | 32 ++- glfw/wl_protocols/gtk-primary-selection.xml | 225 +++++++++++++++++++ glfw/wl_window.c | 235 ++++++++++++++++---- glfw/x11_window.c | 49 ++-- kitty/constants.py | 2 +- kitty/glfw-wrapper.c | 4 +- kitty/glfw-wrapper.h | 12 +- kitty/glfw.c | 14 +- 13 files changed, 538 insertions(+), 95 deletions(-) create mode 100644 glfw/wl_protocols/gtk-primary-selection.xml diff --git a/glfw/glfw.py b/glfw/glfw.py index 1bbfd0b43..cb2392d09 100755 --- a/glfw/glfw.py +++ b/glfw/glfw.py @@ -90,9 +90,12 @@ def init_env(env, pkg_config, at_least_version, test_compile, module='x11'): def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir): for protocol in env.wayland_protocols: - src = os.path.join(env.wayland_packagedir, protocol) - if not os.path.exists(src): - raise SystemExit('The wayland-protocols package on your system is missing the {} protocol definition file'.format(protocol)) + if os.path.dirname(protocol) == 'wl_protocols': + src = os.path.join(dest_dir, protocol) + else: + src = os.path.join(env.wayland_packagedir, protocol) + if not os.path.exists(src): + raise SystemExit('The wayland-protocols package on your system is missing the {} protocol definition file'.format(protocol)) for ext in 'hc': dest = wayland_protocol_file_name(src, ext) dest = os.path.join(dest_dir, dest) @@ -208,8 +211,8 @@ def generate_wrappers(glfw_header): void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods) void* glfwGetX11Display(void) int32_t glfwGetX11Window(GLFWwindow* window) - void glfwSetX11SelectionString(const char* string) - const char* glfwGetX11SelectionString(void) + void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string) + const char* glfwGetPrimarySelectionString(GLFWwindow* window, void) int glfwGetXKBScancode(const char* key_name, int case_sensitive) void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback) '''.splitlines(): diff --git a/glfw/input.c b/glfw/input.c index 46d06abc4..f863c8b1a 100644 --- a/glfw/input.c +++ b/glfw/input.c @@ -1375,6 +1375,22 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle) return _glfwPlatformGetClipboardString(); } +#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) +GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle, const char* string) +{ + assert(string != NULL); + + _GLFW_REQUIRE_INIT(); + _glfwPlatformSetPrimarySelectionString(string); +} + +GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle) +{ + _GLFW_REQUIRE_INIT_OR_RETURN(NULL); + return _glfwPlatformGetPrimarySelectionString(); +} +#endif + GLFWAPI double glfwGetTime(void) { _GLFW_REQUIRE_INIT_OR_RETURN(0.0); diff --git a/glfw/internal.h b/glfw/internal.h index 6b064c664..8879d58b1 100644 --- a/glfw/internal.h +++ b/glfw/internal.h @@ -624,6 +624,10 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp) void _glfwPlatformSetClipboardString(const char* string); const char* _glfwPlatformGetClipboardString(void); +#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) +void _glfwPlatformSetPrimarySelectionString(const char* string); +const char* _glfwPlatformGetPrimarySelectionString(void); +#endif int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode); void _glfwPlatformUpdateGamepadGUID(char* guid); diff --git a/glfw/source-info.json b/glfw/source-info.json index b4a8b8bcb..fe8955696 100644 --- a/glfw/source-info.json +++ b/glfw/source-info.json @@ -72,7 +72,8 @@ "unstable/relative-pointer/relative-pointer-unstable-v1.xml", "unstable/pointer-constraints/pointer-constraints-unstable-v1.xml", "unstable/idle-inhibit/idle-inhibit-unstable-v1.xml", - "unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" + "unstable/xdg-decoration/xdg-decoration-unstable-v1.xml", + "wl_protocols/gtk-primary-selection.xml" ], "sources": [ "wl_init.c", diff --git a/glfw/wl_init.c b/glfw/wl_init.c index 7f918f04c..9bb7040c3 100644 --- a/glfw/wl_init.c +++ b/glfw/wl_init.c @@ -563,8 +563,11 @@ static void registryHandleGlobal(void* data, _glfw.wl.seatVersion); wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL); } - if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) { - _glfwSetupWaylandDataDevice(); + if (_glfw.wl.seat) { + if (_glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) _glfwSetupWaylandDataDevice(); + if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) { + _glfwSetupWaylandPrimarySelectionDevice(); + } } } else if (strcmp(interface, "xdg_wm_base") == 0) @@ -615,6 +618,16 @@ static void registryHandleGlobal(void* data, _glfwSetupWaylandDataDevice(); } } + else if (strcmp(interface, "gtk_primary_selection_device_manager") == 0) + { + _glfw.wl.primarySelectionDeviceManager = + wl_registry_bind(registry, name, + >k_primary_selection_device_manager_interface, + 1); + if (_glfw.wl.seat && _glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) { + _glfwSetupWaylandPrimarySelectionDevice(); + } + } } @@ -808,6 +821,10 @@ void _glfwPlatformTerminate(void) wl_data_device_destroy(_glfw.wl.dataDevice); if (_glfw.wl.dataDeviceManager) wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager); + if (_glfw.wl.primarySelectionDevice) + gtk_primary_selection_device_destroy(_glfw.wl.primarySelectionDevice); + if (_glfw.wl.primarySelectionDeviceManager) + gtk_primary_selection_device_manager_destroy(_glfw.wl.primarySelectionDeviceManager); if (_glfw.wl.registry) wl_registry_destroy(_glfw.wl.registry); if (_glfw.wl.display) @@ -817,7 +834,8 @@ void _glfwPlatformTerminate(void) } closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0])); free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL; - free(_glfw.wl.clipboardSourceString); _glfw.wl.clipboardSourceString = NULL; + free(_glfw.wl.primarySelectionString); _glfw.wl.primarySelectionString = NULL; + free(_glfw.wl.pasteString); _glfw.wl.pasteString = NULL; } diff --git a/glfw/wl_platform.h b/glfw/wl_platform.h index 276ecb2e7..9f69dab69 100644 --- a/glfw/wl_platform.h +++ b/glfw/wl_platform.h @@ -60,6 +60,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR #include "wayland-relative-pointer-unstable-v1-client-protocol.h" #include "wayland-pointer-constraints-unstable-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h" +#include "wayland-gtk-primary-selection-client-protocol.h" #define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL) #define _glfw_dlclose(handle) dlclose(handle) @@ -186,11 +187,19 @@ typedef struct _GLFWwindowWayland } _GLFWwindowWayland; +typedef enum _GLFWWaylandOfferType +{ + EXPIRED, + CLIPBOARD, + DRAG_AND_DROP, + PRIMARY_SELECTION +}_GLFWWaylandOfferType ; + typedef struct _GLFWWaylandDataOffer { struct wl_data_offer *id; const char *mime; - int offer_type; + _GLFWWaylandOfferType offer_type; size_t idx; int is_self_offer; int has_uri_list; @@ -199,6 +208,17 @@ typedef struct _GLFWWaylandDataOffer struct wl_surface *surface; } _GLFWWaylandDataOffer; +typedef struct _GLFWWaylandPrimaryOffer +{ + struct gtk_primary_selection_offer *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 @@ -221,6 +241,9 @@ typedef struct _GLFWlibraryWayland struct wl_data_device_manager* dataDeviceManager; struct wl_data_device* dataDevice; struct wl_data_source* dataSourceForClipboard; + struct gtk_primary_selection_device_manager* primarySelectionDeviceManager; + struct gtk_primary_selection_device* primarySelectionDevice; + struct gtk_primary_selection_source* dataSourceForPrimarySelection; int compositorVersion; int seatVersion; @@ -261,11 +284,13 @@ typedef struct _GLFWlibraryWayland } egl; EventLoopData eventLoopData; + char* pasteString; char* clipboardString; - char* clipboardSourceString; - struct wl_data_offer* clipboardSourceOffer; size_t dataOffersCounter; _GLFWWaylandDataOffer dataOffers[8]; + char* primarySelectionString; + size_t primarySelectionOffersCounter; + _GLFWWaylandPrimaryOffer primarySelectionOffers[8]; } _GLFWlibraryWayland; // Wayland-specific per-monitor data @@ -296,4 +321,5 @@ typedef struct _GLFWcursorWayland void _glfwAddOutputWayland(uint32_t name, uint32_t version); void _glfwSetupWaylandDataDevice(); +void _glfwSetupWaylandPrimarySelectionDevice(); void animateCursorImage(id_type timer_id, void *data); diff --git a/glfw/wl_protocols/gtk-primary-selection.xml b/glfw/wl_protocols/gtk-primary-selection.xml new file mode 100644 index 000000000..02cab94fc --- /dev/null +++ b/glfw/wl_protocols/gtk-primary-selection.xml @@ -0,0 +1,225 @@ + + + + Copyright © 2015, 2016 Red Hat + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol provides the ability to have a primary selection device to + match that of the X server. This primary selection is a shortcut to the + common clipboard selection, where text just needs to be selected in order + to allow copying it elsewhere. The de facto way to perform this action + is the middle mouse button, although it is not limited to this one. + + Clients wishing to honor primary selection should create a primary + selection source and set it as the selection through + wp_primary_selection_device.set_selection whenever the text selection + changes. In order to minimize calls in pointer-driven text selection, + it should happen only once after the operation finished. Similarly, + a NULL source should be set when text is unselected. + + wp_primary_selection_offer objects are first announced through the + wp_primary_selection_device.data_offer event. Immediately after this event, + the primary data offer will emit wp_primary_selection_offer.offer events + to let know of the mime types being offered. + + When the primary selection changes, the client with the keyboard focus + will receive wp_primary_selection_device.selection events. Only the client + with the keyboard focus will receive such events with a non-NULL + wp_primary_selection_offer. Across keyboard focus changes, previously + focused clients will receive wp_primary_selection_device.events with a + NULL wp_primary_selection_offer. + + In order to request the primary selection data, the client must pass + a recent serial pertaining to the press event that is triggering the + operation, if the compositor deems the serial valid and recent, the + wp_primary_selection_source.send event will happen in the other end + to let the transfer begin. The client owning the primary selection + should write the requested data, and close the file descriptor + immediately. + + If the primary selection owner client disappeared during the transfer, + the client reading the data will receive a + wp_primary_selection_device.selection event with a NULL + wp_primary_selection_offer, the client should take this as a hint + to finish the reads related to the no longer existing offer. + + The primary selection owner should be checking for errors during + writes, merely cancelling the ongoing transfer if any happened. + + + + + The primary selection device manager is a singleton global object that + provides access to the primary selection. It allows to create + wp_primary_selection_source objects, as well as retrieving the per-seat + wp_primary_selection_device objects. + + + + + Create a new primary selection source. + + + + + + + Create a new data device for a given seat. + + + + + + + + Destroy the primary selection device manager. + + + + + + + + Replaces the current selection. The previous owner of the primary selection + will receive a wp_primary_selection_source.cancelled event. + + To unset the selection, set the source to NULL. + + + + + + + + Introduces a new wp_primary_selection_offer object that may be used + to receive the current primary selection. Immediately following this + event, the new wp_primary_selection_offer object will send + wp_primary_selection_offer.offer events to describe the offered mime + types. + + + + + + + The wp_primary_selection_device.selection event is sent to notify the + client of a new primary selection. This event is sent after the + wp_primary_selection.data_offer event introducing this object, and after + the offer has announced its mimetypes through + wp_primary_selection_offer.offer. + + The data_offer is valid until a new offer or NULL is received + or until the client loses keyboard focus. The client must destroy the + previous selection data_offer, if any, upon receiving this event. + + + + + + + Destroy the primary selection device. + + + + + + + A wp_primary_selection_offer represents an offer to transfer the contents + of the primary selection clipboard to the client. Similar to + wl_data_offer, the offer also describes the mime types that the source + will transferthat the + data can be converted to and provides the mechanisms for transferring the + data directly to the client. + + + + + To transfer the contents of the primary selection clipboard, the client + issues this request and indicates the mime type that it wants to + receive. The transfer happens through the passed file descriptor + (typically created with the pipe system call). The source client writes + the data in the mime type representation requested and then closes the + file descriptor. + + The receiving client reads from the read end of the pipe until EOF and + closes its end, at which point the transfer is complete. + + + + + + + + Destroy the primary selection offer. + + + + + + Sent immediately after creating announcing the wp_primary_selection_offer + through wp_primary_selection_device.data_offer. One event is sent per + offered mime type. + + + + + + + + The source side of a wp_primary_selection_offer, it provides a way to + describe the offered data and respond to requests to transfer the + requested contents of the primary selection clipboard. + + + + + This request adds a mime type to the set of mime types advertised to + targets. Can be called several times to offer multiple types. + + + + + + + Destroy the primary selection source. + + + + + + Request for the current primary selection contents from the client. + Send the specified mime type over the passed file descriptor, then + close it. + + + + + + + + This primary selection source is no longer valid. The client should + clean up and destroy this primary selection source. + + + + diff --git a/glfw/wl_window.c b/glfw/wl_window.c index 6e69a14e6..17365cbaf 100644 --- a/glfw/wl_window.c +++ b/glfw/wl_window.c @@ -1542,13 +1542,13 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor) } } -static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd) +static void send_text(char *text, int fd) { - if (_glfw.wl.clipboardString) { - size_t len = strlen(_glfw.wl.clipboardString), pos = 0; + if (text) { + size_t len = strlen(text), pos = 0; double start = glfwGetTime(); while (pos < len && glfwGetTime() - start < 2.0) { - ssize_t ret = write(fd, _glfw.wl.clipboardString + pos, len - pos); + ssize_t ret = write(fd, text + pos, len - pos); if (ret < 0) { if (errno == EAGAIN || errno == EINTR) continue; _glfwInputError(GLFW_PLATFORM_ERROR, @@ -1564,22 +1564,27 @@ static void _glfwSendClipboardText(void *data, struct wl_data_source *data_sourc close(fd); } -static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) { - int pipefd[2]; - if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL; - wl_data_offer_receive(data_offer, mime, pipefd[1]); - close(pipefd[1]); +static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd) { + send_text(_glfw.wl.clipboardString, fd); +} + +static void _glfwSendPrimarySelectionText(void *data, struct gtk_primary_selection_source *primary_selection_source, + const char *mime_type, int fd) { + send_text(_glfw.wl.primarySelectionString, fd); +} + +static char* read_offer_string(int data_pipe) { wl_display_flush(_glfw.wl.display); size_t sz = 0, capacity = 0; char *buf = NULL; struct pollfd fds; - fds.fd = pipefd[0]; + fds.fd = data_pipe; fds.events = POLLIN; double start = glfwGetTime(); #define bail(...) { \ _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \ free(buf); buf = NULL; \ - close(pipefd[0]); \ + close(data_pipe); \ return NULL; \ } @@ -1599,12 +1604,12 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) bail("Wayland: Failed to allocate memory to read clipboard data"); } } - ret = read(pipefd[0], buf + sz, capacity - sz - 1); + ret = read(data_pipe, buf + sz, capacity - 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(pipefd[0]); buf[sz] = 0; return buf; } + if (ret == 0) { close(data_pipe); buf[sz] = 0; return buf; } sz += ret; start = glfwGetTime(); } @@ -1613,13 +1618,20 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) } -static const char* _glfwReceiveClipboardText(struct wl_data_offer *data_offer, const char *mime) -{ - if (_glfw.wl.clipboardSourceOffer == data_offer && _glfw.wl.clipboardSourceString) - return _glfw.wl.clipboardSourceString; - free(_glfw.wl.clipboardSourceString); - _glfw.wl.clipboardSourceString = read_data_offer(data_offer, mime); - return _glfw.wl.clipboardSourceString; +static char* read_primary_selection_offer(struct gtk_primary_selection_offer *primary_selection_offer, const char *mime) { + int pipefd[2]; + if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL; + gtk_primary_selection_offer_receive(primary_selection_offer, mime, pipefd[1]); + close(pipefd[1]); + return read_offer_string(pipefd[0]); +} + +static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) { + 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]); } static void data_source_canceled(void *data, struct wl_data_source *wl_data_source) { @@ -1628,6 +1640,12 @@ static void data_source_canceled(void *data, struct wl_data_source *wl_data_sour wl_data_source_destroy(wl_data_source); } +static void primary_selection_source_canceled(void *data, struct gtk_primary_selection_source *primary_selection_source) { + if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source) + _glfw.wl.dataSourceForPrimarySelection = NULL; + gtk_primary_selection_source_destroy(primary_selection_source); +} + static void data_source_target(void *data, struct wl_data_source *wl_data_source, const char* mime) { } @@ -1637,6 +1655,11 @@ const static struct wl_data_source_listener data_source_listener = { .target = data_source_target, }; +const static struct gtk_primary_selection_source_listener primary_selection_source_listener = { + .send = _glfwSendPrimarySelectionText, + .cancelled = primary_selection_source_canceled, +}; + static void prune_unclaimed_data_offers() { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) { @@ -1646,29 +1669,63 @@ static void prune_unclaimed_data_offers() { } } +static void prune_unclaimed_primary_selection_offers() { + for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) { + if (_glfw.wl.primarySelectionOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) { + gtk_primary_selection_offer_destroy(_glfw.wl.primarySelectionOffers[i].id); + memset(_glfw.wl.primarySelectionOffers + i, 0, sizeof(_glfw.wl.primarySelectionOffers[0])); + } + } +} + static void mark_selection_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id == data_offer) { - _glfw.wl.dataOffers[i].offer_type = 1; - } else if (_glfw.wl.dataOffers[i].offer_type == 1) { - _glfw.wl.dataOffers[i].offer_type = 0; // previous selection offer + _glfw.wl.dataOffers[i].offer_type = CLIPBOARD; + } else if (_glfw.wl.dataOffers[i].offer_type == CLIPBOARD) { + _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer } } prune_unclaimed_data_offers(); } +static void mark_primary_selection_offer(void *data, struct gtk_primary_selection_device* primary_selection_device, + struct gtk_primary_selection_offer *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 + } + } + prune_unclaimed_primary_selection_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 handle_offer_mimetype(void *data, struct wl_data_offer* id, const char *mime) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { if (_glfw.wl.dataOffers[i].id == id) { - if (strcmp(mime, "text/plain;charset=utf-8") == 0) - _glfw.wl.dataOffers[i].mime = "text/plain;charset=utf-8"; - else if (!_glfw.wl.dataOffers[i].mime && strcmp(mime, "text/plain") == 0) - _glfw.wl.dataOffers[i].mime = "text/plain"; - else if (strcmp(mime, clipboard_mime()) == 0) - _glfw.wl.dataOffers[i].is_self_offer = 1; - else if (strcmp(mime, URI_LIST_MIME) == 0) - _glfw.wl.dataOffers[i].has_uri_list = 1; + set_offer_mimetype(&_glfw.wl.dataOffers[i], mime); + break; + } + } +} + +static void handle_primary_selection_offer_mimetype(void *data, struct gtk_primary_selection_offer* 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); break; } } @@ -1699,6 +1756,10 @@ static const struct wl_data_offer_listener data_offer_listener = { .action = data_offer_action, }; +static const struct gtk_primary_selection_offer_listener primary_selection_offer_listener = { + .offer = handle_primary_selection_offer_mimetype, +}; + static void handle_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) { size_t smallest_idx = SIZE_MAX, pos = 0; for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { @@ -1720,15 +1781,36 @@ end: wl_data_offer_add_listener(id, &data_offer_listener, NULL); } +static void handle_primary_selection_offer(void *data, struct gtk_primary_selection_device *gtk_primary_selection_device, struct gtk_primary_selection_offer *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) gtk_primary_selection_offer_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: + gtk_primary_selection_offer_add_listener(id, &primary_selection_offer_listener, NULL); +} + static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, 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 = 2; + _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); - } else if (_glfw.wl.dataOffers[i].offer_type == 2) { - _glfw.wl.dataOffers[i].offer_type = 0; // previous drag offer + } else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) { + _glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer } } prune_unclaimed_data_offers(); @@ -1736,18 +1818,16 @@ static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32 static void drag_leave(void *data, struct wl_data_device *wl_data_device) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].offer_type == 2) { + 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])); } } } - - static void drop(void *data, struct wl_data_device *wl_data_device) { for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) { - if (_glfw.wl.dataOffers[i].offer_type == 2) { + 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) { wl_data_offer_finish(_glfw.wl.dataOffers[i].id); @@ -1789,20 +1869,38 @@ const static struct wl_data_device_listener data_device_listener = { .leave = drag_leave, }; +const static struct gtk_primary_selection_device_listener primary_selection_device_listener = { + .data_offer = handle_primary_selection_offer, + .selection = mark_primary_selection_offer, +}; + static void -copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) { +clipboard_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) { if (_glfw.wl.dataDevice && data == (void*)_glfw.wl.dataSourceForClipboard) { wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial); } wl_callback_destroy(callback); } +static void +primary_selection_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) { + if (_glfw.wl.primarySelectionDevice && data == (void*)_glfw.wl.dataSourceForPrimarySelection) { + gtk_primary_selection_device_set_selection(_glfw.wl.primarySelectionDevice, data, serial); + } + wl_callback_destroy(callback); +} + void _glfwSetupWaylandDataDevice() { _glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat); if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL); } +void _glfwSetupWaylandPrimarySelectionDevice() { + _glfw.wl.primarySelectionDevice = gtk_primary_selection_device_manager_get_device(_glfw.wl.primarySelectionDeviceManager, _glfw.wl.seat); + if (_glfw.wl.primarySelectionDevice) gtk_primary_selection_device_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL); +} + static inline GLFWbool _glfwEnsureDataDevice() { if (!_glfw.wl.dataDeviceManager) { @@ -1851,16 +1949,67 @@ void _glfwPlatformSetClipboardString(const char* string) wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING"); wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING"); struct wl_callback *callback = wl_display_sync(_glfw.wl.display); - const static struct wl_callback_listener copy_callback_listener = {.done = copy_callback_done }; - wl_callback_add_listener(callback, ©_callback_listener, _glfw.wl.dataSourceForClipboard); + const static struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done}; + wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard); } 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 == 1) { + 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; - return _glfwReceiveClipboardText(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime); + free(_glfw.wl.pasteString); + _glfw.wl.pasteString = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime); + return _glfw.wl.pasteString; + } + } + return NULL; +} + +void _glfwPlatformSetPrimarySelectionString(const char* string) +{ + if (!_glfw.wl.primarySelectionDevice) { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Cannot copy no primary selection device available"); + return; + } + if (_glfw.wl.primarySelectionString == string) return; + free(_glfw.wl.primarySelectionString); + _glfw.wl.primarySelectionString = _glfw_strdup(string); + + if (_glfw.wl.dataSourceForPrimarySelection) + gtk_primary_selection_source_destroy(_glfw.wl.dataSourceForPrimarySelection); + _glfw.wl.dataSourceForPrimarySelection = gtk_primary_selection_device_manager_create_source(_glfw.wl.primarySelectionDeviceManager); + if (!_glfw.wl.dataSourceForPrimarySelection) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "Wayland: Cannot copy failed to create primary selection source"); + return; + } + gtk_primary_selection_source_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL); + gtk_primary_selection_source_offer(_glfw.wl.dataSourceForPrimarySelection, clipboard_mime()); + gtk_primary_selection_source_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain"); + gtk_primary_selection_source_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain;charset=utf-8"); + gtk_primary_selection_source_offer(_glfw.wl.dataSourceForPrimarySelection, "TEXT"); + gtk_primary_selection_source_offer(_glfw.wl.dataSourceForPrimarySelection, "STRING"); + gtk_primary_selection_source_offer(_glfw.wl.dataSourceForPrimarySelection, "UTF8_STRING"); + struct wl_callback *callback = wl_display_sync(_glfw.wl.display); + const static struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done}; + wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection); +} + +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) { + 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); + return _glfw.wl.pasteString; } } return NULL; diff --git a/glfw/x11_window.c b/glfw/x11_window.c index cdc8c063a..a6796904b 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -2680,6 +2680,29 @@ const char* _glfwPlatformGetClipboardString(void) return getSelectionString(_glfw.x11.CLIPBOARD); } +void _glfwPlatformSetPrimarySelectionString(const char* string) +{ + free(_glfw.x11.primarySelectionString); + _glfw.x11.primarySelectionString = _glfw_strdup(string); + + XSetSelectionOwner(_glfw.x11.display, + _glfw.x11.PRIMARY, + _glfw.x11.helperWindowHandle, + CurrentTime); + + if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) != + _glfw.x11.helperWindowHandle) + { + _glfwInputError(GLFW_PLATFORM_ERROR, + "X11: Failed to become owner of primary selection"); + } +} + +const char* _glfwPlatformGetPrimarySelectionString(void) +{ + return getSelectionString(_glfw.x11.PRIMARY); +} + void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) { if (!_glfw.vk.KHR_surface) @@ -2851,32 +2874,6 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle) return window->x11.handle; } -GLFWAPI void glfwSetX11SelectionString(const char* string) -{ - _GLFW_REQUIRE_INIT(); - - free(_glfw.x11.primarySelectionString); - _glfw.x11.primarySelectionString = _glfw_strdup(string); - - XSetSelectionOwner(_glfw.x11.display, - _glfw.x11.PRIMARY, - _glfw.x11.helperWindowHandle, - CurrentTime); - - if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) != - _glfw.x11.helperWindowHandle) - { - _glfwInputError(GLFW_PLATFORM_ERROR, - "X11: Failed to become owner of primary selection"); - } -} - -GLFWAPI const char* glfwGetX11SelectionString(void) -{ - _GLFW_REQUIRE_INIT_OR_RETURN(NULL); - return getSelectionString(_glfw.x11.PRIMARY); -} - GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) { return glfw_xkb_keysym_from_name(keyName, caseSensitive); } diff --git a/kitty/constants.py b/kitty/constants.py index abefeb898..eef92db97 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -125,4 +125,4 @@ if os.environ.get('WAYLAND_DISPLAY') and 'KITTY_ENABLE_WAYLAND' in os.environ an is_wayland = True -supports_primary_selection = not is_macos and not is_wayland +supports_primary_selection = not is_macos diff --git a/kitty/glfw-wrapper.c b/kitty/glfw-wrapper.c index 1772cb262..0d52870be 100644 --- a/kitty/glfw-wrapper.c +++ b/kitty/glfw-wrapper.c @@ -377,9 +377,9 @@ load_glfw(const char* path) { *(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window"); - *(void **) (&glfwSetX11SelectionString_impl) = dlsym(handle, "glfwSetX11SelectionString"); + *(void **) (&glfwSetPrimarySelectionString_impl) = dlsym(handle, "glfwSetPrimarySelectionString"); - *(void **) (&glfwGetX11SelectionString_impl) = dlsym(handle, "glfwGetX11SelectionString"); + *(void **) (&glfwGetPrimarySelectionString_impl) = dlsym(handle, "glfwGetPrimarySelectionString"); *(void **) (&glfwGetXKBScancode_impl) = dlsym(handle, "glfwGetXKBScancode"); diff --git a/kitty/glfw-wrapper.h b/kitty/glfw-wrapper.h index e0634f008..cc06bd769 100644 --- a/kitty/glfw-wrapper.h +++ b/kitty/glfw-wrapper.h @@ -1884,13 +1884,13 @@ typedef int32_t (*glfwGetX11Window_func)(GLFWwindow*); glfwGetX11Window_func glfwGetX11Window_impl; #define glfwGetX11Window glfwGetX11Window_impl -typedef void (*glfwSetX11SelectionString_func)(const char*); -glfwSetX11SelectionString_func glfwSetX11SelectionString_impl; -#define glfwSetX11SelectionString glfwSetX11SelectionString_impl +typedef void (*glfwSetPrimarySelectionString_func)(GLFWwindow*, const char*); +glfwSetPrimarySelectionString_func glfwSetPrimarySelectionString_impl; +#define glfwSetPrimarySelectionString glfwSetPrimarySelectionString_impl -typedef const char* (*glfwGetX11SelectionString_func)(); -glfwGetX11SelectionString_func glfwGetX11SelectionString_impl; -#define glfwGetX11SelectionString glfwGetX11SelectionString_impl +typedef const char* (*glfwGetPrimarySelectionString_func)(GLFWwindow*); +glfwGetPrimarySelectionString_func glfwGetPrimarySelectionString_impl; +#define glfwGetPrimarySelectionString glfwGetPrimarySelectionString_impl typedef int (*glfwGetXKBScancode_func)(const char*, int); glfwGetXKBScancode_func glfwGetXKBScancode_impl; diff --git a/kitty/glfw.c b/kitty/glfw.c index c800c5de2..4bedfff61 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -889,9 +889,10 @@ x11_window_id(PyObject UNUSED *self, PyObject *os_wid) { static PyObject* get_primary_selection(PYNOARG) { - if (glfwGetX11SelectionString) { - return Py_BuildValue("y", glfwGetX11SelectionString()); - } else log_error("Failed to load glfwGetX11SelectionString"); + if (glfwGetPrimarySelectionString) { + OSWindow *w = current_os_window(); + if (w) return Py_BuildValue("y", glfwGetPrimarySelectionString(w->handle)); + } else log_error("Failed to load glfwGetPrimarySelectionString"); Py_RETURN_NONE; } @@ -899,8 +900,11 @@ static PyObject* set_primary_selection(PyObject UNUSED *self, PyObject *args) { char *text; if (!PyArg_ParseTuple(args, "s", &text)) return NULL; - if (glfwSetX11SelectionString) glfwSetX11SelectionString(text); - else log_error("Failed to load glfwSetX11SelectionString"); + if (glfwSetPrimarySelectionString) { + OSWindow *w = current_os_window(); + if (w) glfwSetPrimarySelectionString(w->handle, text); + } + else log_error("Failed to load glfwSetPrimarySelectionString"); Py_RETURN_NONE; }