ホームページ  >  記事  >  バックエンド開発  >  RGFW の内部: X ドラッグ &#n ドロップ

RGFW の内部: X ドラッグ &#n ドロップ

WBOY
WBOYオリジナル
2024-09-03 14:31:47991ブラウズ

RGFW Under the Hood: XDrag

導入

X11 でドラッグ アンド ドロップ イベントを処理するには、XDnD プロトコルを使用する必要があります。 XDnD プロトコルは他のドラッグ アンド ドロップ API に比べてかなり複雑ですが、それでも理論的には比較的単純です。ただし、X11 サーバーおよびソース ウィンドウと適切に通信する必要があるため、実装は面倒です。

このチュートリアルでは、XDnD プロトコルを処理し、X11 ドラッグ アンド ドロップ イベントを管理する方法を説明します。コードは RGFW のソース コードに基づいています。

概要

必要な手順の詳細な概要:

まず、X11 Atoms が初期化されます。 X11 アトムは、X11 を通じて特定のデータまたはプロパティを要求または送信するために使用されます。
次に、ウィンドウのプロパティが変更され、XDND (X ドラッグ アンド ドロップ) イベントを認識できるようになります。
ドラッグが発生すると、ウィンドウは、ドラッグが開始されたことをターゲット ウィンドウに伝える XdndEnter メッセージを含む ClientMessage イベントを受け取ります。
ドラッグの進行中、ソース ウィンドウは、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 アトムが必要です。

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 バージョンを設定する必要もあります。XDnD プロトコルの最新バージョンであるバージョン 5 を使用する必要があります。

const Atom XdndAware = XInternAtom(display, "XdndAware", False);
const char myversion = 5;

XChangeProperty(display, window, XdndAware, 4, 32, PropModeReplace, &myversion, 1);

ステップ 3 (ClientMessage 経由で XDnD イベントを処理する)

イベントが処理される前に、いくつかの変数を定義する必要があります。
これらの変数はソース ウィンドウによって提供され、複数のインスタンスにわたって使用されます。

これらの変数は、ソース ウィンドウ、使用される XDnD プロトコルコールのバージョン、およびドロップ データの形式です。

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 の内部: X ドラッグ &#n ドロップの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。