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 <knielsen@knielsen-hq.org>
This commit is contained in:
Kristian Nielsen 2017-08-02 16:10:13 +02:00 committed by Camilla Löwy
parent 3ee7f8f695
commit 29a75ab09d
6 changed files with 180 additions and 72 deletions

View File

@ -124,6 +124,9 @@ information on what to include when reporting a bug.
## Changelog ## 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 - Added `glfwGetError` function for querying the last error code and its
description (#970) description (#970)
- Added `glfwUpdateGamepadMappings` function for importing gamepad mappings in - Added `glfwUpdateGamepadMappings` function for importing gamepad mappings in

View File

@ -5,6 +5,15 @@
@section news_33 Release notes for 3.3 @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 @subsection news_33_geterror Error query
GLFW now supports querying the last error code for the calling thread and its GLFW now supports querying the last error code for the calling thread and its

View File

@ -289,6 +289,56 @@ GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* monitor);
* @ingroup native * @ingroup native
*/ */
GLFWAPI Window glfwGetX11Window(GLFWwindow* window); 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 #endif
#if defined(GLFW_EXPOSE_NATIVE_GLX) #if defined(GLFW_EXPOSE_NATIVE_GLX)

View File

@ -611,6 +611,7 @@ static GLFWbool initExtensions(void)
// ICCCM standard clipboard atoms // ICCCM standard clipboard atoms
_glfw.x11.TARGETS = XInternAtom(_glfw.x11.display, "TARGETS", False); _glfw.x11.TARGETS = XInternAtom(_glfw.x11.display, "TARGETS", False);
_glfw.x11.MULTIPLE = XInternAtom(_glfw.x11.display, "MULTIPLE", 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); _glfw.x11.CLIPBOARD = XInternAtom(_glfw.x11.display, "CLIPBOARD", False);
// Clipboard manager atoms // Clipboard manager atoms
@ -853,6 +854,7 @@ void _glfwPlatformTerminate(void)
_glfw.x11.hiddenCursorHandle = (Cursor) 0; _glfw.x11.hiddenCursorHandle = (Cursor) 0;
} }
free(_glfw.x11.primarySelectionString);
free(_glfw.x11.clipboardString); free(_glfw.x11.clipboardString);
if (_glfw.x11.im) if (_glfw.x11.im)

View File

@ -161,6 +161,8 @@ typedef struct _GLFWlibraryX11
XIM im; XIM im;
// Most recent error code received by X error handler // Most recent error code received by X error handler
int errorCode; int errorCode;
// Primary selection string (while the primary selection is owned)
char* primarySelectionString;
// Clipboard string (while the selection is owned) // Clipboard string (while the selection is owned)
char* clipboardString; char* clipboardString;
// Key name string // Key name string
@ -214,6 +216,7 @@ typedef struct _GLFWlibraryX11
Atom TARGETS; Atom TARGETS;
Atom MULTIPLE; Atom MULTIPLE;
Atom CLIPBOARD; Atom CLIPBOARD;
Atom PRIMARY;
Atom CLIPBOARD_MANAGER; Atom CLIPBOARD_MANAGER;
Atom SAVE_TARGETS; Atom SAVE_TARGETS;
Atom NULL_; Atom NULL_;

View File

@ -675,6 +675,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
_glfw.x11.COMPOUND_STRING, _glfw.x11.COMPOUND_STRING,
XA_STRING }; XA_STRING };
const int formatCount = sizeof(formats) / sizeof(formats[0]); 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) if (request->property == None)
{ {
@ -735,8 +737,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
targets[i], targets[i],
8, 8,
PropModeReplace, PropModeReplace,
(unsigned char*) _glfw.x11.clipboardString, (unsigned char *) selectionString,
strlen(_glfw.x11.clipboardString)); strlen(selectionString));
} }
else else
targets[i + 1] = None; targets[i + 1] = None;
@ -787,8 +789,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
request->target, request->target,
8, 8,
PropModeReplace, PropModeReplace,
(unsigned char*) _glfw.x11.clipboardString, (unsigned char *) selectionString,
strlen(_glfw.x11.clipboardString)); strlen(selectionString));
return request->property; return request->property;
} }
@ -801,8 +803,17 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
static void handleSelectionClear(XEvent* event) static void handleSelectionClear(XEvent* event)
{ {
free(_glfw.x11.clipboardString); const XSelectionClearEvent* request = &event->xselectionclear;
_glfw.x11.clipboardString = NULL; 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) static void handleSelectionRequest(XEvent* event)
@ -823,6 +834,76 @@ static void handleSelectionRequest(XEvent* event)
XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply); 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 // Make the specified window and its video mode active on its monitor
// //
static GLFWbool acquireMonitor(_GLFWwindow* window) static GLFWbool acquireMonitor(_GLFWwindow* window)
@ -2572,72 +2653,7 @@ void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string)
const char* _glfwPlatformGetClipboardString(_GLFWwindow* window) const char* _glfwPlatformGetClipboardString(_GLFWwindow* window)
{ {
size_t i; return getSelection(_glfw.x11.CLIPBOARD, &_glfw.x11.clipboardString);
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;
} }
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions) void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
@ -2807,3 +2823,28 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle)
return window->x11.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);
}