//======================================================================== // GLFW 3.0 X11 - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2010 Camilla Berglund // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include "internal.h" #include #include #include #include // Returns whether the event is a selection event // static Bool isSelectionMessage(Display* display, XEvent* event, XPointer pointer) { return event->type == SelectionRequest || event->type == SelectionNotify || event->type == SelectionClear; } // Set the specified property to the selection converted to the requested target // static Atom writeTargetToProperty(const XSelectionRequestEvent* request) { int i; const Atom formats[] = { _glfw.x11.UTF8_STRING, _glfw.x11.COMPOUND_STRING, XA_STRING }; const int formatCount = sizeof(formats) / sizeof(formats[0]); if (request->property == None) { // The requestor is a legacy client (ICCCM section 2.2) // We don't support legacy clients, so fail here return None; } if (request->target == _glfw.x11.TARGETS) { // The list of supported targets was requested const Atom targets[] = { _glfw.x11.TARGETS, _glfw.x11.MULTIPLE, _glfw.x11.UTF8_STRING, _glfw.x11.COMPOUND_STRING, XA_STRING }; XChangeProperty(_glfw.x11.display, request->requestor, request->property, XA_ATOM, 32, PropModeReplace, (unsigned char*) targets, sizeof(targets) / sizeof(targets[0])); return request->property; } if (request->target == _glfw.x11.MULTIPLE) { // Multiple conversions were requested Atom* targets; unsigned long i, count; count = _glfwGetWindowProperty(request->requestor, request->property, _glfw.x11.ATOM_PAIR, (unsigned char**) &targets); for (i = 0; i < count; i += 2) { int j; for (j = 0; j < formatCount; j++) { if (targets[i] == formats[j]) break; } if (j < formatCount) { XChangeProperty(_glfw.x11.display, request->requestor, targets[i + 1], targets[i], 8, PropModeReplace, (unsigned char*) _glfw.x11.selection.string, strlen(_glfw.x11.selection.string)); } else targets[i + 1] = None; } XChangeProperty(_glfw.x11.display, request->requestor, request->property, _glfw.x11.ATOM_PAIR, 32, PropModeReplace, (unsigned char*) targets, count); XFree(targets); return request->property; } if (request->target == _glfw.x11.SAVE_TARGETS) { // The request is a check whether we support SAVE_TARGETS // It should be handled as a no-op side effect target XChangeProperty(_glfw.x11.display, request->requestor, request->property, XInternAtom(_glfw.x11.display, "NULL", False), 32, PropModeReplace, NULL, 0); return request->property; } // Conversion to a data target was requested for (i = 0; i < formatCount; i++) { if (request->target == formats[i]) { // The requested target is one we support XChangeProperty(_glfw.x11.display, request->requestor, request->property, request->target, 8, PropModeReplace, (unsigned char*) _glfw.x11.selection.string, strlen(_glfw.x11.selection.string)); return request->property; } } // The requested target is not supported return None; } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// void _glfwHandleSelectionClear(XEvent* event) { free(_glfw.x11.selection.string); _glfw.x11.selection.string = NULL; } void _glfwHandleSelectionRequest(XEvent* event) { const XSelectionRequestEvent* request = &event->xselectionrequest; XEvent response; memset(&response, 0, sizeof(response)); response.xselection.property = writeTargetToProperty(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; XSendEvent(_glfw.x11.display, request->requestor, False, 0, &response); } void _glfwPushSelectionToManager(_GLFWwindow* window) { XConvertSelection(_glfw.x11.display, _glfw.x11.CLIPBOARD_MANAGER, _glfw.x11.SAVE_TARGETS, None, window->x11.handle, CurrentTime); for (;;) { XEvent event; if (!XCheckIfEvent(_glfw.x11.display, &event, isSelectionMessage, NULL)) continue; switch (event.type) { case SelectionRequest: _glfwHandleSelectionRequest(&event); break; case SelectionClear: _glfwHandleSelectionClear(&event); break; case SelectionNotify: { if (event.xselection.target == _glfw.x11.SAVE_TARGETS) { // This means one of two things; either the selection was // not owned, which means there is no clipboard manager, or // the transfer to the clipboard manager has completed // In either case, it means we are done here return; } break; } } } } ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// void _glfwPlatformSetClipboardString(_GLFWwindow* window, const char* string) { free(_glfw.x11.selection.string); _glfw.x11.selection.string = strdup(string); XSetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD, window->x11.handle, CurrentTime); if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) != window->x11.handle) { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to become owner of the clipboard selection"); } } 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 (_glfwFindWindowByHandle(XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD))) { // 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.selection.string; } free(_glfw.x11.selection.string); _glfw.x11.selection.string = NULL; for (i = 0; i < formatCount; i++) { char* data; XEvent event; XConvertSelection(_glfw.x11.display, _glfw.x11.CLIPBOARD, formats[i], _glfw.x11.GLFW_SELECTION, window->x11.handle, CurrentTime); // XCheckTypedEvent is used instead of XIfEvent in order not to lock // other threads out from the display during the entire wait period while (!XCheckTypedEvent(_glfw.x11.display, SelectionNotify, &event)) ; if (event.xselection.property == None) continue; if (_glfwGetWindowProperty(event.xselection.requestor, event.xselection.property, event.xselection.target, (unsigned char**) &data)) { _glfw.x11.selection.string = strdup(data); } XFree(data); XDeleteProperty(_glfw.x11.display, event.xselection.requestor, event.xselection.property); if (_glfw.x11.selection.string) break; } if (_glfw.x11.selection.string == NULL) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "X11: Failed to convert selection to string"); } return _glfw.x11.selection.string; }