aboutsummaryrefslogtreecommitdiff
path: root/sokol_app.h
diff options
context:
space:
mode:
authorqwx <qwx@sciops.net>2024-09-12 10:58:28 +0200
committerqwx <qwx@sciops.net>2024-09-12 18:33:52 +0200
commitf07eda195dfe073d7fea675de3c181f88e22872e (patch)
tree7f73858b411d66760588b4f7fcd6ef04e3990065 /sokol_app.h
parent1eb96dd0f96b9ea73065f9078244c2255c2b75d9 (diff)
sokol_app: x11 clipboard support
based on #998 and https://github.com/X11-good-tools/x11clipboard
Diffstat (limited to 'sokol_app.h')
-rw-r--r--sokol_app.h124
1 files changed, 123 insertions, 1 deletions
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 <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;
@@ -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;