> 백엔드 개발 > C++ > 본문

RGFW 세부 정보: XDrag &#n Drop

WBOY
풀어 주다: 2024-09-03 14:31:47
원래의
985명이 탐색했습니다.

RGFW Under the Hood: XDrag

소개

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 드롭 데이터를 가져오고 상호작용을 종료합니다

1단계(X11 원자 정의)

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);
로그인 후 복사

2단계(창에 XDnD 이벤트 활성화)

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);
로그인 후 복사

3단계(ClientMessage를 통해 XDnD 이벤트 처리)

이벤트를 처리하기 전에 일부 변수를 정의해야 합니다.
이러한 변수는 소스 창을 통해 제공되며 여러 인스턴스에서 사용됩니다.

이러한 변수는 소스 창, 사용된 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가지입니다.

3.1단계(XdndEnter)

XdndEnter는 드롭이 대상 창에 들어갈 때 전송됩니다.

if (E.xclient.message_type == XdndEnter) {
로그인 후 복사

먼저 RGFW는 필수 변수를 초기화합니다.

  • count: number of formats in the the format list,
  • formats: the list of supported formats and
  • real_formats: this is used here to avoid running malloc for each drop
    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;
}
로그인 후 복사

Step 3.2 (XdndPosition)

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;
}
로그인 후 복사

Step 3.3 (XdndDrop)

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);
    }
}
로그인 후 복사

Step 4 (Get the XDnD drop data via ClientMessage and end the interaction)

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);
}
로그인 후 복사

Full code example

// 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿