From 29a75ab09dab1984e7da19f6d4ce36c00a1d61ff Mon Sep 17 00:00:00 2001 From: Kristian Nielsen Date: Wed, 2 Aug 2017 16:10:13 +0200 Subject: [PATCH] X11: Add native access to primary selection This adds the native access functions glfwSetX11SelectionString and glfwGetX11SelectionString under GLFW_EXPOSE_NATIVE_X11. They are similar to glfwSetClipboardString and glfwGetClipboardString but operate on the PRIMARY selection. The primary selection is widely used in X11, and so seems important to support. Primary selection is mostly an X11-specific thing, hence it's exposed as an X11 native interface. Fixes #894. Closes #1056. Signed-off-by: Kristian Nielsen --- README.md | 3 + docs/news.dox | 9 ++ include/GLFW/glfw3native.h | 50 ++++++++++ src/x11_init.c | 2 + src/x11_platform.h | 3 + src/x11_window.c | 185 ++++++++++++++++++++++--------------- 6 files changed, 180 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 41173f0f..e24293b5 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,9 @@ information on what to include when reporting a bug. ## Changelog +- Added `glfwSetX11SelectionString` and `glfwGetX11SelectionString` + native functions for accessing X11 primary selection as a supplement to + the clipboard on the X11 platform (#894) - Added `glfwGetError` function for querying the last error code and its description (#970) - Added `glfwUpdateGamepadMappings` function for importing gamepad mappings in diff --git a/docs/news.dox b/docs/news.dox index d8bb23a5..0fdb5a73 100644 --- a/docs/news.dox +++ b/docs/news.dox @@ -5,6 +5,15 @@ @section news_33 Release notes for 3.3 +@subsection news_33_native_x11_selection X11 native Primary Selection access + +GLFW now supports X11 platform specific native functions for accessing +the X11 primary selection, as a supplement to the clipboard on this +platform. See @ref clipboard. + +@see @ref error_handling + + @subsection news_33_geterror Error query GLFW now supports querying the last error code for the calling thread and its diff --git a/include/GLFW/glfw3native.h b/include/GLFW/glfw3native.h index 982eaa65..ad7ccb57 100644 --- a/include/GLFW/glfw3native.h +++ b/include/GLFW/glfw3native.h @@ -289,6 +289,56 @@ GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* monitor); * @ingroup native */ GLFWAPI Window glfwGetX11Window(GLFWwindow* window); + +/*! @brief Sets the current primary selection to the specified string. + * + * @param[in] string A UTF-8 encoded string. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref + * GLFW_PLATFORM_ERROR. + * + * @pointer_lifetime The specified string is copied before this function + * returns. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref clipboard + * @sa glfwGetX11SelectionString + * @sa glfwSetClipboardString + * + * @since Added in version 3.3. + * + * @ingroup native + */ +GLFWAPI void glfwSetX11SelectionString(const char* string); + +/*! @brief Returns the contents of the current primary selection as a string. + * + * If the selection is empty or if its contents cannot be converted, `NULL` + * is returned and a @ref GLFW_FORMAT_UNAVAILABLE error is generated. + * + * @return The contents of the selection as a UTF-8 encoded string, or `NULL` + * if an [error](@ref error_handling) occurred. + * + * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref + * GLFW_PLATFORM_ERROR. + * + * @pointer_lifetime The returned string is allocated and freed by GLFW. You + * should not free it yourself. It is valid until the next call to @ref + * glfwGetX11SelectionString or @ref glfwSetX11SelectionString, or until the + * library is terminated. + * + * @thread_safety This function must only be called from the main thread. + * + * @sa @ref clipboard + * @sa glfwSetX11SelectionString + * @sa glfwGetClipboardString + * + * @since Added in version 3.3. + * + * @ingroup native + */ +GLFWAPI const char* glfwGetX11SelectionString(void); #endif #if defined(GLFW_EXPOSE_NATIVE_GLX) diff --git a/src/x11_init.c b/src/x11_init.c index 402552bb..7acea707 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -611,6 +611,7 @@ static GLFWbool initExtensions(void) // ICCCM standard clipboard atoms _glfw.x11.TARGETS = XInternAtom(_glfw.x11.display, "TARGETS", False); _glfw.x11.MULTIPLE = XInternAtom(_glfw.x11.display, "MULTIPLE", False); + _glfw.x11.PRIMARY = XInternAtom(_glfw.x11.display, "PRIMARY", False); _glfw.x11.CLIPBOARD = XInternAtom(_glfw.x11.display, "CLIPBOARD", False); // Clipboard manager atoms @@ -853,6 +854,7 @@ void _glfwPlatformTerminate(void) _glfw.x11.hiddenCursorHandle = (Cursor) 0; } + free(_glfw.x11.primarySelectionString); free(_glfw.x11.clipboardString); if (_glfw.x11.im) diff --git a/src/x11_platform.h b/src/x11_platform.h index e56630cf..e28127bc 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -161,6 +161,8 @@ typedef struct _GLFWlibraryX11 XIM im; // Most recent error code received by X error handler int errorCode; + // Primary selection string (while the primary selection is owned) + char* primarySelectionString; // Clipboard string (while the selection is owned) char* clipboardString; // Key name string @@ -214,6 +216,7 @@ typedef struct _GLFWlibraryX11 Atom TARGETS; Atom MULTIPLE; Atom CLIPBOARD; + Atom PRIMARY; Atom CLIPBOARD_MANAGER; Atom SAVE_TARGETS; Atom NULL_; diff --git a/src/x11_window.c b/src/x11_window.c index cdaac5f8..eacb7467 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -675,6 +675,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request) _glfw.x11.COMPOUND_STRING, XA_STRING }; const int formatCount = sizeof(formats) / sizeof(formats[0]); + char *selectionString = request->selection == _glfw.x11.PRIMARY ? + _glfw.x11.primarySelectionString : _glfw.x11.clipboardString; if (request->property == None) { @@ -735,8 +737,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request) targets[i], 8, PropModeReplace, - (unsigned char*) _glfw.x11.clipboardString, - strlen(_glfw.x11.clipboardString)); + (unsigned char *) selectionString, + strlen(selectionString)); } else targets[i + 1] = None; @@ -787,8 +789,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request) request->target, 8, PropModeReplace, - (unsigned char*) _glfw.x11.clipboardString, - strlen(_glfw.x11.clipboardString)); + (unsigned char *) selectionString, + strlen(selectionString)); return request->property; } @@ -801,8 +803,17 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request) static void handleSelectionClear(XEvent* event) { - free(_glfw.x11.clipboardString); - _glfw.x11.clipboardString = NULL; + const XSelectionClearEvent* request = &event->xselectionclear; + if (request->selection == _glfw.x11.PRIMARY) + { + free(_glfw.x11.primarySelectionString); + _glfw.x11.primarySelectionString = NULL; + } + else if (request->selection == _glfw.x11.CLIPBOARD) + { + free(_glfw.x11.clipboardString); + _glfw.x11.clipboardString = NULL; + } } static void handleSelectionRequest(XEvent* event) @@ -823,6 +834,76 @@ static void handleSelectionRequest(XEvent* event) XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); } +static const char *getSelection(Atom selection, char **ptr) +{ + size_t i; + const Atom formats[] = { _glfw.x11.UTF8_STRING, + _glfw.x11.COMPOUND_STRING, + XA_STRING }; + const size_t formatCount = sizeof(formats) / sizeof(formats[0]); + + if (XGetSelectionOwner(_glfw.x11.display, selection) == + _glfw.x11.helperWindowHandle) + { + // 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 *ptr; + } + + free(*ptr); + *ptr = NULL; + + for (i = 0; i < formatCount; i++) + { + char* data; + XEvent event; + + XConvertSelection(_glfw.x11.display, + selection, + formats[i], + _glfw.x11.GLFW_SELECTION, + _glfw.x11.helperWindowHandle, + CurrentTime); + + while (!XCheckTypedWindowEvent(_glfw.x11.display, + _glfw.x11.helperWindowHandle, + SelectionNotify, + &event)) + { + waitForEvent(NULL); + } + + if (event.xselection.property == None) + continue; + + if (_glfwGetWindowPropertyX11(event.xselection.requestor, + event.xselection.property, + event.xselection.target, + (unsigned char**) &data)) + { + *ptr = strdup(data); + } + + if (data) + XFree(data); + + XDeleteProperty(_glfw.x11.display, + event.xselection.requestor, + event.xselection.property); + + if (*ptr) + break; + } + + if (*ptr == NULL) + { + _glfwInputError(GLFW_FORMAT_UNAVAILABLE, + "X11: Failed to convert clipboard to string"); + } + + return *ptr; +} + // Make the specified window and its video mode active on its monitor // static GLFWbool acquireMonitor(_GLFWwindow* window) @@ -2572,72 +2653,7 @@ void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string) const char* _glfwPlatformGetClipboardString(_GLFWwindow* window) { - size_t i; - const Atom formats[] = { _glfw.x11.UTF8_STRING, - _glfw.x11.COMPOUND_STRING, - XA_STRING }; - const size_t formatCount = sizeof(formats) / sizeof(formats[0]); - - if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) == - _glfw.x11.helperWindowHandle) - { - // 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 _glfw.x11.clipboardString; - } - - free(_glfw.x11.clipboardString); - _glfw.x11.clipboardString = NULL; - - for (i = 0; i < formatCount; i++) - { - char* data; - XEvent event; - - XConvertSelection(_glfw.x11.display, - _glfw.x11.CLIPBOARD, - formats[i], - _glfw.x11.GLFW_SELECTION, - _glfw.x11.helperWindowHandle, - CurrentTime); - - while (!XCheckTypedWindowEvent(_glfw.x11.display, - _glfw.x11.helperWindowHandle, - SelectionNotify, - &event)) - { - waitForEvent(NULL); - } - - if (event.xselection.property == None) - continue; - - if (_glfwGetWindowPropertyX11(event.xselection.requestor, - event.xselection.property, - event.xselection.target, - (unsigned char**) &data)) - { - _glfw.x11.clipboardString = strdup(data); - } - - if (data) - XFree(data); - - XDeleteProperty(_glfw.x11.display, - event.xselection.requestor, - event.xselection.property); - - if (_glfw.x11.clipboardString) - break; - } - - if (_glfw.x11.clipboardString == NULL) - { - _glfwInputError(GLFW_FORMAT_UNAVAILABLE, - "X11: Failed to convert clipboard to string"); - } - - return _glfw.x11.clipboardString; + return getSelection(_glfw.x11.CLIPBOARD, &_glfw.x11.clipboardString); } void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) @@ -2807,3 +2823,28 @@ 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 = 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 getSelection(_glfw.x11.PRIMARY, &_glfw.x11.primarySelectionString); +}