| //======================================================================== |
| // GLFW 3.0 X11 - www.glfw.org |
| //------------------------------------------------------------------------ |
| // Copyright (c) 2010 Camilla Berglund <elmindreda@elmindreda.org> |
| // |
| // 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 <stdio.h> |
| #include <limits.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| |
| // 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; |
| } |
| |