Untuk mengendalikan acara Drag 'n Drop dengan X11, anda mesti menggunakan protokol XDnD. Walaupun protokol XDnD jauh lebih rumit daripada API Drag 'n Drop yang lain, ia masih agak mudah dari segi teori. Walau bagaimanapun, melaksanakannya adalah membosankan kerana ia memerlukan komunikasi yang betul dengan pelayan X11 dan tetingkap sumber.
Tutorial ini menerangkan cara mengendalikan protokol XDnD dan mengurus acara Drag 'n Drop X11. Kod ini adalah berdasarkan kod sumber RGFW.
Gambaran keseluruhan terperinci tentang langkah-langkah yang diperlukan:
Pertama, X11 Atoms akan dimulakan. X11 Atom digunakan untuk meminta atau menghantar data atau sifat tertentu melalui X11.
Kemudian, sifat tetingkap akan ditukar, membolehkannya mengetahui peristiwa XDND (X Drag 'n Drop).
Apabila seretan berlaku, tetingkap akan menerima Acara ClientMessage yang termasuk mesej XdndEnter memberitahu tetingkap sasaran bahawa seretan telah bermula.
Semasa seretan sedang berjalan, tetingkap sumber menghantar kemas kini tentang seretan ke tetingkap sasaran melalui acara ClientMessage. Setiap kali tetingkap sasaran mendapat kemas kini, ia mesti mengesahkan ia menerima kemas kini; jika tidak, interaksi akan berakhir.
Sebaik sahaja penurunan berlaku, tetingkap sumber akan menghantar mesej XdndDrop. Kemudian tetingkap sasaran akan menukar pemilihan drop melalui X11 dan akan menerima acara SelectionNotify untuk mendapatkan data yang ditukar.
Tetingkap sasaran akan mengendalikan acara ini, menukar data kepada rentetan yang boleh dibaca dan akhirnya menghantar ClientMessage dengan atom XdndFinished untuk memberitahu tetingkap sumber bahawa interaksi telah dilakukan.
Ikhtisar ringkas langkah-langkah yang diperlukan:
1) Takrifkan Atom X11
2) Dayakan acara XDnD untuk tetingkap
3) Mengendalikan acara XDnD melalui ClientMessage
4) Dapatkan data drop XDnD melalui ClientMessage dan tamatkan interaksi
Untuk mengendalikan peristiwa XDnD, atom XDnD mesti dimulakan melalui XInternAtom. Atom digunakan apabila menghantar atau meminta data atau tindakan tertentu.
XdndTypeList digunakan apabila tetingkap sasaran ingin mengetahui jenis data yang disokong oleh tetingkap sumber.
XdndSelection digunakan untuk memeriksa pemilihan data selepas jatuh dan untuk mendapatkan semula data selepas ia ditukar.
const Atom XdndTypeList = XInternAtom(display, "XdndTypeList", False); const Atom XdndSelection = XInternAtom(display, "XdndSelection", False);
Atom Xdnd generik ini ialah mesej yang dihantar oleh tetingkap sumber kecuali untuk XdndStatus.
XdndEnter, digunakan apabila drop telah memasuki tetingkap sasaran.
XdndPosition digunakan untuk mengemas kini tetingkap sasaran pada kedudukan jatuh.
XdndStatus digunakan untuk memberitahu tetingkap sumber bahawa sasaran telah menerima mesej.
XdndLeave digunakan apabila drop telah meninggalkan tetingkap sasaran.
XdndDrop digunakan apabila drop telah dijatuhkan ke dalam tetingkap sasaran.
XdndFinished digunakan apabila drop telah selesai.
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);
Tindakan Xdnd ialah tindakan yang ingin dilakukan oleh tetingkap sasaran dengan data seret.
XdndActionCopy digunakan apabila tetingkap sasaran mahu menyalin data seret.
const Atom XdndActionCopy = XInternAtom(display, "XdndActionCopy", False);
Senarai teks/uri dan teks/atom biasa diperlukan untuk menyemak format data jatuh.
const Atom XtextUriList = XInternAtom((Display*) display, "text/uri-list", False); const Atom XtextPlain = XInternAtom((Display*) display, "text/plain", False);
Untuk menerima peristiwa XDnD, tetingkap mesti mendayakan atom XDndAware. Atom ini memberitahu pengurus tetingkap dan tetingkap sumber bahawa tetingkap itu mahu menerima peristiwa XDnD.
Ini boleh dilakukan dengan mencipta atom XdndAware dan menggunakan XChangeProperty untuk menukar sifat XdndAware tetingkap.
Anda juga mesti menetapkan versi XDnD menggunakan penuding, versi 5 harus digunakan kerana ia adalah versi terbaru protokol XDnD.
const Atom XdndAware = XInternAtom(display, "XdndAware", False); const char myversion = 5; XChangeProperty(display, window, XdndAware, 4, 32, PropModeReplace, &myversion, 1);
Sebelum sebarang acara dikendalikan, beberapa pembolehubah mesti ditakrifkan.
Pembolehubah ini diberikan kepada kami oleh tetingkap sumber dan digunakan merentas berbilang kejadian.
Pembolehubah ini ialah tetingkap sumber, versi Protocall XDnD yang digunakan dan format data drop.
int64_t source, version; int32_t format;
Kini acara ClientMessage boleh dikendalikan.
case ClientMessage:
Pertama, saya akan mencipta struktur XEvent generik untuk membalas acara XDnD. Ini adalah pilihan, tetapi dalam menggunakannya kita perlu melakukan lebih sedikit kerja.
Ini akan menghantar acara ke tetingkap sumber dan memasukkan tetingkap kami (sasaran) dalam data.
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;
Struktur acara ClientMessage boleh diakses melalui XEvent.xclient.
message_type ialah atribut dalam struktur, ia memegang jenis mesej itu. Kami akan menggunakannya untuk menyemak sama ada jenis mesej ialah mesej XDnD.
Terdapat 3 acara XDnD yang akan kami kendalikan, XdndEnter, XdndPosition dan XdndDrop.
XdndEnter dihantar apabila drop memasuki tetingkap sasaran.
if (E.xclient.message_type == XdndEnter) {
Pertama, RGFW memulakan pembolehubah yang diperlukan.
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); }
Atas ialah kandungan terperinci RGFW Di Bawah Tudung: XDrag n Drop. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!