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); +}