首頁  >  文章  >  後端開發  >  RGFW 底層:XDrag &#n Drop

RGFW 底層:XDrag &#n Drop

WBOY
WBOY原創
2024-09-03 14:31:47991瀏覽

RGFW Under the Hood: XDrag

介紹

要使用 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掉落資料並結束互動

步驟 1(定義 X11 原子)

要處理 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);

步驟 2(為視窗啟用 XDnD 事件)

要接收 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);

步驟 3(透過 ClientMessage 處理 XDnD 事件)

在處理任何事件之前,必須先定義一些變數。
這些變數由來源視窗提供給我們,並在多個實例中使用。

這些變數是來源視窗、使用的 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。

步驟 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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
上一篇:C 中的頭文件下一篇:C 中的頭文件