Implement access to X11 primary selection for GLFW

New X11 native functions glfwSetX11SelectionString() and
glfwGetX11SelectionString() are added under GLFW_EXPOSE_NATIVE_X11.
They are similar to glfwSetClipboardString() and
glfwGetClipboardString().

Primary selection is widely used in X11, and so seems important to
support. This patch fixes issue #894 which requests access to primary
selection. Primary selection is mostly an X11-specific thing, hence
exposed as an X11 native interface.

Signed-off-by: Kristian Nielsen <knielsen@knielsen-hq.org>
This commit is contained in:
Kristian Nielsen 2017-08-02 16:10:13 +02:00
parent e376404d38
commit 8e6b7c72f7
8 changed files with 219 additions and 72 deletions

View File

@ -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

View File

@ -868,6 +868,12 @@ The clipboard functions take a window handle argument because some window
systems require a window to communicate with the system clipboard. Any valid
window may be used.
X11 also has the concept of primary selection, which is similar to,
but distinct from, the clipboard. If this is needed on X11, it can be
accessed from the X11 native interface with the functions @ref
glfwGetX11SelectionString and @ref glfwSetX11SelectionString. See
@ref native.
@section path_drop Path drop input

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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_;

View File

@ -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)
{
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);
}

View File

@ -30,6 +30,12 @@
#include <glad/glad.h>
#include <GLFW/glfw3.h>
// Can use cmake -DCMAKE_C_FLAGS=-DGLFW_EXPOSE_NATIVE_X11 to test X11 native
// interface for primary selection.
#ifdef GLFW_EXPOSE_NATIVE_X11
#include <GLFW/glfw3native.h>
#endif
#include <stdio.h>
#include <stdlib.h>
@ -86,6 +92,30 @@ static void key_callback(GLFWwindow* window, int key, int scancode, int action,
}
}
#ifdef GLFW_EXPOSE_NATIVE_X11
static void mouse_button_callback(GLFWwindow *window, int button, int action, int mods)
{
if (action != GLFW_PRESS)
return;
if (button == GLFW_MOUSE_BUTTON_LEFT)
{
const char* string = "GLFW Selection";
glfwSetX11SelectionString(string);
printf("Setting selection to \"%s\"\n", string);
}
if (button == GLFW_MOUSE_BUTTON_MIDDLE)
{
const char* string;
string = glfwGetX11SelectionString();
if (string)
printf("Selection contains \"%s\"\n", string);
else
printf("Selection does not contain a string\n");
}
}
#endif
static void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
@ -132,6 +162,9 @@ int main(int argc, char** argv)
glfwSwapInterval(1);
glfwSetKeyCallback(window, key_callback);
#ifdef GLFW_EXPOSE_NATIVE_X11
glfwSetMouseButtonCallback(window, mouse_button_callback);
#endif
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glClearColor(0.5f, 0.5f, 0.5f, 0);