Heim > Backend-Entwicklung > C++ > RGFW unter der Haube: XDrag & Drop

RGFW unter der Haube: XDrag & Drop

WBOY
Freigeben: 2024-09-03 14:31:47
Original
1019 Leute haben es durchsucht

RGFW Under the Hood: XDrag

Einführung

Um Drag 'n Drop-Ereignisse mit X11 zu verarbeiten, müssen Sie das XDnD-Protokoll verwenden. Obwohl das XDnD-Protokoll deutlich komplizierter ist als andere Drag & Drop-APIs, ist es theoretisch immer noch relativ einfach. Die Implementierung ist jedoch mühsam, da eine ordnungsgemäße Kommunikation mit dem X11-Server und dem Quellfenster erforderlich ist.

In diesem Tutorial wird erläutert, wie Sie mit dem XDnD-Protokoll umgehen und X11-Drag-and-Drop-Ereignisse verwalten. Der Code basiert auf dem Quellcode von RGFW.

Überblick

Eine detaillierte Übersicht der erforderlichen Schritte:

Zuerst werden X11-Atome initialisiert. X11-Atome werden verwendet, um bestimmte Daten oder Eigenschaften über X11 abzufragen oder zu senden.
Anschließend werden die Eigenschaften des Fensters geändert, sodass es XDND-Ereignisse (X Drag 'n Drop) erkennt.
Wenn ein Ziehen stattfindet, empfängt das Fenster ein ClientMessage-Ereignis, das eine XdndEnter-Nachricht enthält, die dem Zielfenster mitteilt, dass das Ziehen begonnen hat.
Während das Ziehen ausgeführt wird, sendet das Quellfenster über ClientMessage-Ereignisse Aktualisierungen zum Ziehen an das Zielfenster. Jedes Mal, wenn das Zielfenster ein Update erhält, muss es bestätigen, dass es das Update erhalten hat. Andernfalls wird die Interaktion beendet.
Sobald der Drop erfolgt, sendet das Quellfenster eine XdndDrop-Nachricht. Anschließend konvertiert das Zielfenster die Drop-Auswahl über X11 und empfängt ein SelectionNotify-Ereignis, um die konvertierten Daten abzurufen.
Das Zielfenster verarbeitet dieses Ereignis, konvertiert die Daten in eine lesbare Zeichenfolge und sendet schließlich eine ClientMessage mit dem XdndFinished-Atom, um dem Quellfenster mitzuteilen, dass die Interaktion abgeschlossen ist.

Ein kurzer Überblick über die erforderlichen Schritte:

1) Definieren Sie X11-Atome
2) Aktivieren Sie XDnD-Ereignisse für das Fenster
3) Verarbeiten Sie XDnD-Ereignisse über ClientMessage
4) Holen Sie sich die XDnD-Drop-Daten über ClientMessage und beenden Sie die Interaktion

Schritt 1 (X11-Atome definieren)

Um XDnD-Ereignisse zu verarbeiten, müssen XDnD-Atome über XInternAtom initialisiert werden. Atome werden beim Senden oder Anfordern bestimmter Daten oder Aktionen verwendet.

XdndTypeList wird verwendet, wenn das Zielfenster wissen möchte, welche Datentypen das Quellfenster unterstützt.
XdndSelection wird verwendet, um die Datenauswahl nach einem Drop zu untersuchen und die Daten nach der Konvertierung abzurufen.

const Atom XdndTypeList = XInternAtom(display, "XdndTypeList", False);
const Atom XdndSelection = XInternAtom(display, "XdndSelection", False);
Nach dem Login kopieren

Diese generischen Xdnd-Atome sind Nachrichten, die vom Quellfenster gesendet werden, mit Ausnahme von XdndStatus.

XdndEnter, wird verwendet, wenn der Tropfen das Zielfenster erreicht hat.
XdndPosition wird verwendet, um das Zielfenster an der Position des Tropfens zu aktualisieren.
XdndStatus wird verwendet, um dem Quellfenster mitzuteilen, dass das Ziel die Nachricht empfangen hat.
XdndLeave wird verwendet, wenn der Tropfen das Zielfenster verlassen hat.
XdndDrop wird verwendet, wenn der Drop im Zielfenster abgelegt wurde.
XdndFinished wird verwendet, wenn der Drop abgeschlossen ist.

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);
Nach dem Login kopieren

Xdnd-Aktionen sind Aktionen, die das Zielfenster mit den Ziehdaten durchführen möchte.

XdndActionCopy wird verwendet, wenn das Zielfenster die Ziehdaten kopieren möchte.

const Atom XdndActionCopy = XInternAtom(display, "XdndActionCopy", False);
Nach dem Login kopieren

Die Atome text/uri-list und text/plain werden benötigt, um das Format der Drop-Daten zu überprüfen.

const Atom XtextUriList = XInternAtom((Display*) display, "text/uri-list", False); 
const Atom XtextPlain = XInternAtom((Display*) display, "text/plain", False);
Nach dem Login kopieren

Schritt 2 (XDnD-Ereignisse für das Fenster aktivieren)

Um XDnD-Ereignisse zu empfangen, muss das Fenster das XDndAware-Atom aktivieren. Dieses Atom teilt dem Fenstermanager und dem Quellfenster mit, dass das Fenster XDnD-Ereignisse empfangen möchte.

Dies kann erreicht werden, indem ein XdndAware-Atom erstellt und XChangeProperty verwendet wird, um die XdndAware-Eigenschaft des Fensters zu ändern.

Sie müssen auch die XDnD-Version mithilfe eines Zeigers festlegen. Version 5 sollte verwendet werden, da es sich um die neueste Version des XDnD-Protokolls handelt.

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

XChangeProperty(display, window, XdndAware, 4, 32, PropModeReplace, &myversion, 1);
Nach dem Login kopieren

Schritt 3 (XDnD-Ereignisse über ClientMessage verarbeiten)

Bevor Ereignisse behandelt werden, müssen einige Variablen definiert werden.
Diese Variablen werden uns vom Quellfenster bereitgestellt und über mehrere Instanzen hinweg verwendet.

Diese Variablen sind das Quellfenster, die verwendete XDnD-Protokollversion und das Format der Drop-Daten.

int64_t source, version;
int32_t format;
Nach dem Login kopieren

Jetzt kann das ClientMessage-Ereignis verarbeitet werden.

case ClientMessage:
Nach dem Login kopieren

Zuerst werde ich eine generische XEvent-Struktur erstellen, um auf XDnD-Ereignisse zu antworten. Dies ist optional, aber bei der Verwendung müssen wir weniger Arbeit leisten.

Dadurch wird das Ereignis an das Quellfenster gesendet und unser Fenster (das Ziel) in die Daten einbezogen.

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;
Nach dem Login kopieren

Auf die ClientMessage-Ereignisstruktur kann über XEvent.xclient zugegriffen werden.

message_type ist ein Attribut in der Struktur, es enthält den Nachrichtentyp. Wir werden damit prüfen, ob es sich bei dem Nachrichtentyp um eine XDnD-Nachricht handelt.

Es gibt 3 XDnD-Ereignisse, die wir verarbeiten werden: XdndEnter, XdndPosition und XdndDrop.

Schritt 3.1 (XdndEnter)

XdndEnter wird gesendet, wenn der Tropfen in das Zielfenster gelangt.

if (E.xclient.message_type == XdndEnter) {
Nach dem Login kopieren

Zuerst initialisiert RGFW die erforderlichen Variablen.

  • 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];
Nach dem Login kopieren

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;
Nach dem Login kopieren

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);
    } 
Nach dem Login kopieren

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;
    }
Nach dem Login kopieren

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;
}
Nach dem Login kopieren

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)) {
Nach dem Login kopieren

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;
Nach dem Login kopieren

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);
Nach dem Login kopieren

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;
}
Nach dem Login kopieren

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) {
Nach dem Login kopieren

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

    if (format) {
Nach dem Login kopieren

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);
    } 
Nach dem Login kopieren

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);
    }
}
Nach dem Login kopieren

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: {
Nach dem Login kopieren

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

/* this is only for checking for drops */

if (E.xselection.property != XdndSelection)
    break;
Nach dem Login kopieren

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);
Nach dem Login kopieren

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);
Nach dem Login kopieren

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);
}
Nach dem Login kopieren

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);
}
Nach dem Login kopieren

Das obige ist der detaillierte Inhalt vonRGFW unter der Haube: XDrag & Drop. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage