Heim > Artikel > Backend-Entwicklung > RGFW unter der Haube: XDrag & Drop
Um Drag 'n Drop-Ereignisse mit X11 zu verarbeiten, müssen Sie das XDnD-Protokoll verwenden. Obwohl das XDnD-Protokoll deutlich komplizierter ist als andere Drag & Drop-APIs, ist es theoretisch immer noch relativ einfach. Die Implementierung ist jedoch mühsam, da eine ordnungsgemäße Kommunikation mit dem X11-Server und dem Quellfenster erforderlich ist.
In diesem Tutorial wird erläutert, wie Sie mit dem XDnD-Protokoll umgehen und X11-Drag-and-Drop-Ereignisse verwalten. Der Code basiert auf dem Quellcode von RGFW.
Eine detaillierte Übersicht der erforderlichen Schritte:
Zuerst werden X11-Atome initialisiert. X11-Atome werden verwendet, um bestimmte Daten oder Eigenschaften über X11 abzufragen oder zu senden.
Anschließend werden die Eigenschaften des Fensters geändert, sodass es XDND-Ereignisse (X Drag 'n Drop) erkennt.
Wenn ein Ziehen stattfindet, empfängt das Fenster ein ClientMessage-Ereignis, das eine XdndEnter-Nachricht enthält, die dem Zielfenster mitteilt, dass das Ziehen begonnen hat.
Während das Ziehen ausgeführt wird, sendet das Quellfenster über ClientMessage-Ereignisse Aktualisierungen zum Ziehen an das Zielfenster. Jedes Mal, wenn das Zielfenster ein Update erhält, muss es bestätigen, dass es das Update erhalten hat. Andernfalls wird die Interaktion beendet.
Sobald der Drop erfolgt, sendet das Quellfenster eine XdndDrop-Nachricht. Anschließend konvertiert das Zielfenster die Drop-Auswahl über X11 und empfängt ein SelectionNotify-Ereignis, um die konvertierten Daten abzurufen.
Das Zielfenster verarbeitet dieses Ereignis, konvertiert die Daten in eine lesbare Zeichenfolge und sendet schließlich eine ClientMessage mit dem XdndFinished-Atom, um dem Quellfenster mitzuteilen, dass die Interaktion abgeschlossen ist.
Ein kurzer Überblick über die erforderlichen Schritte:
1) Definieren Sie X11-Atome
2) Aktivieren Sie XDnD-Ereignisse für das Fenster
3) Verarbeiten Sie XDnD-Ereignisse über ClientMessage
4) Holen Sie sich die XDnD-Drop-Daten über ClientMessage und beenden Sie die Interaktion
Um XDnD-Ereignisse zu verarbeiten, müssen XDnD-Atome über XInternAtom initialisiert werden. Atome werden beim Senden oder Anfordern bestimmter Daten oder Aktionen verwendet.
XdndTypeList wird verwendet, wenn das Zielfenster wissen möchte, welche Datentypen das Quellfenster unterstützt.
XdndSelection wird verwendet, um die Datenauswahl nach einem Drop zu untersuchen und die Daten nach der Konvertierung abzurufen.
const Atom XdndTypeList = XInternAtom(display, "XdndTypeList", False); const Atom XdndSelection = XInternAtom(display, "XdndSelection", False);
Diese generischen Xdnd-Atome sind Nachrichten, die vom Quellfenster gesendet werden, mit Ausnahme von XdndStatus.
XdndEnter, wird verwendet, wenn der Tropfen das Zielfenster erreicht hat.
XdndPosition wird verwendet, um das Zielfenster an der Position des Tropfens zu aktualisieren.
XdndStatus wird verwendet, um dem Quellfenster mitzuteilen, dass das Ziel die Nachricht empfangen hat.
XdndLeave wird verwendet, wenn der Tropfen das Zielfenster verlassen hat.
XdndDrop wird verwendet, wenn der Drop im Zielfenster abgelegt wurde.
XdndFinished wird verwendet, wenn der Drop abgeschlossen ist.
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-Aktionen sind Aktionen, die das Zielfenster mit den Ziehdaten durchführen möchte.
XdndActionCopy wird verwendet, wenn das Zielfenster die Ziehdaten kopieren möchte.
const Atom XdndActionCopy = XInternAtom(display, "XdndActionCopy", False);
Die Atome text/uri-list und text/plain werden benötigt, um das Format der Drop-Daten zu überprüfen.
const Atom XtextUriList = XInternAtom((Display*) display, "text/uri-list", False); const Atom XtextPlain = XInternAtom((Display*) display, "text/plain", False);
Um XDnD-Ereignisse zu empfangen, muss das Fenster das XDndAware-Atom aktivieren. Dieses Atom teilt dem Fenstermanager und dem Quellfenster mit, dass das Fenster XDnD-Ereignisse empfangen möchte.
Dies kann erreicht werden, indem ein XdndAware-Atom erstellt und XChangeProperty verwendet wird, um die XdndAware-Eigenschaft des Fensters zu ändern.
Sie müssen auch die XDnD-Version mithilfe eines Zeigers festlegen. Version 5 sollte verwendet werden, da es sich um die neueste Version des XDnD-Protokolls handelt.
const Atom XdndAware = XInternAtom(display, "XdndAware", False); const char myversion = 5; XChangeProperty(display, window, XdndAware, 4, 32, PropModeReplace, &myversion, 1);
Bevor Ereignisse behandelt werden, müssen einige Variablen definiert werden.
Diese Variablen werden uns vom Quellfenster bereitgestellt und über mehrere Instanzen hinweg verwendet.
Diese Variablen sind das Quellfenster, die verwendete XDnD-Protokollversion und das Format der Drop-Daten.
int64_t source, version; int32_t format;
Jetzt kann das ClientMessage-Ereignis verarbeitet werden.
case ClientMessage:
Zuerst werde ich eine generische XEvent-Struktur erstellen, um auf XDnD-Ereignisse zu antworten. Dies ist optional, aber bei der Verwendung müssen wir weniger Arbeit leisten.
Dadurch wird das Ereignis an das Quellfenster gesendet und unser Fenster (das Ziel) in die Daten einbezogen.
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;
Auf die ClientMessage-Ereignisstruktur kann über XEvent.xclient zugegriffen werden.
message_type ist ein Attribut in der Struktur, es enthält den Nachrichtentyp. Wir werden damit prüfen, ob es sich bei dem Nachrichtentyp um eine XDnD-Nachricht handelt.
Es gibt 3 XDnD-Ereignisse, die wir verarbeiten werden: XdndEnter, XdndPosition und XdndDrop.
XdndEnter wird gesendet, wenn der Tropfen in das Zielfenster gelangt.
if (E.xclient.message_type == XdndEnter) {
Zuerst initialisiert RGFW die erforderlichen Variablen.
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); }
Das obige ist der detaillierte Inhalt vonRGFW unter der Haube: XDrag & Drop. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!