GNU/Linux >> Tutoriales Linux >  >> Linux

C++ Obtener una cadena del Portapapeles en Linux

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() y XPaste() implementaciones
  • xsel y xclip 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 :)


Linux
  1. C++ obtener nombre de distribución de Linux\versión

  2. ¿Cómo encontrar la ruta completa del programa C++ Linux desde adentro?

  3. C++ Linux:obtenga la frecuencia de actualización de un monitor

  4. Shell de Linux obtiene el valor de un campo de un archivo yml

  5. Obtenga resolución de pantalla desde la línea de comandos para Linux Desktop

Mostrar citas aleatorias desde la línea de comandos en Linux

Wikit:obtenga resúmenes de Wikipedia desde la línea de comandos en Linux

Cómo obtener noticias al instante desde la línea de comandos en Linux

Cómo obtener el nombre de archivo de la ruta completa en Linux

¿Cómo obtener el nombre de usuario en C/C++ en Linux?

¿Cómo obtener el nombre de host de IP (Linux)?