diff --git a/src/x11_init.c b/src/x11_init.c index 11aeb9e5..36450c17 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -910,11 +910,6 @@ static GLFWbool initExtensions(void) // the keyboard mapping. createKeyTables(); - // String format atoms - _glfw.x11.NULL_ = XInternAtom(_glfw.x11.display, "NULL", False); - _glfw.x11.UTF8_STRING = XInternAtom(_glfw.x11.display, "UTF8_STRING", False); - _glfw.x11.ATOM_PAIR = XInternAtom(_glfw.x11.display, "ATOM_PAIR", False); - // Custom selection property atom _glfw.x11.GLFW_SELECTION = XInternAtom(_glfw.x11.display, "GLFW_SELECTION", False); @@ -925,6 +920,7 @@ static GLFWbool initExtensions(void) _glfw.x11.PRIMARY = XInternAtom(_glfw.x11.display, "PRIMARY", False); _glfw.x11.INCR = XInternAtom(_glfw.x11.display, "INCR", False); _glfw.x11.CLIPBOARD = XInternAtom(_glfw.x11.display, "CLIPBOARD", False); + _glfw.x11.ATOM_PAIR = XInternAtom(_glfw.x11.display, "ATOM_PAIR", False); // Clipboard manager atoms _glfw.x11.CLIPBOARD_MANAGER = @@ -937,11 +933,24 @@ static GLFWbool initExtensions(void) _glfw.x11.XdndEnter = XInternAtom(_glfw.x11.display, "XdndEnter", False); _glfw.x11.XdndPosition = XInternAtom(_glfw.x11.display, "XdndPosition", False); _glfw.x11.XdndStatus = XInternAtom(_glfw.x11.display, "XdndStatus", False); - _glfw.x11.XdndActionCopy = XInternAtom(_glfw.x11.display, "XdndActionCopy", False); + _glfw.x11.XdndLeave = XInternAtom(_glfw.x11.display, "XdndLeave", False); + _glfw.x11.XdndActions[_GLFW_DND_COPY_INDEX] = + XInternAtom(_glfw.x11.display, "XdndActionCopy", False); + _glfw.x11.XdndActions[_GLFW_DND_LINK_INDEX] = + XInternAtom(_glfw.x11.display, "XdndActionLink", False); + _glfw.x11.XdndActions[_GLFW_DND_MOVE_INDEX] = + XInternAtom(_glfw.x11.display, "XdndActionMove", False); _glfw.x11.XdndDrop = XInternAtom(_glfw.x11.display, "XdndDrop", False); _glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False); _glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False); _glfw.x11.XdndTypeList = XInternAtom(_glfw.x11.display, "XdndTypeList", False); + + // Format atoms (shared by Xdnd and selection) + _glfw.x11.NULL_ = XInternAtom(_glfw.x11.display, "NULL", False); + _glfw.x11.UTF8_STRING = XInternAtom(_glfw.x11.display, "UTF8_STRING", False); + _glfw.x11.STRING = XInternAtom(_glfw.x11.display, "STRING", False); + _glfw.x11.TEXT = XInternAtom(_glfw.x11.display, "TEXT", False); + _glfw.x11.text_plain = XInternAtom(_glfw.x11.display, "text/plain", False); _glfw.x11.text_uri_list = XInternAtom(_glfw.x11.display, "text/uri-list", False); // ICCCM, EWMH and Motif window property atoms diff --git a/src/x11_platform.h b/src/x11_platform.h index ecaa0fa4..8018c706 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -97,6 +97,9 @@ typedef struct __GLXFBConfig* GLXFBConfig; typedef struct __GLXcontext* GLXContext; typedef void (*__GLXextproc)(void); +// TODO for dev; remove +typedef char* (* PFN_XGetAtomName)(Display*,Atom); + typedef XClassHint* (* PFN_XAllocClassHint)(void); typedef XSizeHints* (* PFN_XAllocSizeHints)(void); typedef XWMHints* (* PFN_XAllocWMHints)(void); @@ -622,12 +625,12 @@ typedef struct _GLFWlibraryX11 Atom XdndEnter; Atom XdndPosition; Atom XdndStatus; - Atom XdndActionCopy; + Atom XdndLeave; + Atom XdndActions[_GLFW_DND_ACTION_COUNT]; Atom XdndDrop; Atom XdndFinished; Atom XdndSelection; Atom XdndTypeList; - Atom text_uri_list; // Selection (clipboard) atoms Atom TARGETS; @@ -637,12 +640,17 @@ typedef struct _GLFWlibraryX11 Atom PRIMARY; Atom CLIPBOARD_MANAGER; Atom SAVE_TARGETS; - Atom NULL_; - Atom UTF8_STRING; - Atom COMPOUND_STRING; Atom ATOM_PAIR; Atom GLFW_SELECTION; + // Format atoms (shared by Xdnd and Selection) + Atom NULL_; + Atom UTF8_STRING; + Atom STRING; + Atom TEXT; + Atom text_plain; + Atom text_uri_list; + struct { void* handle; GLFWbool utf8; @@ -800,7 +808,12 @@ typedef struct _GLFWlibraryX11 struct { int version; Window source; - Atom format; + Atom formatAtoms[_GLFW_DND_FORMAT_COUNT]; + int availableFormats; + int chosenFormat; + int availableActions; + int proposedAction; + int chosenAction; } xdnd; struct { diff --git a/src/x11_window.c b/src/x11_window.c index 98f990b2..316327f3 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1519,15 +1519,22 @@ static void processEvent(XEvent *event) // A drag operation has entered the window unsigned long count; Atom* formats = NULL; + double xpos, ypos; const GLFWbool list = event->xclient.data.l[1] & 1; + memset(&_glfw.x11.xdnd, 0, sizeof(_glfw.x11.xdnd)); _glfw.x11.xdnd.source = event->xclient.data.l[0]; _glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24; - _glfw.x11.xdnd.format = None; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; + // Xdnd doesn't support the concepts "available actions" and + // "proposed action"; default to all and copy + _glfw.x11.xdnd.availableActions = + GLFW_DND_COPY | GLFW_DND_LINK | GLFW_DND_MOVE; + _glfw.x11.xdnd.proposedAction = GLFW_DND_COPY; + if (list) { count = _glfwGetWindowPropertyX11(_glfw.x11.xdnd.source, @@ -1541,27 +1548,75 @@ static void processEvent(XEvent *event) formats = (Atom*) event->xclient.data.l + 2; } + // TODO remove + PFN_XGetAtomName GetAtomName = (PFN_XGetAtomName) + _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XGetAtomName"); for (unsigned int i = 0; i < count; i++) { - if (formats[i] == _glfw.x11.text_uri_list) + printf("format (%d): %s\n", i, GetAtomName(_glfw.x11.display, formats[i])); + if (!_glfw.x11.xdnd.formatAtoms[_GLFW_DND_PATHS_INDEX] + && formats[i] == _glfw.x11.text_uri_list) { - _glfw.x11.xdnd.format = _glfw.x11.text_uri_list; - break; + _glfw.x11.xdnd.formatAtoms[_GLFW_DND_PATHS_INDEX] = formats[i]; + _glfw.x11.xdnd.availableFormats |= GLFW_DND_PATHS; + } + else if (!_glfw.x11.xdnd.formatAtoms[_GLFW_DND_TEXT_INDEX] + && (formats[i] == _glfw.x11.text_plain + || formats[i] == _glfw.x11.UTF8_STRING + || formats[i] == _glfw.x11.STRING + || formats[i] == _glfw.x11.TEXT)) + { + _glfw.x11.xdnd.formatAtoms[_GLFW_DND_TEXT_INDEX] = formats[i]; + _glfw.x11.xdnd.availableFormats |= GLFW_DND_TEXT; } } if (list && formats) XFree(formats); + + window->dndDragging = GLFW_TRUE; + + _glfwGetCursorPosX11(window, &xpos, &ypos); + + _glfw.x11.xdnd.chosenFormat = _glfw.x11.xdnd.availableFormats; + _glfw.x11.xdnd.chosenAction = _glfw.x11.xdnd.proposedAction; + _glfwInputDrag(window, GLFW_DND_ENTER, xpos, ypos, + _glfw.x11.xdnd.availableFormats, + &_glfw.x11.xdnd.chosenFormat, + _glfw.x11.xdnd.availableActions, + &_glfw.x11.xdnd.chosenAction); + } + else if (event->xclient.message_type == _glfw.x11.XdndLeave) + { + // A drag operation has left the window or was canceled + window->dndDragging = GLFW_FALSE; + + _glfw.x11.xdnd.chosenFormat = + _glfw.x11.xdnd.availableFormats = GLFW_DND_NONE; + _glfw.x11.xdnd.chosenAction = + _glfw.x11.xdnd.proposedAction = + _glfw.x11.xdnd.availableActions = GLFW_DND_NONE; + _glfwInputDrag(window, GLFW_DND_LEAVE, 0.0, 0.0, + _glfw.x11.xdnd.availableFormats, + &_glfw.x11.xdnd.chosenFormat, + _glfw.x11.xdnd.availableActions, + &_glfw.x11.xdnd.chosenAction); } else if (event->xclient.message_type == _glfw.x11.XdndDrop) { // The drag operation has finished by dropping on the window Time time = CurrentTime; + int formatIndex = _glfw_ffs(_glfw.x11.xdnd.chosenFormat + & _GLFW_DND_MASK) - 1; + + window->dndDragging = GLFW_FALSE; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; - if (_glfw.x11.xdnd.format) + if (_glfw.x11.xdnd.chosenAction + && formatIndex >= 0 && formatIndex < _GLFW_DND_FORMAT_COUNT + && _glfw.x11.xdnd.formatAtoms[formatIndex]) { if (_glfw.x11.xdnd.version >= 1) time = event->xclient.data.l[2]; @@ -1569,7 +1624,7 @@ static void processEvent(XEvent *event) // Request the chosen format from the source window XConvertSelection(_glfw.x11.display, _glfw.x11.XdndSelection, - _glfw.x11.xdnd.format, + _glfw.x11.xdnd.formatAtoms[formatIndex], _glfw.x11.XdndSelection, window->x11.handle, time); @@ -1594,12 +1649,29 @@ static void processEvent(XEvent *event) // The drag operation has moved over the window const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff; const int yabs = (event->xclient.data.l[2]) & 0xffff; + const Atom action = event->xclient.data.l[4]; Window dummy; int xpos, ypos; if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) return; + if (_glfw.x11.xdnd.version >= 2) + { + _glfw.x11.xdnd.proposedAction = GLFW_DND_NONE; + for(int i = 0; i < _GLFW_DND_ACTION_COUNT; ++i) + { + if(action == _glfw.x11.XdndActions[i]) + { + _glfw.x11.xdnd.proposedAction = + (GLFW_DND_COPY & ~_GLFW_DND_MASK) | (1 << i); + break; + } + } + } + else + _glfw.x11.xdnd.proposedAction = GLFW_DND_COPY; + XTranslateCoordinates(_glfw.x11.display, _glfw.x11.root, window->x11.handle, @@ -1609,6 +1681,13 @@ static void processEvent(XEvent *event) _glfwInputCursorPos(window, xpos, ypos); + _glfw.x11.xdnd.chosenAction = _glfw.x11.xdnd.proposedAction; + _glfwInputDrag(window, GLFW_DND_DRAG, xpos, ypos, + _glfw.x11.xdnd.availableFormats, + &_glfw.x11.xdnd.chosenFormat, + _glfw.x11.xdnd.availableActions, + &_glfw.x11.xdnd.chosenAction); + XEvent reply = { ClientMessage }; reply.xclient.window = _glfw.x11.xdnd.source; reply.xclient.message_type = _glfw.x11.XdndStatus; @@ -1617,12 +1696,19 @@ static void processEvent(XEvent *event) reply.xclient.data.l[2] = 0; // Specify an empty rectangle reply.xclient.data.l[3] = 0; - if (_glfw.x11.xdnd.format) + if (_glfw.x11.xdnd.chosenAction) { // Reply that we are ready to copy the dragged data reply.xclient.data.l[1] = 1; // Accept with no rectangle if (_glfw.x11.xdnd.version >= 2) - reply.xclient.data.l[4] = _glfw.x11.XdndActionCopy; + { + int actionIndex = _glfw_ffs(_glfw.x11.xdnd.chosenAction + & _GLFW_DND_MASK) - 1; + if(actionIndex >= 0 && actionIndex < _GLFW_DND_ACTION_COUNT) + { + reply.xclient.data.l[4] = _glfw.x11.XdndActions[actionIndex]; + } + } } XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, @@ -1647,14 +1733,38 @@ static void processEvent(XEvent *event) if (result) { - int count; - char** paths = _glfwParseUriList(data, &count); + int formatIndex = _glfw_ffs(_glfw.x11.xdnd.chosenFormat + & _GLFW_DND_MASK) - 1; + if(formatIndex >= 0 && formatIndex < _GLFW_DND_FORMAT_COUNT) + { + int format = (GLFW_DND_TEXT & ~_GLFW_DND_MASK) | (1 << formatIndex); + switch (format) + { + case GLFW_DND_PATHS: + { + int count; + char** paths = _glfwParseUriList(data, &count); - _glfwInputDrop(window, count, (const char**) paths); + _glfwInputDrop(window, count, (const char **) paths); - for (int i = 0; i < count; i++) - _glfw_free(paths[i]); - _glfw_free(paths); + _glfwInputDropEx(window, format, count, paths, + &_glfw.x11.xdnd.chosenAction); + + for (int i = 0; i < count; ++i) + _glfw_free(paths[i]); + _glfw_free(paths); + break; + } + case GLFW_DND_TEXT: + { + _glfwInputDropEx(window, format, -1, data, + &_glfw.x11.xdnd.chosenAction); + break; + } + default: + break; + } + } } if (data) @@ -1668,7 +1778,13 @@ static void processEvent(XEvent *event) reply.xclient.format = 32; reply.xclient.data.l[0] = window->x11.handle; reply.xclient.data.l[1] = result; - reply.xclient.data.l[2] = _glfw.x11.XdndActionCopy; + + int actionIndex = _glfw_ffs(_glfw.x11.xdnd.chosenAction + & _GLFW_DND_MASK) - 1; + if(actionIndex >= 0 && actionIndex < _GLFW_DND_ACTION_COUNT) + { + reply.xclient.data.l[2] = _glfw.x11.XdndActions[actionIndex]; + } XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source, False, NoEventMask, &reply);