diff options
| author | Andre Weissflog <floooh@gmail.com> | 2024-09-17 19:08:53 +0200 |
|---|---|---|
| committer | Andre Weissflog <floooh@gmail.com> | 2024-09-17 19:08:53 +0200 |
| commit | 15412a3c03a6a799d267599c5378a7422b14ab58 (patch) | |
| tree | 49948f83ed6280f6625bb9f8d9af5598337059a2 | |
| parent | f62817137961a1eedc3eac03f62012795d2c79c4 (diff) | |
| parent | b19ab8b92b7350ed7f63d24bccec43e51d374d16 (diff) | |
Merge branch 'qwx9-master'
| -rw-r--r-- | CHANGELOG.md | 8 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | sokol_app.h | 126 |
3 files changed, 134 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e685621..05b7bd4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ ## Updates +### 17-Sep-2024 + +- The sokol_app.h Linux backend now has clipboard support. Many thanks to + @Dvad for the initial PR with most of the work and @qwx9 for the addtional + updates. See PR https://github.com/floooh/sokol/pull/1108 for details (this + isn't quite what ended up in sokol_app.h, because I did a couple of code + cleanup changes in during the merge). + ### 10-Sep-2024 - Update sokol_imgui.h for Dear ImGui and cimgui version 1.91.1. This @@ -6,7 +6,7 @@ # Sokol -[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**02-Sep-2024** in sokol_gfx.h, SG_FILTER_NONE has been removed) +[**See what's new**](https://github.com/floooh/sokol/blob/master/CHANGELOG.md) (**17-Sep-2024** sokol_app.h: clipboard support for Linux) [](/../../actions/workflows/main.yml) [](/../../actions/workflows/gen_bindings.yml) [](https://github.com/floooh/sokol-zig/actions/workflows/main.yml) [](https://github.com/floooh/sokol-nim/actions/workflows/main.yml) [](https://github.com/floooh/sokol-odin/actions/workflows/main.yml)[](https://github.com/floooh/sokol-rust/actions/workflows/main.yml)[](https://github.com/kassane/sokol-d/actions/workflows/build.yml) diff --git a/sokol_app.h b/sokol_app.h index 7cd29bf8..97f72ab3 100644 --- a/sokol_app.h +++ b/sokol_app.h @@ -130,7 +130,7 @@ screen keyboard | --- | --- | --- | YES | TODO | YES swap interval | YES | YES | YES | YES | TODO | YES high-dpi | YES | YES | TODO | YES | YES | YES - clipboard | YES | YES | TODO | --- | --- | YES + clipboard | YES | YES | YES | --- | --- | YES MSAA | YES | YES | YES | YES | YES | YES drag'n'drop | YES | YES | YES | --- | --- | YES window icon | YES | YES(1)| YES | --- | --- | YES @@ -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 <limits.h> /* LONG_MAX */ #include <pthread.h> /* only used a linker-guard, search for _sapp_linux_run() and see first comment */ #include <time.h> + #include <poll.h> #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; @@ -9618,6 +9622,8 @@ _SOKOL_PRIVATE void _sapp_x11_init_extensions(void) { _sapp.x11.NET_WM_ICON = XInternAtom(_sapp.x11.display, "_NET_WM_ICON", False); _sapp.x11.NET_WM_STATE = XInternAtom(_sapp.x11.display, "_NET_WM_STATE", False); _sapp.x11.NET_WM_STATE_FULLSCREEN = XInternAtom(_sapp.x11.display, "_NET_WM_STATE_FULLSCREEN", False); + _sapp.x11.CLIPBOARD = XInternAtom(_sapp.x11.display, "CLIPBOARD", False); + _sapp.x11.TARGETS = XInternAtom(_sapp.x11.display, "TARGETS", False); if (_sapp.drop.enabled) { _sapp.x11.xdnd.XdndAware = XInternAtom(_sapp.x11.display, "XdndAware", False); _sapp.x11.xdnd.XdndEnter = XInternAtom(_sapp.x11.display, "XdndEnter", False); @@ -10463,6 +10469,75 @@ _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, @@ -11202,6 +11277,48 @@ _SOKOL_PRIVATE void _sapp_x11_on_clientmessage(XEvent* event) { } } +_SOKOL_PRIVATE void _sapp_x11_on_selectionrequest(XEvent* event) { + XSelectionRequestEvent* req = &event->xselectionrequest; + if (req->selection != _sapp.x11.CLIPBOARD) { + return; + } + if (!_sapp.clipboard.enabled) { + return; + } + SOKOL_ASSERT(_sapp.clipboard.buffer); + XSelectionEvent reply; + _sapp_clear(&reply, sizeof(reply)); + reply.type = SelectionNotify; + reply.display = req->display; + reply.requestor = req->requestor; + reply.selection = req->selection; + reply.target = req->target; + reply.property = req->property; + reply.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); +} + _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { switch (event->type) { case GenericEvent: @@ -11243,6 +11360,9 @@ _SOKOL_PRIVATE void _sapp_x11_process_event(XEvent* event) { case SelectionNotify: _sapp_x11_on_selectionnotify(event); break; + case SelectionRequest: + _sapp_x11_on_selectionrequest(event); + break; case DestroyNotify: // not a bug break; @@ -11723,6 +11843,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 +11861,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; |