blog · git · desktop · images · contact & privacy · gopher

X11: Wie funktioniert „das“ Clipboard?

Wenn man ein anderes Betriebssystem benutzt hat, bevor man zu etwas mit X11 gewechselt ist, wird einem recht früh aufgefallen sein, dass es in X mehr als ein Clipboard gibt:

Manchmal kann man Text mit der Maus markieren, in ein andere Fenster wechseln und dann den Text durch Drücken der mittleren Maustaste einfügen.

Manchmal kann man Text markieren, dann einen Hotkey wie Strg+C drücken, das Fenster wechseln und dann mit einem Hotkey wie Strg+V den Text einfügen.

Manchmal geht beides.

Diese beiden Clipboards überschneiden sich auch oft nicht. Man kann den Inhalt des „Strg+C-Cliboards“ behalten und währenddessen über das „Clipboard der mittleren Maustaste“ etwas anderes kopieren und einfügen.

Wie funktioniert das? Gibt es mehr als ein Clipboard? Wie viele? Unterstützt jeder X-Client jede Art von Clipboard?

Hier ist der passende Abschnitt in ICCCM zu diesem Thema.

Selections als IPC-Mechanismus

Zuerst einmal: Im X11-Land heißen „Clipboards“ eigentlich „Selections“.

Ja, es gibt mehr als eine Selection und sie arbeiten alle unabhängig voneinander. Es ist sogar so, dass man so viele Selections benutzen kann, wie man möchte. Zumindest in der Theorie. Wenn man Selections benutzt, dann weist man verschiedene X-Clients an, miteinander zu reden. Das heißt natürlich, dass die Clients eine gemeinsame Sprache sprechen und sich darüber einig sein müssen, welche Selections sie verwenden wollen. Man kann also nicht einfach eine neue Selection erfinden und dann erwarten, dass Firefox damit kompatibel ist.

Aus der Vogelperspektive sieht das alles ungefähr so aus:

Client A X Server Client B ---------------------------------------------------------------- (1) | I own selection FOO! | | -------------------> | | Write sel. FOO to BAR! | (2) | <--------------------- | | Write sel. FOO to BAR! | | <--------------------- | | Here is FOO. | -------------------------:-----------------------> | Okay, got it. | | <------------------------:------------------------ |

Punkt (1) heißt, dass jeder Client zu jeder Zeit „Ownership“ einer Selection für sich beanspruchen kann. Der Client sagt das dem X-Server nur – es werden noch keine Daten übertragen. Das ist ein wichtiger Punkt. Der X-Server ist in diesem Fall eigentlich nichts weiter als eine Vermittlungsstelle. Er merkt sich, welcher Client welche Selection für sich beansprucht.

Bei (2) fragt ein anderer Client den X-Server nach dem Inhalt der Selection „FOO“. Der X-Server gibt diese Anfrage dann nur an den aktuellen Owner weiter. Client A ist dann dafür verantwortlich, die Daten tatsächlich zu Client B zu übermitteln.

Wie werden Selections identifiziert?

Ich habe das oben „Selection FOO“ genannt, um schon ein bisschen anzureißen, dass das ein ziemlich willkürlicher Bezeichner ist. Wenn man schonmal mit X11 etwas gemacht hat, wird man nicht überrascht sein, dass Selections über „Atoms“ identifiziert werden.

Kurze Wiederholung: Über Atoms kann man in X11 irgendwas identifizieren und die Dinger sind im Wesentlichen Strings. Intern werden ihnen zwar Nummern zugeordnet, aber man muss den X-Server nur selten fragen, „was ist der Name für Atom Nummer 42?“ In der Regel hantiert man als Programmierer nur mit den Strings.

Es gibt drei Selections mit „Standardnamen“:

PRIMARY : Das „Clipboard der mittleren Maustaste“

: Das „Clipboard der mittleren Maustaste“ SECONDARY : Heutzutage quasi unbenutzt

: Heutzutage quasi unbenutzt CLIPBOARD : Das „Strg+C-Clipboard“

„Standard“ heißt, dass diese Namen in ICCCM 2.6.1 spezifiziert sind. Und, ja, es ist verwirrend, dass eine der Selections „Clipboard“ heißt.

Programm 1: Selection-Owner erfragen

Mit dem, was wir bisher wissen, können wir den X-Server nach den derzeitigen Ownern fragen. Hier ist xowners.c :

#include <stdio.h> #include <X11/Xlib.h> int main() { Display *dpy; Window owner; Atom sel; char *selections[] = { "PRIMARY", "SECONDARY", "CLIPBOARD", "FOOBAR" }; size_t i; dpy = XOpenDisplay(NULL); if (!dpy) { fprintf(stderr, "Could not open X display

"); return 1; } for (i = 0; i < sizeof selections / sizeof selections[0]; i++) { sel = XInternAtom(dpy, selections[i], False); owner = XGetSelectionOwner(dpy, sel); printf("Owner of '%s': 0x%lX

", selections[i], owner); } return 0; }

Übersetzen kann man dieses und die folgenden Programme mit:

cc -Wall -Wextra -o xowners xowners.c -lX11

FOOBAR ist ein erfundener Name. Es ist völlig zulässig, den zu benutzen, aber man darf nicht erwarten, dass er mit allen Clients funktioniert. :-)

Wie man dann sehen kann, gibt das Programm Window-IDs aus:

$ ./xowners Owner of 'PRIMARY': 0x60080F Owner of 'SECONDARY': 0x0 Owner of 'CLIPBOARD': 0x1E00024 Owner of 'FOOBAR': 0x0

Windows sind ein weiteres grundlegendes Mittel in der X11-Welt, damit Clients miteinander kommunizieren können. Sprich, nicht jedes Window ist notwendigerweise ein sichtbares Rechteck mit Pixeln drin. „Unmapped Windows“ können problemlos in einer X-Session existieren (und üblicherweise gibt es davon auch einige).

Mit xwininfo kann man mehr über die beiden genannten Fenster herausfinden:

$ xwininfo -id 0x60080F | grep '^xwininfo' xwininfo: Window id: 0x60080f "xiate" $ xwininfo -id 0x1E00024 | grep '^xwininfo' xwininfo: Window id: 0x1e00024 "lariza"

Aha, xiate ist also derzeit „Besitzer“ der PRIMARY -Selection und lariza der des CLIPBOARD .

Die volle Ausgabe verrät noch mehr:

$ xwininfo -id 0x60080F xwininfo: Window id: 0x60080f "xiate" Absolute upper-left X: -100 Absolute upper-left Y: -100 Relative upper-left X: -100 Relative upper-left Y: -100 Width: 10 Height: 10 Depth: 0 ... Map State: IsUnMapped ...

Es ist also wirklich ein unsichtbares Fenster. Clients machen das oft so. Sie erstellen Fenster, deren einziger Zweck es ist, eine Selection zu verwalten. Sie könnten auch ihre sichtbaren Fenster dazu benutzen, das bringt jedoch ein paar Probleme mit sich. Sichtbare Fenster sind manchmal eher kurzlebig und sobald ein Fenster wieder gelöscht wird, verliert man auch den „Ownership“ einer Selection.

Inhaltstypen und Konvertierungen

So weit, so gut. Und so einfach.

Es fängt an, kompliziert zu werden, sobald einem auffällt, dass manche Clients das Clipboard ja für Text benutzen, andere für Bilder, wieder andere für Audio-Daten und dann ganz andere Clients für Datentypen, von denen man noch nie etwas gehört hat.

Und dann gibt es noch Situationen, in denen ein Client dieselben Daten in verschiedenen Formen anbieten möchte. Ein Beispiel hierfür: Man markiere Text in einem Webbrowser und kopiere ihn. Fügt man ihn in Vim ein, erhält man Plain Text. Fügt man ihn aber zum Beispiel in LibreOffice Writer ein, dann erhält man auf einmal formatierten Text mit fetter Schrift und Codeblöcken und allem drum und dran.

Man erinnere sich an das Diagramm am Anfang. Schritt 2 war: Client B sagt dem X-Server, er solle „FOO“ nach „BAR“ schreiben lassen. (Was „BAR“ ist, habe ich noch nicht erklärt, aber da kommen wir gleich hin.) Eigentlich müsste es aber heißen: „Schreibe ‚FOO‘ nach ‚BAR‘ und zwar konvertiert zu ‚BAZ‘“. Mit anderen Worten, Client B kann den Inhalt der Selection als Text anfragen oder als Bild oder als irgendetwas anderes.

Deswegen heißt der Bibliotheksaufruf für „gib’ mir den Inhalt einer Selection“ XConvertSelection() statt XGetSelection() .

Programm 2: Clipboard als UTF-8 erfragen

Das ist nun ein Beispiel für „Client B“:

#include <stdio.h> #include <X11/Xlib.h> void show_utf8_prop(Display *dpy, Window w, Atom p) { Atom da, incr, type; int di; unsigned long size, dul; unsigned char *prop_ret = NULL; /* Dummy call to get type and size. */ XGetWindowProperty(dpy, w, p, 0, 0, False, AnyPropertyType, &type, &di, &dul, &size, &prop_ret); XFree(prop_ret); incr = XInternAtom(dpy, "INCR", False); if (type == incr) { printf("Data too large and INCR mechanism not implemented

"); return; } /* Read the data in one go. */ printf("Property size: %lu

", size); XGetWindowProperty(dpy, w, p, 0, size, False, AnyPropertyType, &da, &di, &dul, &dul, &prop_ret); printf("%s", prop_ret); fflush(stdout); XFree(prop_ret); /* Signal the selection owner that we have successfully read the * data. */ XDeleteProperty(dpy, w, p); } int main() { Display *dpy; Window owner, target_window, root; int screen; Atom sel, target_property, utf8; XEvent ev; XSelectionEvent *sev; dpy = XOpenDisplay(NULL); if (!dpy) { fprintf(stderr, "Could not open X display

"); return 1; } screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); sel = XInternAtom(dpy, "CLIPBOARD", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False); owner = XGetSelectionOwner(dpy, sel); if (owner == None) { printf("'CLIPBOARD' has no owner

"); return 1; } printf("0x%lX

", owner); /* The selection owner will store the data in a property on this * window: */ target_window = XCreateSimpleWindow(dpy, root, -10, -10, 1, 1, 0, 0, 0); /* That's the property used by the owner. Note that it's completely * arbitrary. */ target_property = XInternAtom(dpy, "PENGUIN", False); /* Request conversion to UTF-8. Not all owners will be able to * fulfill that request. */ XConvertSelection(dpy, sel, utf8, target_property, target_window, CurrentTime); for (;;) { XNextEvent(dpy, &ev); switch (ev.type) { case SelectionNotify: sev = (XSelectionEvent*)&ev.xselection; if (sev->property == None) { printf("Conversion could not be performed.

"); return 1; } else { show_utf8_prop(dpy, target_window, target_property); return 0; } break; } } }

Mehr Code als erwartet? Tjoa. Nur Geduld. Wir gehen es einzeln durch.

Erst einmal enthüllen wir, was „BAR“ ist. Der Code oben erzeugt ein Fenster namens target_window und dann fragt er ein Atom namens target_property an. Diese beiden Dinge zusammengenommen ergeben „BAR“. Wenn Client A den Inhalt der Selection zu Client B schicken will, dann tut er das, indem er den Inhalt in die genannte Property des genannten Windows schreibt. Das ist so ziemlich der einzige Weg, mit dem zwei X-Clients beliebige Daten über den X-Server kommunizieren können.

Man erinnere sich, dass X11 netzwerktransparent ist. Die Clients A und B müssen nicht auf demselben Host laufen. Sie müssen noch nicht einmal dieselben Netzwerkprotokolle sprechen. Vielleicht spricht der eine TCP/IP und der andere … irgendwas anderes. ICCCM bringt hier als Beispiel das DECnet, was aber heute vermutlich niemand mehr benutzt. Das Ergebnis ist jedenfalls, dass die beiden Clients nicht direkt kommunizieren dürfen, sondern alles irgendwie über den X-Server schicken müssen.

Okay. Unser Ziel „BAR“ ist also ein Fenster und eine Property.

Wir brauchen noch einen Content Type. Ich habe hier UTF8_STRING benutzt. Dieses Atom wird man in ICCCM nicht finden. UTF-8 hat noch nicht einmal existiert, als ICCCM ursprünglich veröffentlicht wurde. Neuere Clients kommen damit jedoch klar.

Wir bitten den X-Server dann, die Konvertierung „durchzuführen“: XConvertSelection() . Jetzt nochmal genau auf das Diagramm am Anfang schauen. Es gibt keine unmittelbare Antwort auf XConvertSelection() . Der X-Server vermittelt die Anfrage nur weiter und schickt sie an Client A, sofern es im Moment überhaupt einen Client gibt, der die Selection für sich in Anspruch nimmt. Dann, irgendwann in der Zukunft, erledigt Client A seine Arbeit – oder vielleicht auch nicht. Das heißt, dass wir nur auf ein X-Event warten können. Dafür ist die Schleife am Ende da. Das Event SelectionNotify sagt uns, dass eine Konvertierung einer Selection durchgeführt wurde oder fehlgeschlagen ist. Wir können dann vielleicht weitermachen und die Property an unserem Fenster auslesen; Client A sollte seine Daten dort hingeschrieben haben.

Erwähnenswert weiterhin:

Client A kann die Daten vielleicht gar nicht liefern. Oder er könnte abgestürzt sein. Oder sonstwas. Client B darf also nicht blockieren und auf Abschluss des Datentransfers warten.

Client A kann die Daten vielleicht nicht konvertieren. Das passiert zum Beispiel, wenn man The GIMP nach Textdaten fragt, obwohl GIMP ein Bild im Clipboard hält.

Die Funktion XDeleteProperty() teilt Client A mit, dass wir die von ihm übermittelten Daten erfolgreich gelesen haben.

teilt Client A mit, dass wir die von ihm übermittelten Daten erfolgreich gelesen haben. Es ist nicht notwendig, vor der ganzen Aktion den aktuellen Owner einer Selection zu erfragen. Ich habe das nur gemacht, um schnell prüfen zu können, ob es überhaupt einen Owner gibt. (Macht man das nicht, bekommt man nur ein nicht aussagekräftiges „Konvertierung fehlgeschlagen.)

Programm 3: Eine Selection „ownen“

Das ist nun die andere Richtung. Ein Client beansprucht CLIPBOARD für sich und liefert Daten, sofern man nach UTF8_STRING als Typ fragt. Das wäre also Client A:

#include <stdio.h> #include <string.h> #include <time.h> #include <X11/Xlib.h> void send_no(Display *dpy, XSelectionRequestEvent *sev) { XSelectionEvent ssev; char *an; an = XGetAtomName(dpy, sev->target); printf("Denying request of type '%s'

", an); if (an) XFree(an); /* All of these should match the values of the request. */ ssev.type = SelectionNotify; ssev.requestor = sev->requestor; ssev.selection = sev->selection; ssev.target = sev->target; ssev.property = None; /* signifies "nope" */ ssev.time = sev->time; XSendEvent(dpy, sev->requestor, True, NoEventMask, (XEvent *)&ssev); } void send_utf8(Display *dpy, XSelectionRequestEvent *sev, Atom utf8) { XSelectionEvent ssev; time_t now_tm; char *now, *an; now_tm = time(NULL); now = ctime(&now_tm); an = XGetAtomName(dpy, sev->property); printf("Sending data to window 0x%lx, property '%s'

", sev->requestor, an); if (an) XFree(an); XChangeProperty(dpy, sev->requestor, sev->property, utf8, 8, PropModeReplace, (unsigned char *)now, strlen(now)); ssev.type = SelectionNotify; ssev.requestor = sev->requestor; ssev.selection = sev->selection; ssev.target = sev->target; ssev.property = sev->property; ssev.time = sev->time; XSendEvent(dpy, sev->requestor, True, NoEventMask, (XEvent *)&ssev); } int main() { Display *dpy; Window owner, root; int screen; Atom sel, utf8; XEvent ev; XSelectionRequestEvent *sev; dpy = XOpenDisplay(NULL); if (!dpy) { fprintf(stderr, "Could not open X display

"); return 1; } screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); /* We need a window to receive messages from other clients. */ owner = XCreateSimpleWindow(dpy, root, -10, -10, 1, 1, 0, 0, 0); sel = XInternAtom(dpy, "CLIPBOARD", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False); /* Claim ownership of the clipboard. */ XSetSelectionOwner(dpy, sel, owner, CurrentTime); for (;;) { XNextEvent(dpy, &ev); switch (ev.type) { case SelectionClear: printf("Lost selection ownership

"); return 1; break; case SelectionRequest: sev = (XSelectionRequestEvent*)&ev.xselectionrequest; printf("Requestor: 0x%lx

", sev->requestor); /* Property is set to None by "obsolete" clients. */ if (sev->target != utf8 || sev->property == None) send_no(dpy, sev); else send_utf8(dpy, sev, utf8); break; } } }

Es wird ein unsichtbares Fenster erzeugt und dann die Selection CLIPBOARD für sich beansprucht. Wie man sieht, besitzt also nicht „der Client“ eine Selection, sondern ein konkretes Fenster.

Das Programm wartet dann auf Events. SelectionClear ist einfach: Irgendein anderer Client kann Ownership des Clipboards übernommen haben. Ja, das kann zu jedem Zeitpunkt einfach so passieren.

SelectionRequest wird vom X-Server zu Client A geschickt. Das ist das Event, das erzeugt wird, wenn ein anderer Client XConvertSelection() ausführt. Wir prüfen dann ganz einfach, ob der Zieltyp target gleich UTF8_STRING ist. Falls nicht, verweigern wir die Konversion. Andernfalls rufen wir letztendlich XChangeProperty() auf, um am angegebenen Zielfenster die angegebene Property zu setzen. Wenn wir damit fertig sind, schicken wir selbst ein SelectionNotify -Event an Client B.

Dieser Client schickt nun die aktuelle Uhrzeit mit Datum an den Anfragesteller. Das habe ich als Beispiel gewählt, um zu zeigen, dass bei Clipboards keine Nutzdaten im X-Server gespeichert werden. Stattdessen werden die Daten nur auf Anfrage konvertiert (und vielleicht sogar dann erst erzeugt).

Programm 4: Inhaltstyp TARGETS

Ein paar besondere Inhaltstypen gibt es. Man kann den Owner einer Selection zum Beispiel nach dem Typ TARGETS fragen. Dabei wird keine tatsächliche Konvertierung der Nutzdaten passieren, sondern man bekommt als Antwort nur eine Liste von Atomen. Jedes davon ist ein gültiges Target für den aktuellen Inhalt dieses Clipboards.

#include <stdio.h> #include <X11/Xatom.h> #include <X11/Xlib.h> void show_targets(Display *dpy, Window w, Atom p) { Atom type, *targets; int di; unsigned long i, nitems, dul; unsigned char *prop_ret = NULL; char *an = NULL; /* Read the first 1024 atoms from this list of atoms. We don't * expect the selection owner to be able to convert to more than * 1024 different targets. :-) */ XGetWindowProperty(dpy, w, p, 0, 1024 * sizeof (Atom), False, XA_ATOM, &type, &di, &nitems, &dul, &prop_ret); printf("Targets:

"); targets = (Atom *)prop_ret; for (i = 0; i < nitems; i++) { an = XGetAtomName(dpy, targets[i]); printf(" '%s'

", an); if (an) XFree(an); } XFree(prop_ret); XDeleteProperty(dpy, w, p); } int main() { Display *dpy; Window target_window, root; int screen; Atom sel, targets, target_property; XEvent ev; XSelectionEvent *sev; dpy = XOpenDisplay(NULL); if (!dpy) { fprintf(stderr, "Could not open X display

"); return 1; } screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); sel = XInternAtom(dpy, "CLIPBOARD", False); targets = XInternAtom(dpy, "TARGETS", False); target_property = XInternAtom(dpy, "PENGUIN", False); target_window = XCreateSimpleWindow(dpy, root, -10, -10, 1, 1, 0, 0, 0); XConvertSelection(dpy, sel, targets, target_property, target_window, CurrentTime); for (;;) { XNextEvent(dpy, &ev); switch (ev.type) { case SelectionNotify: sev = (XSelectionEvent*)&ev.xselection; if (sev->property == None) { printf("Conversion could not be performed.

"); return 1; } else { show_targets(dpy, target_window, target_property); return 0; } break; } } }

Wenn man das nun laufen lässt, während ein typischer GTK-Client Owner einer Selection ist, dann sieht man etwas Interessantes:

$ ./xtargets Targets: 'TIMESTAMP' 'TARGETS' 'MULTIPLE' 'SAVE_TARGETS' 'UTF8_STRING' 'COMPOUND_TEXT' 'TEXT' 'STRING' 'text/plain;charset=utf-8' 'text/plain'

X11 ist alt und es gibt viele Konventionen, um Datentypen anzugeben. Manche davon sind Altlasten, andere missverständlich, viele gar nicht von ICCCM erwähnt. MIME-Types sind heute vermutlich okay, aber in ICCCM liest man nichts davon.

Das fühlt sich recht chaotisch an. Das ist der Preis, den man zahlen muss, wenn man noch mit 30-Jahre alten Clients kompatibel sein will.

Binäraten mit xclip handhaben

Ich habe mich schon lange gefragt, wieso ich denn mit xclip kein Bilder einfügen kann. Sollte doch eigentlich einfach sein: xclip -o >foo.img . Naja, nee. Jetzt weiß ich, wie es geht, und dann ist es tatsächlich einfach. :-)

Zunächst kopiere man ein Bild mit einem Tool wie The GIMP.

xclip kann den Typ TARGETS erfragen:

$ xclip -o -target TARGETS -selection clipboard TIMESTAMP TARGETS MULTIPLE SAVE_TARGETS image/png image/tiff image/x-icon image/x-ico image/x-win-bitmap image/vnd.microsoft.icon application/ico image/ico image/icon text/ico image/bmp image/x-bmp image/x-MS-bmp image/jpeg

Man wähle etwas Geeignetes aus und frage dann die Daten an:

$ xclip -o -target image/png -selection clipboard >foo.png $ file foo.png foo.png: PNG image data, 373 x 309, 8-bit/color RGBA, non-interlaced

Nicht kompliziert. Auf diese Weise kann man mit xclip dann auch Bilddaten kopieren, man muss nur mit -t einen MIME-Typ angeben.

(Es sei übrigens ergänzend erwähnt, dass die damalige xclip -Version aus Ubuntu 12.04 sehr wahrscheinlich noch nicht mit TARGETS umgehen konnte. Ich habe kein 12.04 mehr zur Hand, um es auszuprobieren, aber die Version, die man bei Ubuntu herunterladen kann, erwähnt in den Command Line Options kein -target .)

Große Datenmengen

Aufgefallen ist vielleicht, dass Programm Nummer 2 oben abbricht, wenn da irgendwas mit INCR vorkommt. Das ist einer der vielen Hacks im Selection-Umfeld.

Eine Property an einem Fenster kann nur eine beschränkte Menge an Daten enthalten, da die Property im Speicher des X-Servers existiert. Will man also mehrere Megabyte mittels Selections übermitteln, dann muss man dafür als Sender seine Daten in kleinere Chunks aufteilen. Client B muss die Daten entsprechend wieder zusammensetzen. So ein Chunk ist wohl ungefähr 256 kB groß. Nicht so viel, reicht aber tatsächlich in den meisten Fällen. Schade ist, dass das jeder Client neu für sich implementieren muss. Das macht Clients komplexer.

Clipboard-Manager

Im Alltag ist sowas vielleicht schon passiert: Man öffnet ein Fenster, markiert Text, drückt Strg+C, schließt das Fenster – und dann? Ist der Text wieder aus dem Clipboard raus. Klar, denn es gibt ja gar kein Clipboard im Server. Der eben geschlossene Client hat die Daten vorgehalten und darauf gewartet, dass jemand danach fragt. Jetzt ist dieser Client weg und damit auch die Daten. Von anderen Betriebssystemen ist man das anders gewohnt. Aber selbst wenn alle Betriebssysteme so arbeiten würden, wäre das Verhalten immer noch ein bisschen lästig.

Es gibt dafür keine „saubere“ Lösung in X11. Stattdessen schlägt ICCCM vor, einen Clipboard-Manager zu schreiben. Der funktioniert ungefähr so:

Der Clipboard-Manager beansprucht eine Selection für sich.

Sobald jemand anderes die Selection übernimmt: Frage den aktuellen, neuen Owner nach dem Content der Selection. Halte die Daten bei dir selbst im Speicher vor. Der Clipboard-Manager macht sich jetzt wieder zum Owner.



Das klingt danach, als seien da einige Race Conditions im Spiel. Außerdem geht es kaputt, wenn ein Client den TARGETS -Typ nicht unterstützt. Es wird allerdings von ICCCM verlangt, dass ein Client auf eine TARGETS -Anfrage antworten kann, also „sollte“ das schon funktionieren.

Zusammenfassung

Ich denke, es ist wichtig, zu verstehen, dass der X-Server nur eine Vermittlungsstelle ist. Die Clients reden mittels des Server direkt miteinander und tauschen so die Nutzdaten aus. Es gibt kein Clipboard „im“ Server. Daten werden nur auf Anfrage konvertiert beziehungsweise erzeugt. Man kann so viele Selections benutzen, wie man möchte, aber nicht alle Clients unterstützen alle davon.

Eine schließende Anmerkung: Auf den ersten Blick wirken X11-Selections ziemlich einfach. Ich befürchte aber, dass die bald annähernd so komplex sind wie Zeitzonen. Selbst das „Standard“-Utility xclip ist nicht vollständig ICCCM-kompatibel und enthält hier und da ein „FIXME“. Es gibt viele Race Conditions und Sonderfälle.

tl; dr: Wenn irgendwie möglich, benutz’ eine Bibliothek dafür.

Addendum, 2019-07-28

Der ursprüngliche Code von 2017 hat XSelectInput() aufgerufen. Ich wollte dem X-Server sagen, dass meine Fenster Events wie SelectionNotify empfangen sollen. Das war aber falsch. Es gibt für diese Events schlichtweg keine Masken und man muss sie nicht selektieren.

Danke, Ulrich!