mirror of
https://github.com/glfw/glfw.git
synced 2025-10-05 06:06:36 +00:00
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:
parent
e376404d38
commit
8e6b7c72f7
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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_;
|
||||
|
185
src/x11_window.c
185
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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user