X11 utiliza un protocolo de portapapeles del lado de la aplicación asíncrono de formato múltiple y búfer flexible.
La mayoría de los kits de herramientas lo tienen implementado (GTK's gtk_clipboard_get()
, QApplication::clipboard()
de Qt , portapapeles_get de Tk). Pero puede hacerlo manualmente con la API X11, por ejemplo, si no está utilizando kits de herramientas o si debe pasar una gran cantidad de datos a través del búfer del portapapeles sin mantenerlos todos en la memoria al mismo tiempo.
Teoría
Puede haber muchos búferes, pero solo necesita saber acerca de dos:
CLIPBOARD
es el búfer explícito habitual:copia cosas allí con el menú Editar/Copiar y lo pega con el menú Editar/Pegar.PRIMARY
La selección es una función de selección implícita del mouse:el texto ingresa cuando se selecciona con el cursor del mouse y se pega al hacer clic con el botón central en los campos de entrada de texto.
La selección principal no necesita presionar teclas, por lo que es útil para copiar pequeños fragmentos entre ventanas que están una al lado de la otra. Esta característica es en su mayoría específica de Unix, pero he visto que Puty, Trillian y algunas aplicaciones de gtk la emulan en el sistema operativo Windows. Firefox también tiene la función "Pegar y listo" al hacer clic con el botón central en un espacio vacío no interactivo de la página.
Para optimizar las cosas, esos son del lado de la aplicación búferes:en lugar de enviar toda la selección/portapapeles al servidor cada vez que cambia, la aplicación simplemente le dice al servidor "Me pertenece". Para obtener el búfer, le pide al propietario que le proporcione su contenido. De esta manera, incluso un búfer grande no consume recursos hasta que realmente se solicita.
Al solicitar el búfer, le solicita al propietario un formato específico que necesita. Por ejemplo, una imagen copiada del navegador seamonkey (haga clic derecho en una imagen y presione "Copiar imagen") se puede representar en diferentes formatos. Aparecería como URL de imagen si la pega en la terminal. Se convertiría en una imagen cargada desde esa URL si la pega en el escritor de libreoffice. Y sería la imagen misma si se pegara en gimp. Eso funciona porque seamonkey es inteligente y proporciona a cada aplicación el formato que solicita:cadena de texto para terminal, html para libreoffice y datos de imagen para gimp. Para solicitar formato de texto, pediría UTF8_STRING
formato con respaldo a STRING
.
Como le pide a otra aplicación que prepare el búfer, y eso puede llevar algún tiempo, la solicitud es asincrónica. :el propietario prepara el búfer, lo guarda en una ubicación específica (la propiedad de la ventana se usa como almacenamiento temporal) y le notifica con SelectionNotify
evento cuando haya terminado.
Entonces, para obtener el búfer:
- elija el nombre del búfer (
CLIPBOARD
,PRIMARY
), formato (UTF8_STRING
,STRING
) y una propiedad de ventana para almacenar el resultado - llame al
XConvertSelection()
para solicitar el búfer - espera a
SelectionNotify
evento - leer el contenido del búfer desde la propiedad de la ventana
Implementación 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;
}
Esto funcionará para muchos casos simples. Una cosa que falta aquí es la compatibilidad con la lectura incremental de búferes grandes. ¡Vamos a agregarlo!
Búferes grandes
Algunas aplicaciones pueden querer copiar/pegar 100 gigabytes de registros de texto. ¡Y X11 lo permite! Pero los datos deben pasarse de forma incremental, divididos en fragmentos.
Si el búfer solicitado es demasiado grande, en lugar de almacenarlo en la propiedad de la ventana, el propietario establece una propiedad de formato INCR
. Si lo elimina, el propietario asume que lo ha leído y coloca el siguiente fragmento en la misma propiedad. Eso continúa hasta que se lee y elimina el último fragmento. Finalmente, el propietario establece la propiedad de tamaño 0 para marcar el final de los datos.
Entonces, para leer un búfer grande, elimine INCR
propiedad y espere a que la propiedad vuelva a aparecer (PropertyNotify
evento, estado ==PropertyNewValue
), léelo y bórralo, espera a que vuelva a aparecer, y así sucesivamente hasta que aparezca con tamaño cero.
// 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;
}
Por ejemplo xsel
la herramienta usa INCR
transferencia para búferes de más de 4000. Según ICCCM, depende de la aplicación elegir un límite de tamaño razonable.
El mismo código funciona para PRIMARY
selección. Reemplace "PORTAPAPELES" con "PRIMARIO" para imprimir PRIMARY
contenido de la selección.
Referencias
- Resumen de X Selecciones por Jamie Zawinski
- Manual de programación Xlib - Selecciones
- ICCCM:grandes transferencias de datos y protocolo INCR
- https://github.com/exebook/x11clipboard - mínimo
XCopy()
yXPaste()
implementaciones xsel
yxclip
fuentes- La selección secundaria:historia e ideas de Charles Lindsey
¿Intentó encontrar no un código primero sino un programa con una implementación? Lo hice por ti y encontré muchas implementaciones que usan llamadas directas X11. Creo que lo más valioso es esto, pero también puedes leer esto. Simplemente encuentre cualquier programa y busque las fuentes. Intente buscar en wikipedia qué aplicaciones utilizan el portapapeles/sistema de selección x11.
Los siguientes programas operan específicamente en mecanismos de transferencia de datos:
xcutsel
transfiere datos de selecciones a búferes de corte o viceversa
xclipboard
, glipper
(Gnomo), parcellite
(LXDE) y klipper
(KDE) son administradores de portapapeles, tal vez wmcliphist
también xcb
muestra el contenido de los buffers cortados y permite al usuario manipularlos xselección,
xclip
, xsel
y xcopy
son programas de línea de comandos que copian datos hacia o desde la selección X. xcopy tiene una opción de verbosidad que ayuda a depurar problemas de Xselection. parcelalite también tiene la capacidad de leer y escribir en selecciones X específicas desde la línea de comandos.
synergy
es una herramienta multiplataforma que le permite compartir un portapapeles entre varias computadoras que ejecutan varios sistemas operativos
xfce4-clipman-plugin
es un "complemento de historial del portapapeles para el panel Xfce4" y también un administrador de portapapeles xtranslate busca palabras en la selección X en un diccionario multilingüe autocutsel sincroniza el búfer de corte y el búfer de selección
En breve, en teoría, X11 tiene 2 "portapapeles":en realidad un teclado y para las selecciones:el texto que seleccionó de inmediato se puede pegar en cualquier lugar que desee presionando el botón central del mouse, mientras que el "teclado" real se crea para propósitos de portapapeles principal/predeterminado como intercambio por diferentes tipos de objetos.
PD No trabajaría más con x11 después de mi experiencia. Disfruta :)