X11 utilizza un protocollo di appunti lato applicazione asincrono multiformato multi-buffer flessibile.
La maggior parte dei toolkit lo hanno implementato (gtk_clipboard_get()
di GTK , Qt è QApplication::clipboard()
, clipboard_get di Tk). Ma puoi farlo manualmente con l'API X11, ad esempio, se non stai utilizzando i toolkit o se devi passare una grande quantità di dati attraverso il buffer degli appunti senza tenerli tutti in memoria allo stesso tempo.
Teoria
Potrebbero esserci molti buffer, ma devi conoscerne solo due:
CLIPBOARD
è il solito buffer esplicito:copi le cose lì con il menu Modifica/Copia e le incolli con il menu Modifica/Incolla.PRIMARY
la selezione è una funzione implicita di selezione del mouse:il testo viene inserito quando viene selezionato con il cursore del mouse e viene incollato da esso facendo clic con il pulsante centrale del mouse nei campi di immissione del testo.
La selezione primaria non richiede pressioni di tasti, quindi è utile per copiare piccoli frammenti tra finestre che si trovano una accanto all'altra. Questa funzione è per lo più specifica per Unix, ma ho visto putty, trillian e alcune app gtk che la emulano su sistema operativo Windows. Anche firefox ha la funzione "Incolla e vai" quando si fa clic con il pulsante centrale su uno spazio vuoto non interattivo della pagina.
Per ottimizzare le cose quelle sono lato applicazione buffer:invece di inviare interi appunti/selezione al server ogni volta che cambia, l'applicazione dice semplicemente al server "lo possiedo". Per ottenere il buffer chiedi al proprietario di darti il suo contenuto. In questo modo anche un buffer di grandi dimensioni non richiede risorse fino a quando non viene effettivamente richiesto.
Quando richiedi il buffer chiedi al proprietario un formato specifico di cui hai bisogno. Ad esempio, un'immagine copiata dal browser seamonkey (fare clic con il pulsante destro del mouse su un'immagine e premere "Copia immagine") può essere rappresentata in diversi formati. Apparirebbe come URL dell'immagine se lo incolli nel terminale. Diventerebbe un'immagine caricata da quell'URL se la incolli in libreoffice writer. E sarebbe l'immagine stessa se incollata in gimp. Funziona perché seamonkey è intelligente e fornisce a ogni applicazione il formato richiesto:stringa di testo per terminale, html per libreoffice e dati immagine per gimp. Per richiedere il formato del testo devi chiedere UTF8_STRING
formato con fallback a STRING
.
Poiché chiedi a un'altra applicazione di preparare il buffer e ciò potrebbe richiedere del tempo, la richiesta è asincrona :il proprietario prepara il buffer, lo salva in una posizione specificata (la proprietà della finestra viene utilizzata come memoria temporanea) e ti avvisa con SelectionNotify
evento al termine.
Quindi, per ottenere il buffer:
- scegli il nome del buffer (
CLIPBOARD
,PRIMARY
), format(UTF8_STRING
,STRING
) e una proprietà della finestra in cui archiviare il risultato - chiama il
XConvertSelection()
per richiedere il buffer - aspetta
SelectionNotify
evento - legge il contenuto del buffer dalla proprietà della finestra
Implementazione ingenua
// gcc -o xclipget xclipget.c -lX11
#include <stdio.h>
#include <limits.h>
#include <X11/Xlib.h>
Bool PrintSelection(Display *display, Window window, const char *bufname, const char *fmtname)
{
char *result;
unsigned long ressize, restail;
int resbits;
Atom bufid = XInternAtom(display, bufname, False),
fmtid = XInternAtom(display, fmtname, False),
propid = XInternAtom(display, "XSEL_DATA", False),
incrid = XInternAtom(display, "INCR", False);
XEvent event;
XConvertSelection(display, bufid, fmtid, propid, window, CurrentTime);
do {
XNextEvent(display, &event);
} while (event.type != SelectionNotify || event.xselection.selection != bufid);
if (event.xselection.property)
{
XGetWindowProperty(display, window, propid, 0, LONG_MAX/4, False, AnyPropertyType,
&fmtid, &resbits, &ressize, &restail, (unsigned char**)&result);
if (fmtid == incrid)
printf("Buffer is too large and INCR reading is not implemented yet.\n");
else
printf("%.*s", (int)ressize, result);
XFree(result);
return True;
}
else // request failed, e.g. owner can't convert to the target format
return False;
}
int main()
{
Display *display = XOpenDisplay(NULL);
unsigned long color = BlackPixel(display, DefaultScreen(display));
Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0,0, 1,1, 0, color, color);
Bool result = PrintSelection(display, window, "CLIPBOARD", "UTF8_STRING") ||
PrintSelection(display, window, "CLIPBOARD", "STRING");
XDestroyWindow(display, window);
XCloseDisplay(display);
return !result;
}
Questo funzionerà per molti casi semplici. Una cosa che manca qui è il supporto per la lettura incrementale di buffer di grandi dimensioni. Aggiungiamolo!
Grandi buffer
Alcune app potrebbero voler copiare/incollare 100 gigabyte di registri di testo. E X11 lo permette! Ma i dati devono essere passati in modo incrementale, suddivisi in blocchi.
Se il buffer richiesto è troppo grande, invece di memorizzarlo nella proprietà della finestra, il proprietario imposta una proprietà di formato INCR
. Se lo elimini, il proprietario presuppone che tu l'abbia letto e inserisce il blocco successivo nella stessa proprietà. Ciò continua fino a quando l'ultimo blocco non viene letto ed eliminato. Infine il proprietario imposta la proprietà di dimensione 0 per contrassegnare la fine dei dati.
Quindi per leggere un buffer di grandi dimensioni elimini INCR
property e attendi che la proprietà appaia di nuovo (PropertyNotify
evento, stato ==PropertyNewValue
), leggilo ed eliminalo, attendi che appaia di nuovo e così via finché non appare con dimensione zero.
// gcc -o xclipget xclipget.c -lX11
#include <stdio.h>
#include <limits.h>
#include <X11/Xlib.h>
Bool PrintSelection(Display *display, Window window, const char *bufname, const char *fmtname)
{
char *result;
unsigned long ressize, restail;
int resbits;
Atom bufid = XInternAtom(display, bufname, False),
fmtid = XInternAtom(display, fmtname, False),
propid = XInternAtom(display, "XSEL_DATA", False),
incrid = XInternAtom(display, "INCR", False);
XEvent event;
XSelectInput (display, window, PropertyChangeMask);
XConvertSelection(display, bufid, fmtid, propid, window, CurrentTime);
do {
XNextEvent(display, &event);
} while (event.type != SelectionNotify || event.xselection.selection != bufid);
if (event.xselection.property)
{
XGetWindowProperty(display, window, propid, 0, LONG_MAX/4, True, AnyPropertyType,
&fmtid, &resbits, &ressize, &restail, (unsigned char**)&result);
if (fmtid != incrid)
printf("%.*s", (int)ressize, result);
XFree(result);
if (fmtid == incrid)
do {
do {
XNextEvent(display, &event);
} while (event.type != PropertyNotify || event.xproperty.atom != propid || event.xproperty.state != PropertyNewValue);
XGetWindowProperty(display, window, propid, 0, LONG_MAX/4, True, AnyPropertyType,
&fmtid, &resbits, &ressize, &restail, (unsigned char**)&result);
printf("%.*s", (int)ressize, result);
XFree(result);
} while (ressize > 0);
return True;
}
else // request failed, e.g. owner can't convert to the target format
return False;
}
int main()
{
Display *display = XOpenDisplay(NULL);
unsigned long color = BlackPixel(display, DefaultScreen(display));
Window window = XCreateSimpleWindow(display, DefaultRootWindow(display), 0,0, 1,1, 0, color, color);
Bool result = PrintSelection(display, window, "CLIPBOARD", "UTF8_STRING") ||
PrintSelection(display, window, "CLIPBOARD", "STRING");
XDestroyWindow(display, window);
XCloseDisplay(display);
return !result;
}
Ad esempio xsel
lo strumento usa INCR
trasferimento per buffer superiori a 4000. Secondo ICCCM, spetta all'applicazione scegliere un limite di dimensione ragionevole.
Lo stesso codice funziona per PRIMARY
selezione. Sostituisci "CLIPBOARD" con "PRIMARY" per stampare PRIMARY
contenuto della selezione.
Riferimenti
- Riepilogo di X Selections di Jamie Zawinski
- Manuale di programmazione Xlib - Selezioni
- ICCCM - Trasferimenti di dati di grandi dimensioni e protocollo INCR
- https://github.com/exebook/x11clipboard - minimo
XCopy()
eXPaste()
implementazioni xsel
exclip
fonti- The Secondary Selection - storia e idee di Charles Lindsey
Hai provato a trovare prima non un codice ma un programma con un'implementazione? L'ho fatto per te e ho trovato molte implementazioni che utilizzano chiamate X11 dirette. Penso che il più prezioso sia questo, ma potresti anche leggere questo. Basta trovare qualsiasi programma e cercare le fonti. Prova a cercare su wikipedia quali applicazioni utilizzano il sistema x11 clipboard/selection.
I seguenti programmi operano specificamente sui meccanismi di trasferimento dei dati:
xcutsel
trasferisce i dati dalle selezioni ai buffer tagliati o viceversa
xclipboard
,glipper
(Gnomo),parcellite
(LXDE) eklipper
(KDE) sono gestori di appunti, forsewmcliphist
anchexcb
mostra il contenuto dei buffer tagliati e permette all'utente di manipolarli xselezione,
xclip
,xsel
excopy
sono programmi a riga di comando che copiano i dati da o verso la selezione X. xcopy ha un'opzione di verbosità che aiuta a eseguire il debug dei problemi di Xselection. parcellite ha anche la capacità di leggere e scrivere su selezioni X specifiche dalla riga di comando.
synergy
è uno strumento multipiattaforma che ti consente di condividere gli appunti tra più computer che eseguono più sistemi operativi
xfce4-clipman-plugin
è un "plugin della cronologia degli appunti per il pannello Xfce4" e anche un gestore degli appunti xtranslate cerca le parole nella selezione X in un dizionario multilingue autocutsel sincronizza il buffer di taglio e il buffer di selezione
In breve, in teoria, X11 ha 2 "appunti":in realtà una tastiera e per le selezioni - il testo che hai selezionato immediatamente può essere incollato ovunque tu voglia premendo il pulsante centrale del mouse mentre la "tastiera" vera e propria è creata per gli scopi degli appunti principali/predefiniti come scambio di diversi tipi di oggetti.
P.S. Non lavorerei più con x11 dopo la mia esperienza. Divertiti :)