Wayland: Implement support for clipboard copy/paste

Fix #855
This commit is contained in:
Kovid Goyal 2018-09-05 17:54:26 +05:30
parent bbe8bcb28d
commit 128b9e1cd0
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 161 additions and 9 deletions

View File

@ -15,6 +15,8 @@ Changelog
- Linux: Fix numpad arrow keys not working when num lock is off (:iss:`857`) - Linux: Fix numpad arrow keys not working when num lock is off (:iss:`857`)
- Wayland: Implement support for clipboard copy/paste (:iss:`855`)
- Allow mapping shortcuts using the raw key code from the OS (:iss:`848`) - Allow mapping shortcuts using the raw key code from the OS (:iss:`848`)
- Allow mapping of individual keypresses without modifiers as shortcuts - Allow mapping of individual keypresses without modifiers as shortcuts

2
glfw/internal.h vendored
View File

@ -31,6 +31,8 @@
#include "glfw_config.h" #include "glfw_config.h"
#endif #endif
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
#if defined(GLFW_INCLUDE_GLCOREARB) || \ #if defined(GLFW_INCLUDE_GLCOREARB) || \
defined(GLFW_INCLUDE_ES1) || \ defined(GLFW_INCLUDE_ES1) || \
defined(GLFW_INCLUDE_ES2) || \ defined(GLFW_INCLUDE_ES2) || \

13
glfw/wl_init.c vendored
View File

@ -560,6 +560,9 @@ static void registryHandleGlobal(void* data,
_glfw.wl.seatVersion); _glfw.wl.seatVersion);
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL); wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
} }
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
_glfwSetupWaylandDataDevice();
}
} }
else if (strcmp(interface, "xdg_wm_base") == 0) else if (strcmp(interface, "xdg_wm_base") == 0)
{ {
@ -599,6 +602,9 @@ static void registryHandleGlobal(void* data,
wl_registry_bind(registry, name, wl_registry_bind(registry, name,
&wl_data_device_manager_interface, &wl_data_device_manager_interface,
1); 1);
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
_glfwSetupWaylandDataDevice();
}
} }
} }
@ -771,6 +777,11 @@ void _glfwPlatformTerminate(void)
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager); zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
if (_glfw.wl.dataSourceForClipboard) if (_glfw.wl.dataSourceForClipboard)
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++) {
if (_glfw.wl.dataOffers[doi].id) {
wl_data_offer_destroy(_glfw.wl.dataOffers[doi].id);
}
}
if (_glfw.wl.dataDevice) if (_glfw.wl.dataDevice)
wl_data_device_destroy(_glfw.wl.dataDevice); wl_data_device_destroy(_glfw.wl.dataDevice);
if (_glfw.wl.dataDeviceManager) if (_glfw.wl.dataDeviceManager)
@ -784,6 +795,8 @@ void _glfwPlatformTerminate(void)
} }
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0])); 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.clipboardString); _glfw.wl.clipboardString = NULL;
free(_glfw.wl.clipboardSourceString); _glfw.wl.clipboardSourceString = NULL;
} }
const char* _glfwPlatformGetVersionString(void) const char* _glfwPlatformGetVersionString(void)

13
glfw/wl_platform.h vendored
View File

@ -175,6 +175,14 @@ typedef struct _GLFWwindowWayland
} _GLFWwindowWayland; } _GLFWwindowWayland;
typedef struct _GLFWWaylandDataOffer
{
struct wl_data_offer *id;
const char *mime;
int offer_type;
size_t idx;
} _GLFWWaylandDataOffer;
// Wayland-specific global data // Wayland-specific global data
// //
typedef struct _GLFWlibraryWayland typedef struct _GLFWlibraryWayland
@ -236,6 +244,10 @@ typedef struct _GLFWlibraryWayland
EventLoopData eventLoopData; EventLoopData eventLoopData;
char* clipboardString; char* clipboardString;
char* clipboardSourceString;
struct wl_data_offer* clipboardSourceOffer;
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
} _GLFWlibraryWayland; } _GLFWlibraryWayland;
// Wayland-specific per-monitor data // Wayland-specific per-monitor data
@ -264,3 +276,4 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version); void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwSetupWaylandDataDevice();

138
glfw/wl_window.c vendored
View File

@ -1457,10 +1457,126 @@ static void _glfwSendClipboardText(void *data, struct wl_data_source *data_sourc
close(fd); close(fd);
} }
const struct wl_data_source_listener data_source_listener = { 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;
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
wl_data_offer_receive(data_offer, mime, pipefd[1]);
close(pipefd[1]);
wl_display_flush(_glfw.wl.display);
size_t sz = 0, capacity = 0;
free(_glfw.wl.clipboardSourceString);
_glfw.wl.clipboardSourceString = NULL;
struct pollfd fds;
fds.fd = pipefd[0];
fds.events = POLLIN;
double start = glfwGetTime();
#define bail(...) { \
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
free(_glfw.wl.clipboardSourceString); _glfw.wl.clipboardSourceString = NULL; \
close(pipefd[0]); \
return NULL; \
}
while (glfwGetTime() - start < 2) {
int ret = poll(&fds, 1, 2000);
if (ret == -1) {
if (errno == EINTR) continue;
bail("Wayland: Failed to poll clipboard data from pipe with error: %s", strerror(errno));
}
if (!ret) {
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
}
if (capacity <= sz || capacity - sz <= 64) {
capacity += 4096;
_glfw.wl.clipboardSourceString = realloc(_glfw.wl.clipboardSourceString, capacity);
if (!_glfw.wl.clipboardSourceString) {
bail("Wayland: Failed to allocate memory to read clipboard data");
}
}
ret = read(pipefd[0], _glfw.wl.clipboardSourceString + 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]); _glfw.wl.clipboardSourceString[sz] = 0; return _glfw.wl.clipboardSourceString; }
sz += ret;
start = glfwGetTime();
}
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
#undef bail
}
const static struct wl_data_source_listener data_source_listener = {
.send = _glfwSendClipboardText, .send = _glfwSendClipboardText,
}; };
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) {
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[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
}
}
prune_unclaimed_data_offers();
}
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"))
_glfw.wl.dataOffers[i].mime = "text/plain";
break;
}
}
}
static const struct wl_data_offer_listener data_offer_listener = {
.offer = handle_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++) {
if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) {
smallest_idx = _glfw.wl.dataOffers[i].idx;
pos = i;
}
if (_glfw.wl.dataOffers[i].id == NULL) {
_glfw.wl.dataOffers[i].id = id;
_glfw.wl.dataOffers[i].idx = ++_glfw.wl.dataOffersCounter;
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;
end:
wl_data_offer_add_listener(id, &data_offer_listener, NULL);
}
const static struct wl_data_device_listener data_device_listener = {
.data_offer = handle_data_offer,
.selection = mark_selection_offer,
};
static void static void
copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) { copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
if (!_glfw.wl.dataDevice) return; if (!_glfw.wl.dataDevice) return;
@ -1469,15 +1585,20 @@ copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
} }
} }
const struct wl_callback_listener copy_callback_listener = { const static struct wl_callback_listener copy_callback_listener = {
.done = copy_callback_done .done = copy_callback_done
}; };
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);
}
static inline GLFWbool _glfwEnsureDataDevice() { static inline GLFWbool _glfwEnsureDataDevice() {
if (!_glfw.wl.dataDeviceManager) if (!_glfw.wl.dataDeviceManager)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot use clipboard data device manager is not ready"); "Wayland: Cannot use clipboard, data device manager is not ready");
return GLFW_FALSE; return GLFW_FALSE;
} }
@ -1486,10 +1607,9 @@ static inline GLFWbool _glfwEnsureDataDevice() {
if (!_glfw.wl.seat) if (!_glfw.wl.seat)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot use clipboard seat is not ready"); "Wayland: Cannot use clipboard, seat is not ready");
return GLFW_FALSE; return GLFW_FALSE;
} }
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
if (!_glfw.wl.dataDevice) if (!_glfw.wl.dataDevice)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,
@ -1526,9 +1646,11 @@ void _glfwPlatformSetClipboardString(const char* string)
const char* _glfwPlatformGetClipboardString(void) const char* _glfwPlatformGetClipboardString(void)
{ {
// TODO for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
_glfwInputError(GLFW_PLATFORM_ERROR, if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == 1) {
"Wayland: Clipboard getting not implemented yet"); return _glfwReceiveClipboardText(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
}
}
return NULL; return NULL;
} }