X11로 Drag 'n Drop 이벤트를 처리하려면 XDnD 프로토콜을 사용해야 합니다. XDnD 프로토콜은 다른 Drag 'n Drop API보다 훨씬 더 복잡하지만 이론상으로는 여전히 상대적으로 간단합니다. 하지만 이를 구현하려면 X11 서버 및 소스창과의 원활한 통신이 필요하기 때문에 지루한 작업입니다.
이 튜토리얼에서는 XDnD 프로토콜을 처리하고 X11 Drag 'n Drop 이벤트를 관리하는 방법을 설명합니다. 해당 코드는 RGFW의 소스코드를 기반으로 작성되었습니다.
필요한 단계에 대한 자세한 개요:
먼저 X11 Atom이 초기화됩니다. X11 Atom은 X11을 통해 특정 데이터나 속성을 요청하거나 보내는 데 사용됩니다.
그런 다음 창의 속성이 변경되어 XDND(X Drag 'n Drop) 이벤트를 인식할 수 있습니다.
드래그가 발생하면 창은 드래그가 시작되었음을 대상 창에 알리는 XdndEnter 메시지가 포함된 ClientMessage 이벤트를 수신합니다.
드래그가 진행되는 동안 소스 창은 ClientMessage 이벤트를 통해 드래그에 대한 업데이트를 대상 창으로 보냅니다. 대상 창이 업데이트를 받을 때마다 업데이트를 받았는지 확인해야 합니다. 그렇지 않으면 상호작용이 종료됩니다.
드롭이 발생하면 소스 창은 XdndDrop 메시지를 보냅니다. 그런 다음 대상 창은 X11을 통해 드롭 선택을 변환하고 SelectionNotify 이벤트를 수신하여 변환된 데이터를 가져옵니다.
대상 창은 이 이벤트를 처리하고 데이터를 읽을 수 있는 문자열로 변환한 다음 마지막으로 XdndFinished 원자와 함께 ClientMessage를 보내 상호 작용이 완료되었음을 소스 창에 알립니다.
필요한 단계에 대한 간략한 개요:
1) X11 Atom 정의
2) 창에 XDnD 이벤트 활성화
3) ClientMessage를 통해 XDnD 이벤트 처리
4) ClientMessage를 통해 XDnD 드롭 데이터를 가져오고 상호작용을 종료합니다
XDnD 이벤트를 처리하려면 XInternAtom을 통해 XDnD Atom을 초기화해야 합니다. Atom은 특정 데이터나 작업을 보내거나 요청할 때 사용됩니다.
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 Actions는 대상 창이 드래그 데이터로 수행하려는 작업입니다.
XdndActionCopy는 대상 창이 드래그 데이터를 복사하려고 할 때 사용됩니다.
const Atom XdndActionCopy = XInternAtom(display, "XdndActionCopy", False);
드롭 데이터의 형식을 확인하려면 text/uri-list 및 text/plain Atom이 필요합니다.
const Atom XtextUriList = XInternAtom((Display*) display, "text/uri-list", False); const Atom XtextPlain = XInternAtom((Display*) display, "text/plain", False);
XDnD 이벤트를 수신하려면 창에서 XDndAware Atom을 활성화해야 합니다. 이 원자는 창 관리자와 소스 창에 창이 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:
먼저 XDnD 이벤트에 응답하기 위한 일반 XEvent 구조를 만듭니다. 이는 선택 사항이지만 이를 사용하면 작업량이 줄어듭니다.
이렇게 하면 이벤트가 소스 창으로 전송되고 데이터에 우리 창(대상)이 포함됩니다.
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 메시지인지 확인하는 데 사용하겠습니다.
우리가 처리할 XDnD 이벤트는 XdndEnter, XdndPosition, XdndDrop 3가지입니다.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!