要使用 X11 处理 Drag 'n Drop 事件,您必须使用 XDnD 协议。尽管 XDnD 协议比其他 Drag 'n Drop API 复杂得多,但理论上它仍然相对简单。然而,实现起来很繁琐,因为它需要与 X11 服务器和源窗口正确通信。
本教程说明如何处理 XDnD 协议并管理 X11 Drag 'n Drop 事件。代码基于RGFW的源代码。
所需步骤的详细概述:
首先,X11 Atoms 将被初始化。 X11 Atom 用于通过 X11 请求或发送特定数据或属性。
然后,窗口的属性将被更改,使其能够感知 XDND(X 拖放)事件。
当发生拖动时,窗口将收到一个 ClientMessage 事件,其中包含一条 XdndEnter 消息,告诉目标窗口拖动已开始。
当拖动正在进行时,源窗口通过 ClientMessage 事件将有关拖动的更新发送到目标窗口。每次目标窗口获得更新时,它必须确认它收到了更新;否则,交互将结束。
一旦发生放置,源窗口将发送 XdndDrop 消息。然后目标窗口将通过X11转换下拉选择并接收SelectionNotify事件以获取转换后的数据。
目标窗口将处理该事件,将数据转换为可读字符串,最后发送带有 XdndFinished 原子的 ClientMessage 来告诉源窗口交互已完成。
所需步骤的快速概述:
1) 定义 X11 原子
2) 为窗口启用 XDnD 事件
3)通过ClientMessage处理XDnD事件
4)通过ClientMessage获取XDnD掉落数据并结束交互
要处理 XDnD 事件,XDnD 原子必须通过 XInternAtom 初始化。发送或请求特定数据或操作时使用原子。
当目标窗口想要知道源窗口支持的数据类型时使用XdndTypeList。
XdndSelection 用于检查删除后的数据选择并在转换后检索数据。
const Atom XdndTypeList = XInternAtom(display, "XdndTypeList", False); const Atom XdndSelection = XInternAtom(display, "XdndSelection", False);
这些通用 Xdnd 原子是由源窗口发送的消息,XdndStatus 除外。
XdndEnter,当水滴进入目标窗口时使用。
XdndPosition 用于更新目标窗口的拖放位置。
XdndStatus 用于告诉源窗口目标已收到消息。
当水滴离开目标窗口时使用 XdndLeave。
当拖放已放入目标窗口时,使用 XdndDrop。
XdndFinished 在放置完成后使用。
const Atom XdndEnter = XInternAtom(display, "XdndEnter", False); const Atom XdndPosition = XInternAtom(display, "XdndPosition", False); const Atom XdndStatus = XInternAtom(display, "XdndStatus", False); const Atom XdndLeave = XInternAtom(display, "XdndLeave", False); const Atom XdndDrop = XInternAtom(display, "XdndDrop", False); const Atom XdndFinished = XInternAtom(display, "XdndFinished", False);
Xdnd 操作是目标窗口想要对拖动数据执行的操作。
XdndActionCopy 用于目标窗口想要复制拖动数据时。
const Atom XdndActionCopy = XInternAtom(display, "XdndActionCopy", False);
需要text/uri-list和text/plain原子来检查drop数据的格式。
const Atom XtextUriList = XInternAtom((Display*) display, "text/uri-list", False); const Atom XtextPlain = XInternAtom((Display*) display, "text/plain", False);
要接收 XDnD 事件,窗口必须启用 XDndAware 原子。该原子告诉窗口管理器和源窗口该窗口想要接收 XDnD 事件。
这可以通过创建 XdndAware 原子并使用 XChangeProperty 更改窗口的 XdndAware 属性来完成。
您还必须使用指针设置 XDnD 版本,应使用版本 5,因为它是 XDnD 协议的最新版本。
const Atom XdndAware = XInternAtom(display, "XdndAware", False); const char myversion = 5; XChangeProperty(display, window, XdndAware, 4, 32, PropModeReplace, &myversion, 1);
在处理任何事件之前,必须定义一些变量。
这些变量由源窗口提供给我们,并在多个实例中使用。
这些变量是源窗口、使用的 XDnD Protocall 版本以及放置数据的格式。
int64_t source, version; int32_t format;
现在可以处理 ClientMessage 事件了。
case ClientMessage:
首先,我将创建一个通用的 XEvent 结构来回复 XDnD 事件。这是可选的,但在使用它时我们将需要做更少的工作。
这会将事件发送到源窗口,并将我们的窗口(目标)包含在数据中。
XEvent reply = { ClientMessage }; reply.xclient.window = source; reply.xclient.format = 32; reply.xclient.data.l[0] = (long) window; reply.xclient.data.l[1] = 0; reply.xclient.data.l[2] = None;
ClientMessage事件结构可以通过XEvent.xclient访问。
message_type 是结构体中的一个属性,它保存消息类型。我们将用它来检查消息类型是否是 XDnD 消息。
我们将处理 3 个 XDnD 事件,XdndEnter、XdndPosition 和 XdndDrop。
当水滴进入目标窗口时发送 XdndEnter。
if (E.xclient.message_type == XdndEnter) {
首先,RGFW 初始化所需的变量。
unsigned long count; Atom* formats; Atom real_formats[6];
We can also create a bool to check if the supported formats are a list or if there is only one format.
This can be done by using the xclient's data attribute. Data is a list of data about the event.
the first item is the source window.
The second item of the data includes two values, if the format is a list or not and the version of XDnD used.
To get the bool value, you can check the first bit, the version is stored 24 bits after (the final 40 bits).
The format should be set to None for now, also make sure the version is less than or equal to 5. Otherwise, there's probably an issue because 5 is the newest version.
Bool list = E.xclient.data.l[1] & 1; source = E.xclient.data.l[0]; version = E.xclient.data.l[1] >> 24; format = None; if (version > 5) break;
If the format is a list, we'll have to get the format list from the source window's XDndTypeList value using XGetWindowProperty
if (list) { Atom actualType; int32_t actualFormat; unsigned long bytesAfter; XGetWindowProperty((Display*) display, source, XdndTypeList, 0, LONG_MAX, False, 4, &actualType, &actualFormat, &count, &bytesAfter, (unsigned char**) &formats); }
Otherwise, the format can be found using the leftover xclient values (2 - 4)
else { count = 0; if (E.xclient.data.l[2] != None) real_formats[count++] = E.xclient.data.l[2]; if (E.xclient.data.l[3] != None) real_formats[count++] = E.xclient.data.l[3]; if (E.xclient.data.l[4] != None) real_formats[count++] = E.xclient.data.l[4]; formats = real_formats; }
Now that we have the format array, we can check if the format matches any of the formats we're looking for.
The list should also be freed using XFree if it was received using XGetWindowProperty.
unsigned long i; for (i = 0; i < count; i++) { if (formats[i] == XtextUriList || formats[i] == XtextPlain) { format = formats[i]; break; } } if (list) { XFree(formats); } break; }
XdndPosition is used when the drop position is updated.
Before we handle the event, make sure the version is correct.
if (E.xclient.message_type == XdndPosition && version <= 5)) {
The absolute X and Y can be found using the second item of the data list.
The X = the last 32 bits.
The Y = the first 32 bits.
const int32_t xabs = (E.xclient.data.l[2] >> 16) & 0xffff; const int32_t yabs = (E.xclient.data.l[2]) & 0xffff;
The absolute X and Y can be translated to the actual X and Y coordinates of the drop position using XTranslateCoordinates.
Window dummy; int32_t xpos, ypos; XTranslateCoordinates((Display*) display, XDefaultRootWindow((Display*) display), (Window) window, xabs, yabs, &xpos, &ypos, &dummy); printf("File drop starting at %i %i\n", xpos, ypos);
A response must be sent back to the source window. The response uses XdndStatus to tell the window it has received the message.
We should also tell the source the action accepted with the data. (XdndActionCopy)
The message can be sent out via XSendEvent make sure you also send out XFlush to make sure the event is pushed out.
reply.xclient.message_type = XdndStatus; if (format) { reply.xclient.data.l[1] = 1; if (version >= 2) reply.xclient.data.l[4] = XdndActionCopy; } XSendEvent((Display*) display, source, False, NoEventMask, &reply); XFlush((Display*) display); break; }
Before we handle the event, make sure the version is correct.
XdndDrop occurs when the item has been dropped.
if (E.xclient.message_type = XdndDrop && version <= 5) {
First, we should make sure we registered a valid format earlier.
if (format) {
We can use XConvertSection to request that the selection be converted to the format.
We will get the result in an SelectionNotify event.
// newer versions of xDnD require us to tell the source our time Time time = CurrentTime; if (version >= 1) time = E.xclient.data.l[2]; XConvertSelection((Display*) display, XdndSelection, format, XdndSelection, (Window) window, time); }
Otherwise, there is no drop data and the drop has ended. XDnD versions 2 and older require the target to tell the source when the drop has ended.
This can be done by sending out a ClientMessage event with the XdndFinished message type.
else if (version >= 2) { reply.xclient.message_type = XdndFinished; XSendEvent((Display*) display, source, False, NoEventMask, &reply); XFlush((Display*) display); } }
Now we can receive the converted selection from the SlectionNotify event
case SelectionNotify: {
To do this, first, ensure the property is the XdndSelection.
/* this is only for checking for drops */ if (E.xselection.property != XdndSelection) break;
XGetWindowpropery can be used to get the selection data.
char* data; unsigned long result; Atom actualType; int32_t actualFormat; unsigned long bytesAfter; XGetWindowProperty((Display*) display, E.xselection.requestor, E.xselection.property, \ 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (unsigned char**) &data); if (result == 0) break; printf("File dropped: %s\n", data);
This is the raw string data for the drop. If there are multiple drops, it will include the files separated by a '\n'. If you'd prefer an array of strings, you'd have to parse the data into an array.
The data should also be freed once you're done using it.
If you want to use the data after the event has been processed, you should allocate a separate buffer and copy the data over.
if (data) XFree(data);
the drop has ended and XDnD versions 2 and older require the target to tell the source when the drop has ended.
This can be done by sending out a ClientMessage event with the XdndFinished message type.
It will also include the action we did with the data and the result to tell the source wether or not we actually got the data.
if (version >= 2) { reply.xclient.message_type = XdndFinished; reply.xclient.data.l[1] = result; reply.xclient.data.l[2] = XdndActionCopy; XSendEvent((Display*) display, source, False, NoEventMask, &reply); XFlush((Display*) display); }
// This compiles with // gcc example.c -lX11 #include#include #include #include int main(void) { Display* display = XOpenDisplay(NULL); Window window = XCreateSimpleWindow(display, RootWindow(display, DefaultScreen(display)), 10, 10, 200, 200, 1, BlackPixel(display, DefaultScreen(display)), WhitePixel(display, DefaultScreen(display))); XSelectInput(display, window, ExposureMask | KeyPressMask); const Atom wm_delete_window = XInternAtom((Display*) display, "WM_DELETE_WINDOW", False); /* Xdnd code */ /* fetching data */ const Atom XdndTypeList = XInternAtom(display, "XdndTypeList", False); const Atom XdndSelection = XInternAtom(display, "XdndSelection", False); /* client messages */ const Atom XdndEnter = XInternAtom(display, "XdndEnter", False); const Atom XdndPosition = XInternAtom(display, "XdndPosition", False); const Atom XdndStatus = XInternAtom(display, "XdndStatus", False); const Atom XdndLeave = XInternAtom(display, "XdndLeave", False); const Atom XdndDrop = XInternAtom(display, "XdndDrop", False); const Atom XdndFinished = XInternAtom(display, "XdndFinished", False); /* actions */ const Atom XdndActionCopy = XInternAtom(display, "XdndActionCopy", False); const Atom XdndActionMove = XInternAtom(display, "XdndActionMove", False); const Atom XdndActionLink = XInternAtom(display, "XdndActionLink", False); const Atom XdndActionAsk = XInternAtom(display, "XdndActionAsk", False); const Atom XdndActionPrivate = XInternAtom(display, "XdndActionPrivate", False); const Atom XtextUriList = XInternAtom((Display*) display, "text/uri-list", False); const Atom XtextPlain = XInternAtom((Display*) display, "text/plain", False); const Atom XdndAware = XInternAtom(display, "XdndAware", False); const char myVersion = 5; XChangeProperty(display, window, XdndAware, 4, 32, PropModeReplace, &myVersion, 1); XMapWindow(display, window); XEvent E; Bool running = True; int64_t source, version; int32_t format; while (running) { XNextEvent(display, &E); switch (E.type) { case KeyPress: running = False; break; case ClientMessage: if (E.xclient.data.l[0] == (int64_t) wm_delete_window) { running = False; break; } XEvent reply = { ClientMessage }; reply.xclient.window = source; reply.xclient.format = 32; reply.xclient.data.l[0] = (long) window; reply.xclient.data.l[2] = 0; reply.xclient.data.l[3] = 0; if (E.xclient.message_type == XdndEnter) { unsigned long count; Atom* formats; Atom real_formats[6]; Bool list = E.xclient.data.l[1] & 1; source = E.xclient.data.l[0]; version = E.xclient.data.l[1] >> 24; format = None; if (version > 5) break; if (list) { Atom actualType; int32_t actualFormat; unsigned long bytesAfter; XGetWindowProperty((Display*) display, source, XdndTypeList, 0, LONG_MAX, False, 4, &actualType, &actualFormat, &count, &bytesAfter, (unsigned char**) &formats); } else { count = 0; if (E.xclient.data.l[2] != None) real_formats[count++] = E.xclient.data.l[2]; if (E.xclient.data.l[3] != None) real_formats[count++] = E.xclient.data.l[3]; if (E.xclient.data.l[4] != None) real_formats[count++] = E.xclient.data.l[4]; formats = real_formats; } unsigned long i; for (i = 0; i < count; i++) { if (formats[i] == XtextUriList || formats[i] == XtextPlain) { format = formats[i]; break; } } if (list) { XFree(formats); } break; } if (E.xclient.message_type == XdndPosition) { const int32_t xabs = (E.xclient.data.l[2] >> 16) & 0xffff; const int32_t yabs = (E.xclient.data.l[2]) & 0xffff; Window dummy; int32_t xpos, ypos; if (version > 5) break; XTranslateCoordinates((Display*) display, XDefaultRootWindow((Display*) display), (Window) window, xabs, yabs, &xpos, &ypos, &dummy); printf("File drop starting at %i %i\n", xpos, ypos); reply.xclient.message_type = XdndStatus; if (format) { reply.xclient.data.l[1] = 1; if (version >= 2) reply.xclient.data.l[4] = XdndActionCopy; } XSendEvent((Display*) display, source, False, NoEventMask, &reply); XFlush((Display*) display); break; } if (E.xclient.message_type = XdndDrop && version <= 5) { if (format) { Time time = CurrentTime; if (version >= 1) time = E.xclient.data.l[2]; XConvertSelection((Display*) display, XdndSelection, format, XdndSelection, (Window) window, time); } else if (version >= 2) { reply.xclient.message_type = XdndFinished; XSendEvent((Display*) display, source, False, NoEventMask, &reply); XFlush((Display*) display); } } break; case SelectionNotify: { /* this is only for checking for drops */ if (E.xselection.property != XdndSelection) break; char* data; unsigned long result; Atom actualType; int32_t actualFormat; unsigned long bytesAfter; XGetWindowProperty((Display*) display, E.xselection.requestor, E.xselection.property, 0, LONG_MAX, False, E.xselection.target, &actualType, &actualFormat, &result, &bytesAfter, (unsigned char**) &data); if (result == 0) break; printf("File(s) dropped: %s\n", data); if (data) XFree(data); if (version >= 2) { reply.xclient.message_type = XdndFinished; reply.xclient.data.l[1] = result; reply.xclient.data.l[2] = XdndActionCopy; XSendEvent((Display*) display, source, False, NoEventMask, &reply); XFlush((Display*) display); } break; } default: break; } } XCloseDisplay(display); }
以上是RGFW 底层:XDrag n Drop的详细内容。更多信息请关注PHP中文网其他相关文章!