RGFW Di Bawah Tudung: XDrag &#n Drop

WBOY
Lepaskan: 2024-09-03 14:31:47
asal
972 orang telah melayarinya

RGFW Under the Hood: XDrag

pengenalan

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

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

Langkah 1 (Tentukan Atom X11)

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);
Salin selepas log masuk

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);
Salin selepas log masuk

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);
Salin selepas log masuk

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);
Salin selepas log masuk

Langkah 2 (Dayakan acara XDnD untuk tetingkap)

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);
Salin selepas log masuk

Langkah 3 (Kendalikan acara XDnD melalui ClientMessage)

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;
Salin selepas log masuk

Kini acara ClientMessage boleh dikendalikan.

case ClientMessage:
Salin selepas log masuk

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;
Salin selepas log masuk

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.

Langkah 3.1 (XdndEnter)

XdndEnter dihantar apabila drop memasuki tetingkap sasaran.

if (E.xclient.message_type == XdndEnter) {
Salin selepas log masuk

Pertama, RGFW memulakan pembolehubah yang diperlukan.

  • 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];
Salin selepas log masuk

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;
Salin selepas log masuk

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);
    } 
Salin selepas log masuk

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;
    }
Salin selepas log masuk

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;
}
Salin selepas log masuk

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)) {
Salin selepas log masuk

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;
Salin selepas log masuk

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);
Salin selepas log masuk

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;
}
Salin selepas log masuk

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) {
Salin selepas log masuk

First, we should make sure we registered a valid format earlier.

    if (format) {
Salin selepas log masuk

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);
    } 
Salin selepas log masuk

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);
    }
}
Salin selepas log masuk

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: {
Salin selepas log masuk

To do this, first, ensure the property is the XdndSelection.

/* this is only for checking for drops */

if (E.xselection.property != XdndSelection)
    break;
Salin selepas log masuk

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);
Salin selepas log masuk

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);
Salin selepas log masuk

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);
}
Salin selepas log masuk

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);
}
Salin selepas log masuk

Atas ialah kandungan terperinci RGFW Di Bawah Tudung: XDrag &#n Drop. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!