From f07eda195dfe073d7fea675de3c181f88e22872e Mon Sep 17 00:00:00 2001 From: qwx Date: Thu, 12 Sep 2024 10:58:28 +0200 Subject: sokol_app: x11 clipboard support based on #998 and https://github.com/X11-good-tools/x11clipboard --- sokol_app.h | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/sokol_app.h b/sokol_app.h index 7cd29bf8..a3ecf25a 100644 --- a/sokol_app.h +++ b/sokol_app.h @@ -1603,6 +1603,7 @@ typedef struct sapp_allocator { _SAPP_LOGITEM_XMACRO(LINUX_X11_OPEN_DISPLAY_FAILED, "XOpenDisplay() failed") \ _SAPP_LOGITEM_XMACRO(LINUX_X11_QUERY_SYSTEM_DPI_FAILED, "failed to query system dpi value, assuming default 96.0") \ _SAPP_LOGITEM_XMACRO(LINUX_X11_DROPPED_FILE_URI_WRONG_SCHEME, "dropped file URL doesn't start with 'file://'") \ + _SAPP_LOGITEM_XMACRO(LINUX_X11_FAILED_TO_BECOME_OWNER_OF_CLIPBOARD, "X11: Failed to become owner of clipboard selection") \ _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_INPUT_CB, "unsupported input event encountered in _sapp_android_input_cb()") \ _SAPP_LOGITEM_XMACRO(ANDROID_UNSUPPORTED_INPUT_EVENT_MAIN_CB, "unsupported input event encountered in _sapp_android_main_cb()") \ _SAPP_LOGITEM_XMACRO(ANDROID_READ_MSG_FAILED, "failed to read message in _sapp_android_main_cb()") \ @@ -2162,6 +2163,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #include /* LONG_MAX */ #include /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ #include + #include #endif #if defined(_SAPP_APPLE) @@ -2752,6 +2754,8 @@ typedef struct { float dpi; unsigned char error_code; Atom UTF8_STRING; + Atom CLIPBOARD; + Atom TARGETS; Atom WM_PROTOCOLS; Atom WM_DELETE_WINDOW; Atom WM_STATE; @@ -9609,7 +9613,11 @@ _SOKOL_PRIVATE void _sapp_x11_release_error_handler(void) { } _SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { - _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); + if (_sapp.clipboard.enabled) { + _sapp.x11.UTF8_STRING = XInternAtom(_sapp.x11.display, "UTF8_STRING", False); + _sapp.x11.CLIPBOARD = XInternAtom(_sapp.x11.display, "CLIPBOARD", False); + _sapp.x11.TARGETS = XInternAtom(_sapp.x11.display, "TARGETS", False); + } _sapp.x11.WM_PROTOCOLS = XInternAtom(_sapp.x11.display, "WM_PROTOCOLS", False); _sapp.x11.WM_DELETE_WINDOW = XInternAtom(_sapp.x11.display, "WM_DELETE_WINDOW", False); _sapp.x11.WM_STATE = XInternAtom(_sapp.x11.display, "WM_STATE", False); @@ -10463,6 +10471,78 @@ _SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { XFlush(_sapp.x11.display); } +_SOKOL_PRIVATE void _sapp_x11_set_clipboard_string(const char* str) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + if (strlen(str) >= (size_t)_sapp.clipboard.buf_size) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + } + XSetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD, _sapp.x11.window, CurrentTime); + if (XGetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD) != _sapp.x11.window) { + _SAPP_ERROR(LINUX_X11_FAILED_TO_BECOME_OWNER_OF_CLIPBOARD); + } +} + +_SOKOL_PRIVATE const char* _sapp_x11_get_clipboard_string(void) { + SOKOL_ASSERT(_sapp.clipboard.enabled && _sapp.clipboard.buffer); + Atom none = XInternAtom(_sapp.x11.display, "SAPP_SELECTION", False); + Atom incremental = XInternAtom(_sapp.x11.display, "INCR", False); + if (XGetSelectionOwner(_sapp.x11.display, _sapp.x11.CLIPBOARD) == _sapp.x11.window) { + // Instead of doing a large number of X round-trips just to put this + // string into a window property and then read it back, just return it + return _sapp.clipboard.buffer; + } + XConvertSelection(_sapp.x11.display, + _sapp.x11.CLIPBOARD, + _sapp.x11.UTF8_STRING, + none, + _sapp.x11.window, + CurrentTime); + XEvent event; + while (!XCheckTypedWindowEvent(_sapp.x11.display, + _sapp.x11.window, + SelectionNotify, + &event)) { + // Wait for event data to arrive on the X11 display socket + struct pollfd fd = { ConnectionNumber(_sapp.x11.display), POLLIN }; + while (!XPending(_sapp.x11.display)) { + poll(&fd, 1, -1); + } + } + if (event.xselection.property == None) { + return NULL; + } + char* data = NULL; + Atom actualType; + int actualFormat; + unsigned long itemCount, bytesAfter; + const bool ret = XGetWindowProperty(_sapp.x11.display, + event.xselection.requestor, + event.xselection.property, + 0, + LONG_MAX, + True, + _sapp.x11.UTF8_STRING, + &actualType, + &actualFormat, + &itemCount, + &bytesAfter, + (unsigned char**) &data); + if (ret != Success || data == NULL) { + if (data != NULL) { + XFree(data); + } + return NULL; + } + if (actualType == incremental || itemCount >= (size_t)_sapp.clipboard.buf_size) { + _SAPP_ERROR(CLIPBOARD_STRING_TOO_BIG); + XFree(data); + return NULL; + } + _sapp_strcpy(data, _sapp.clipboard.buffer, _sapp.clipboard.buf_size); + XFree(data); + return _sapp.clipboard.buffer; +} + _SOKOL_PRIVATE void _sapp_x11_update_window_title(void) { Xutf8SetWMProperties(_sapp.x11.display, _sapp.x11.window, @@ -11243,6 +11323,44 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { case SelectionNotify: _sapp_x11_on_selectionnotify(event); break; + case SelectionRequest: { + XSelectionRequestEvent* req = &event->xselectionrequest; + if (req->selection != _sapp.x11.CLIPBOARD) { + break; + } + XSelectionEvent reply = { + .type = SelectionNotify, + .display = req->display, + .requestor = req->requestor, + .selection = req->selection, + .target = req->target, + .property = req->property, + .time = req->time + }; + if (req->target == _sapp.x11.UTF8_STRING) { + XChangeProperty(_sapp.x11.display, + req->requestor, + req->property, + _sapp.x11.UTF8_STRING, + 8, + PropModeReplace, + (unsigned char*) _sapp.clipboard.buffer, + strlen(_sapp.clipboard.buffer)); + } else if (req->target == _sapp.x11.TARGETS) { + XChangeProperty(_sapp.x11.display, + req->requestor, + req->property, + XA_ATOM, + 32, + PropModeReplace, + (unsigned char*) &_sapp.x11.UTF8_STRING, + 1); + } else { + reply.property = None; + } + XSendEvent(_sapp.x11.display, req->requestor, False, 0, (XEvent*) &reply); + break; + } case DestroyNotify: // not a bug break; @@ -11723,6 +11841,8 @@ SOKOL_API_IMPL void sapp_set_clipboard_string(const char* str) { _sapp_emsc_set_clipboard_string(str); #elif defined(_SAPP_WIN32) _sapp_win32_set_clipboard_string(str); + #elif defined(_SAPP_LINUX) + _sapp_x11_set_clipboard_string(str); #else /* not implemented */ #endif @@ -11739,6 +11859,8 @@ SOKOL_API_IMPL const char* sapp_get_clipboard_string(void) { return _sapp.clipboard.buffer; #elif defined(_SAPP_WIN32) return _sapp_win32_get_clipboard_string(); + #elif defined(_SAPP_LINUX) + return _sapp_x11_get_clipboard_string(); #else /* not implemented */ return _sapp.clipboard.buffer; -- cgit v1.2.3