diff --git a/include/GL/glfw3.h b/include/GL/glfw3.h index 21ed049a..7f054e5f 100644 --- a/include/GL/glfw3.h +++ b/include/GL/glfw3.h @@ -466,6 +466,7 @@ extern "C" { #define GLFW_PLATFORM_ERROR 0x00070008 #define GLFW_WINDOW_NOT_ACTIVE 0x00070009 #define GLFW_CLIPBOARD_FORMAT_UNAVAILABLE 0x00070010 +#define GLFW_CLIPBOARD_CANNOT_OWN 0x00070011 /* Gamma ramps */ #define GLFW_GAMMA_RAMP_SIZE 256 diff --git a/src/clipboard.c b/src/clipboard.c index 79cf680c..b055c168 100644 --- a/src/clipboard.c +++ b/src/clipboard.c @@ -71,5 +71,8 @@ GLFWAPI size_t glfwGetClipboardData(void *data, size_t size, int format) if (format == GLFW_CLIPBOARD_FORMAT_NONE) return 0; + if (!data || !size) + return 0; + return _glfwPlatformGetClipboardData(data, size, format); } diff --git a/src/x11_clipboard.c b/src/x11_clipboard.c index 467a0d52..1be0ac23 100644 --- a/src/x11_clipboard.c +++ b/src/x11_clipboard.c @@ -27,6 +27,8 @@ // //======================================================================== +// TODO: Incremental support? Overkill perhaps. + #include "internal.h" #include @@ -45,28 +47,101 @@ static Atom *getInternalFormat(int fmt) { // Get the necessary atoms - switch (fmt) { case GLFW_CLIPBOARD_FORMAT_STRING: - return _glfwLibrary.X11.selection.stringatoms; + return _glfwLibrary.X11.selection.atoms.string; default: return 0; } } +//======================================================================== +// X11 selection request event +//======================================================================== + +Atom _glfwSelectionRequest(XSelectionRequestEvent *request) +{ + Atom *atoms = _glfwLibrary.X11.selection.atoms.string; + if (request->target == XA_STRING) + { + // TODO: ISO Latin-1 specific characters don't get converted + // (yet). For cleanliness, would we need something like iconv? + XChangeProperty(_glfwLibrary.X11.display, + request->requestor, + request->target, + request->target, + 8, + PropModeReplace, + (unsigned char *)_glfwLibrary.X11.selection.clipboard.string, + 8); + } + else if (request->target == atoms[_GLFW_STRING_ATOM_COMPOUND] || + request->target == atoms[_GLFW_STRING_ATOM_UTF8]) + { + XChangeProperty(_glfwLibrary.X11.display, + request->requestor, + request->target, + request->target, + 8, + PropModeReplace, + (unsigned char *)_glfwLibrary.X11.selection.clipboard.string, + _glfwLibrary.X11.selection.clipboard.stringlen); + } + else + { + // TODO: Should we set an error? Probably not. + return None; + } + return request->target; +} + //======================================================================== // Set the clipboard contents //======================================================================== void _glfwPlatformSetClipboardData(void *data, size_t size, int format) { + switch (format) + { + case GLFW_CLIPBOARD_FORMAT_STRING: + { + // Allocate memory to keep track of the clipboard + char *cb = malloc(size+1); + // Copy the clipboard data + memcpy(cb, data, size); + + // Set the string length + _glfwLibrary.X11.selection.clipboard.stringlen = size; + + // Check if existing clipboard memory needs to be freed + if (_glfwLibrary.X11.selection.clipboard.string) + free(_glfwLibrary.X11.selection.clipboard.string); + + // Now set the clipboard (awaiting the event SelectionRequest) + _glfwLibrary.X11.selection.clipboard.string = cb; + break; + } + + default: + _glfwSetError(GLFW_CLIPBOARD_FORMAT_UNAVAILABLE, + "X11/GLX: Unavailable clipboard format"); + return; + } + + // Set the selection owner to our active window + XSetSelectionOwner(_glfwLibrary.X11.display, XA_PRIMARY, + _glfwLibrary.activeWindow->X11.handle, CurrentTime); + XSetSelectionOwner(_glfwLibrary.X11.display, + _glfwLibrary.X11.selection.atoms.clipboard + [_GLFW_CLIPBOARD_ATOM_CLIPBOARD], + _glfwLibrary.activeWindow->X11.handle, CurrentTime); + XFlush(_glfwLibrary.X11.display); } //======================================================================== // Return the current clipboard contents -// TODO: Incremental support? Overkill perhaps. //======================================================================== size_t _glfwPlatformGetClipboardData(void *data, size_t size, int format) @@ -74,46 +149,60 @@ size_t _glfwPlatformGetClipboardData(void *data, size_t size, int format) size_t len, rembytes, dummy; unsigned char *d; int fmt; - Window window; - Atom *xfmt, type; + Atom type; - // Try different formats that relate to the GLFW format with preference - // for better formats first - for (xfmt = getInternalFormat(format); *xfmt; xfmt++) + // Try different clipboards and formats that relate to the GLFW + // format with preference for more appropriate formats first + Atom *xcbrd = _glfwLibrary.X11.selection.atoms.clipboard; + Atom *xcbrdend = _glfwLibrary.X11.selection.atoms.clipboard + + _GLFW_CLIPBOARD_ATOM_COUNT; + Atom *xfmt = getInternalFormat(format); + Atom *xfmtend = xfmt + _GLFW_STRING_ATOM_COUNT; + + // Get the currently active window + Window window = _glfwLibrary.activeWindow->X11.handle; + + for (; xcbrd != xcbrdend; xcbrd++) { - // Specify the format we would like. - _glfwLibrary.X11.selection.request = *xfmt; - - // Convert the selection into a format we would like. - window = _glfwLibrary.activeWindow->X11.handle; - XConvertSelection(_glfwLibrary.X11.display, XA_PRIMARY, - *xfmt, None, window, - CurrentTime); - XFlush(_glfwLibrary.X11.display); - - // Process pending events until we get a SelectionNotify. - while (!_glfwLibrary.X11.selection.converted) - _glfwPlatformWaitEvents(); - - // If there is no owner to the selection/wrong request, bail out. - if (_glfwLibrary.X11.selection.converted == 2) + for (; xfmt != xfmtend; xfmt++) { - _glfwLibrary.X11.selection.converted = 0; - _glfwSetError(GLFW_CLIPBOARD_FORMAT_UNAVAILABLE, - "X11/GLX: Unavailable clipboard format"); - return 0; + // Specify the format we would like. + _glfwLibrary.X11.selection.request = *xfmt; + + // Convert the selection into a format we would like. + XConvertSelection(_glfwLibrary.X11.display, *xcbrd, + *xfmt, None, window, CurrentTime); + XFlush(_glfwLibrary.X11.display); + + // Process pending events until we get a SelectionNotify. + while (!_glfwLibrary.X11.selection.converted) + _glfwPlatformWaitEvents(); + + // Successful? + if (_glfwLibrary.X11.selection.converted == 1) + break; } - else // Right format, stop checking + + // Successful? + if (_glfwLibrary.X11.selection.converted == 1) { _glfwLibrary.X11.selection.converted = 0; break; } } + // Unsuccessful conversion, bail with no clipboard data + if (_glfwLibrary.X11.selection.converted) + { + _glfwSetError(GLFW_CLIPBOARD_FORMAT_UNAVAILABLE, + "X11/GLX: Unavailable clipboard format"); + return 0; + } + // Reset for the next selection _glfwLibrary.X11.selection.converted = 0; - // Check the length of data to receive + // Check the length of data to receive (rembytes) XGetWindowProperty(_glfwLibrary.X11.display, window, *xfmt, diff --git a/src/x11_init.c b/src/x11_init.c index a824528b..1fa549bd 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -446,13 +446,20 @@ static GLboolean initDisplay(void) // the keyboard mapping. updateKeyCodeLUT(); + // Find or create clipboard atoms + _glfwLibrary.X11.selection.atoms.clipboard[_GLFW_CLIPBOARD_ATOM_PRIMARY] = + XA_PRIMARY; + _glfwLibrary.X11.selection.atoms.clipboard[_GLFW_CLIPBOARD_ATOM_CLIPBOARD] = + XInternAtom(_glfwLibrary.X11.display, "CLIPBOARD", False); + _glfwLibrary.X11.selection.atoms.clipboard[_GLFW_CLIPBOARD_ATOM_SECONDARY] = + XA_SECONDARY; + // Find or create selection atoms - _glfwLibrary.X11.selection.stringatoms[0] = + _glfwLibrary.X11.selection.atoms.string[_GLFW_STRING_ATOM_UTF8] = XInternAtom(_glfwLibrary.X11.display, "UTF8_STRING", False); - _glfwLibrary.X11.selection.stringatoms[1] = + _glfwLibrary.X11.selection.atoms.string[_GLFW_STRING_ATOM_COMPOUND] = XInternAtom(_glfwLibrary.X11.display, "COMPOUND_STRING", False); - _glfwLibrary.X11.selection.stringatoms[2] = XA_STRING; - _glfwLibrary.X11.selection.stringatoms[3] = 0; + _glfwLibrary.X11.selection.atoms.string[_GLFW_STRING_ATOM_STRING] = XA_STRING; return GL_TRUE; } @@ -616,6 +623,10 @@ int _glfwPlatformTerminate(void) } #endif + // Free clipboard memory + if (_glfwLibrary.X11.selection.clipboard.string) + free(_glfwLibrary.X11.selection.clipboard.string); + return GL_TRUE; } diff --git a/src/x11_platform.h b/src/x11_platform.h index 90b6e75c..6f1bf6a5 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -89,8 +89,17 @@ #define _GLFW_PLATFORM_LIBRARY_STATE _GLFWlibraryX11 X11 #define _GLFW_PLATFORM_CONTEXT_STATE _GLFWcontextGLX GLX -// Number of string atoms that will be checked -#define _GLFW_STRING_ATOMS_COUNT 4 +// Clipboard atoms +#define _GLFW_CLIPBOARD_ATOM_PRIMARY 0 +#define _GLFW_CLIPBOARD_ATOM_CLIPBOARD 1 +#define _GLFW_CLIPBOARD_ATOM_SECONDARY 2 +#define _GLFW_CLIPBOARD_ATOM_COUNT 3 + +// String atoms +#define _GLFW_STRING_ATOM_UTF8 0 +#define _GLFW_STRING_ATOM_COMPOUND 1 +#define _GLFW_STRING_ATOM_STRING 2 +#define _GLFW_STRING_ATOM_COUNT 3 //======================================================================== // GLFW platform specific types @@ -229,8 +238,15 @@ typedef struct _GLFWlibraryX11 // Selection data struct { - Atom stringatoms[_GLFW_STRING_ATOMS_COUNT]; - Atom request; + struct { + Atom clipboard[_GLFW_CLIPBOARD_ATOM_COUNT]; + Atom string[_GLFW_STRING_ATOM_COUNT]; + } atoms; + struct { + size_t stringlen; + char *string; + } clipboard; + Atom request; int converted; } selection; @@ -273,5 +289,7 @@ void _glfwTerminateJoysticks(void); // Unicode support long _glfwKeySym2Unicode(KeySym keysym); +// Clipboard handling +Atom _glfwSelectionRequest(XSelectionRequestEvent *request); #endif // _platform_h_ diff --git a/src/x11_window.c b/src/x11_window.c index ff54a11b..cfb19980 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -41,8 +41,8 @@ #define _NET_WM_STATE_TOGGLE 2 // Additional mouse button names for XButtonEvent -#define Button6 6 -#define Button7 7 +#define Button6 6 +#define Button7 7 //======================================================================== // Error handler for BadMatch errors when requesting context with @@ -1394,6 +1394,23 @@ static void processSingleEvent(void) break; } + case SelectionRequest: + { + XSelectionRequestEvent *request = &event.xselectionrequest; + // Construct the response + XEvent response; + response.xselection.property = _glfwSelectionRequest(request); + response.xselection.type = SelectionNotify; + response.xselection.display = request->display; + response.xselection.requestor = request->requestor; + response.xselection.selection = request->selection; + response.xselection.target = request->target; + response.xselection.time = request->time; + // Send off the event + XSendEvent(_glfwLibrary.X11.display, request->requestor, 0, 0, &response); + break; + } + // Was the window destroyed? case DestroyNotify: return;