X11: Implement extended drag & drop API

This commit is contained in:
Florian Albrechtskirchinger 2022-06-10 13:30:12 +02:00
parent 2933c00e54
commit 0a757737d3
No known key found for this signature in database
GPG Key ID: 19618CE9B2D4BE6D
3 changed files with 165 additions and 27 deletions

View File

@ -910,11 +910,6 @@ static GLFWbool initExtensions(void)
// the keyboard mapping. // the keyboard mapping.
createKeyTables(); 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 // Custom selection property atom
_glfw.x11.GLFW_SELECTION = _glfw.x11.GLFW_SELECTION =
XInternAtom(_glfw.x11.display, "GLFW_SELECTION", False); 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.PRIMARY = XInternAtom(_glfw.x11.display, "PRIMARY", False);
_glfw.x11.INCR = XInternAtom(_glfw.x11.display, "INCR", False); _glfw.x11.INCR = XInternAtom(_glfw.x11.display, "INCR", False);
_glfw.x11.CLIPBOARD = XInternAtom(_glfw.x11.display, "CLIPBOARD", False); _glfw.x11.CLIPBOARD = XInternAtom(_glfw.x11.display, "CLIPBOARD", False);
_glfw.x11.ATOM_PAIR = XInternAtom(_glfw.x11.display, "ATOM_PAIR", False);
// Clipboard manager atoms // Clipboard manager atoms
_glfw.x11.CLIPBOARD_MANAGER = _glfw.x11.CLIPBOARD_MANAGER =
@ -937,11 +933,24 @@ static GLFWbool initExtensions(void)
_glfw.x11.XdndEnter = XInternAtom(_glfw.x11.display, "XdndEnter", False); _glfw.x11.XdndEnter = XInternAtom(_glfw.x11.display, "XdndEnter", False);
_glfw.x11.XdndPosition = XInternAtom(_glfw.x11.display, "XdndPosition", False); _glfw.x11.XdndPosition = XInternAtom(_glfw.x11.display, "XdndPosition", False);
_glfw.x11.XdndStatus = XInternAtom(_glfw.x11.display, "XdndStatus", 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.XdndDrop = XInternAtom(_glfw.x11.display, "XdndDrop", False);
_glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False); _glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False);
_glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False); _glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False);
_glfw.x11.XdndTypeList = XInternAtom(_glfw.x11.display, "XdndTypeList", 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); _glfw.x11.text_uri_list = XInternAtom(_glfw.x11.display, "text/uri-list", False);
// ICCCM, EWMH and Motif window property atoms // ICCCM, EWMH and Motif window property atoms

View File

@ -97,6 +97,9 @@ typedef struct __GLXFBConfig* GLXFBConfig;
typedef struct __GLXcontext* GLXContext; typedef struct __GLXcontext* GLXContext;
typedef void (*__GLXextproc)(void); typedef void (*__GLXextproc)(void);
// TODO for dev; remove
typedef char* (* PFN_XGetAtomName)(Display*,Atom);
typedef XClassHint* (* PFN_XAllocClassHint)(void); typedef XClassHint* (* PFN_XAllocClassHint)(void);
typedef XSizeHints* (* PFN_XAllocSizeHints)(void); typedef XSizeHints* (* PFN_XAllocSizeHints)(void);
typedef XWMHints* (* PFN_XAllocWMHints)(void); typedef XWMHints* (* PFN_XAllocWMHints)(void);
@ -622,12 +625,12 @@ typedef struct _GLFWlibraryX11
Atom XdndEnter; Atom XdndEnter;
Atom XdndPosition; Atom XdndPosition;
Atom XdndStatus; Atom XdndStatus;
Atom XdndActionCopy; Atom XdndLeave;
Atom XdndActions[_GLFW_DND_ACTION_COUNT];
Atom XdndDrop; Atom XdndDrop;
Atom XdndFinished; Atom XdndFinished;
Atom XdndSelection; Atom XdndSelection;
Atom XdndTypeList; Atom XdndTypeList;
Atom text_uri_list;
// Selection (clipboard) atoms // Selection (clipboard) atoms
Atom TARGETS; Atom TARGETS;
@ -637,12 +640,17 @@ typedef struct _GLFWlibraryX11
Atom PRIMARY; Atom PRIMARY;
Atom CLIPBOARD_MANAGER; Atom CLIPBOARD_MANAGER;
Atom SAVE_TARGETS; Atom SAVE_TARGETS;
Atom NULL_;
Atom UTF8_STRING;
Atom COMPOUND_STRING;
Atom ATOM_PAIR; Atom ATOM_PAIR;
Atom GLFW_SELECTION; 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 { struct {
void* handle; void* handle;
GLFWbool utf8; GLFWbool utf8;
@ -800,7 +808,12 @@ typedef struct _GLFWlibraryX11
struct { struct {
int version; int version;
Window source; Window source;
Atom format; Atom formatAtoms[_GLFW_DND_FORMAT_COUNT];
int availableFormats;
int chosenFormat;
int availableActions;
int proposedAction;
int chosenAction;
} xdnd; } xdnd;
struct { struct {

View File

@ -1519,15 +1519,22 @@ static void processEvent(XEvent *event)
// A drag operation has entered the window // A drag operation has entered the window
unsigned long count; unsigned long count;
Atom* formats = NULL; Atom* formats = NULL;
double xpos, ypos;
const GLFWbool list = event->xclient.data.l[1] & 1; 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.source = event->xclient.data.l[0];
_glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24; _glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24;
_glfw.x11.xdnd.format = None;
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
return; 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) if (list)
{ {
count = _glfwGetWindowPropertyX11(_glfw.x11.xdnd.source, count = _glfwGetWindowPropertyX11(_glfw.x11.xdnd.source,
@ -1541,27 +1548,75 @@ static void processEvent(XEvent *event)
formats = (Atom*) event->xclient.data.l + 2; 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++) 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; _glfw.x11.xdnd.formatAtoms[_GLFW_DND_PATHS_INDEX] = formats[i];
break; _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) if (list && formats)
XFree(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) else if (event->xclient.message_type == _glfw.x11.XdndDrop)
{ {
// The drag operation has finished by dropping on the window // The drag operation has finished by dropping on the window
Time time = CurrentTime; 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) if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
return; 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) if (_glfw.x11.xdnd.version >= 1)
time = event->xclient.data.l[2]; time = event->xclient.data.l[2];
@ -1569,7 +1624,7 @@ static void processEvent(XEvent *event)
// Request the chosen format from the source window // Request the chosen format from the source window
XConvertSelection(_glfw.x11.display, XConvertSelection(_glfw.x11.display,
_glfw.x11.XdndSelection, _glfw.x11.XdndSelection,
_glfw.x11.xdnd.format, _glfw.x11.xdnd.formatAtoms[formatIndex],
_glfw.x11.XdndSelection, _glfw.x11.XdndSelection,
window->x11.handle, window->x11.handle,
time); time);
@ -1594,12 +1649,29 @@ static void processEvent(XEvent *event)
// The drag operation has moved over the window // The drag operation has moved over the window
const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff; const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff;
const int yabs = (event->xclient.data.l[2]) & 0xffff; const int yabs = (event->xclient.data.l[2]) & 0xffff;
const Atom action = event->xclient.data.l[4];
Window dummy; Window dummy;
int xpos, ypos; int xpos, ypos;
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION) if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
return; 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, XTranslateCoordinates(_glfw.x11.display,
_glfw.x11.root, _glfw.x11.root,
window->x11.handle, window->x11.handle,
@ -1609,6 +1681,13 @@ static void processEvent(XEvent *event)
_glfwInputCursorPos(window, xpos, ypos); _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 }; XEvent reply = { ClientMessage };
reply.xclient.window = _glfw.x11.xdnd.source; reply.xclient.window = _glfw.x11.xdnd.source;
reply.xclient.message_type = _glfw.x11.XdndStatus; 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[2] = 0; // Specify an empty rectangle
reply.xclient.data.l[3] = 0; 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 that we are ready to copy the dragged data
reply.xclient.data.l[1] = 1; // Accept with no rectangle reply.xclient.data.l[1] = 1; // Accept with no rectangle
if (_glfw.x11.xdnd.version >= 2) 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, XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source,
@ -1646,15 +1732,39 @@ static void processEvent(XEvent *event)
(unsigned char**) &data); (unsigned char**) &data);
if (result) if (result)
{
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; int count;
char** paths = _glfwParseUriList(data, &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++) _glfwInputDropEx(window, format, count, paths,
&_glfw.x11.xdnd.chosenAction);
for (int i = 0; i < count; ++i)
_glfw_free(paths[i]); _glfw_free(paths[i]);
_glfw_free(paths); _glfw_free(paths);
break;
}
case GLFW_DND_TEXT:
{
_glfwInputDropEx(window, format, -1, data,
&_glfw.x11.xdnd.chosenAction);
break;
}
default:
break;
}
}
} }
if (data) if (data)
@ -1668,7 +1778,13 @@ static void processEvent(XEvent *event)
reply.xclient.format = 32; reply.xclient.format = 32;
reply.xclient.data.l[0] = window->x11.handle; reply.xclient.data.l[0] = window->x11.handle;
reply.xclient.data.l[1] = result; 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, XSendEvent(_glfw.x11.display, _glfw.x11.xdnd.source,
False, NoEventMask, &reply); False, NoEventMask, &reply);