GNU/Linux >> Tutoriels Linux >  >> Linux

C++ Récupère la chaîne du Presse-papiers sous Linux

X11 utilise un protocole flexible de presse-papiers asynchrone multi-tampons et multi-formats côté application.

La plupart des boîtes à outils l'ont implémenté (gtk_clipboard_get() de GTK , QApplication::clipboard() de Qt , clipboard_get de Tk). Mais vous pouvez le faire manuellement avec l'API X11, par exemple, si vous n'utilisez pas de kits d'outils, ou si vous devez transmettre une grande quantité de données via le tampon du presse-papiers sans tout conserver en mémoire en même temps.

Théorie

Il peut y avoir de nombreux tampons, mais vous n'avez besoin d'en connaître que deux :

  • CLIPBOARD est le tampon explicite habituel :vous y copiez des choses avec le menu Édition/Copier, et vous les collez avec le menu Édition/Coller.
  • PRIMARY la sélection est une fonction de sélection implicite de la souris :le texte y pénètre lorsqu'il est sélectionné avec le curseur de la souris et est collé à partir de celui-ci lors d'un clic du milieu dans les champs de saisie de texte.

La sélection primaire ne nécessite aucune pression sur les touches, elle est donc utile pour copier de petits fragments entre des fenêtres adjacentes. Cette fonctionnalité est principalement spécifique à Unix, mais j'ai vu du mastic, du trillian et certaines applications gtk l'émuler sur le système d'exploitation Windows. Firefox dispose également de la fonctionnalité "Coller et aller" lors d'un clic central sur un espace vide non interactif de la page.

Pour optimiser les choses, celles-ci sont côté application tampons :au lieu de pousser le presse-papiers/sélection entier vers le serveur à chaque fois qu'il change, l'application indique simplement au serveur "je le possède". Pour obtenir le tampon, vous demandez au propriétaire de vous donner son contenu. De cette façon, même un grand tampon ne prend aucune ressource jusqu'à ce qu'il soit réellement demandé.

Lorsque vous demandez le tampon, vous demandez au propriétaire un format spécifique dont vous avez besoin. Par exemple, une image copiée depuis le navigateur seamonkey (cliquez avec le bouton droit sur une image et appuyez sur "Copier l'image") peut être représentée dans différents formats. Il apparaîtra comme URL d'image si vous le collez dans le terminal. Il deviendrait une image chargée à partir de cette URL si vous la collez dans libreoffice writer. Et ce serait l'image elle-même si elle était collée dans gimp. Cela fonctionne car seamonkey est intelligent et fournit à chaque application le format qu'elle demande :chaîne de texte pour le terminal, html pour libreoffice et données d'image pour gimp. Pour demander le format texte, vous devez demander UTF8_STRING format avec retour à STRING .

Comme vous demandez à une autre application de préparer le tampon, et cela peut prendre un certain temps, la requête est asynchrone :le propriétaire prépare le tampon, le sauvegarde dans un emplacement spécifié (la propriété window est utilisée comme stockage temporaire) et vous notifie avec SelectionNotify événement quand c'est fait.

Donc pour obtenir le tampon :

  • choisir le nom du tampon (CLIPBOARD , PRIMARY ), format(UTF8_STRING , STRING ) et une propriété de fenêtre pour stocker le résultat dans
  • appelez le XConvertSelection() pour demander le tampon
  • attendre SelectionNotify événement
  • lire le contenu du tampon depuis la propriété de la fenêtre

Mise en œuvre naïve

// 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;
}

Cela fonctionnera pour de nombreux cas simples. Une chose qui manque ici est la prise en charge de la lecture incrémentielle de grands tampons. Ajoutons-le !

Grands tampons

Certaines applications peuvent vouloir copier/coller 100 gigaoctets de journaux de texte. Et X11 le permet ! Mais les données doivent être transmises de manière incrémentielle, divisées en morceaux.

Si le tampon demandé est trop grand, au lieu de le stocker dans la propriété de la fenêtre, le propriétaire définit une propriété au format INCR . Si vous le supprimez, le propriétaire suppose que vous l'avez lu et place le morceau suivant dans la même propriété. Cela continue jusqu'à ce que le dernier morceau soit lu et supprimé. Enfin, le propriétaire définit la propriété de taille 0 pour marquer la fin des données.

Donc, pour lire un grand tampon, vous supprimez INCR propriété et attendez que la propriété réapparaisse (PropertyNotify événement, état ==PropertyNewValue ), lisez-le et supprimez-le, attendez qu'il réapparaisse, et ainsi de suite jusqu'à ce qu'il apparaisse avec une taille nulle.

// 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;
}

Par exemple xsel l'outil utilise INCR transfert pour les buffers supérieurs à 4000. Selon ICCCM, c'est à l'application de choisir une limite de taille raisonnable.

Le même code fonctionne pour PRIMARY sélection. Remplacez "CLIPBOARD" par "PRIMARY" pour imprimer PRIMARY contenu de la sélection.

Références

  • Résumé des sélections X par Jamie Zawinski
  • Manuel de programmation Xlib - Sélections
  • ICCCM – Transferts de données volumineuses et protocole INCR
  • https://github.com/exebook/x11clipboard - minimum XCopy() et XPaste() implémentations
  • xsel et xclip source
  • La sélection secondaire - histoire et idées par Charles Lindsey

Avez-vous d'abord essayé de trouver non pas un code mais un programme avec une implémentation ? Je l'ai fait pour vous et j'ai trouvé de nombreuses implémentations qui utilisent des appels X11 directs. Je pense que le plus précieux est celui-ci, mais vous pouvez également lire ceci. Trouvez simplement n'importe quel programme et recherchez les sources. Essayez de regarder sur wikipedia quelles applications utilisent le système de presse-papiers/sélection x11.

Les programmes suivants fonctionnent spécifiquement sur les mécanismes de transfert de données :

xcutsel transfère les données des sélections vers les tampons de coupe ou vice versa

xclipboard , glipper (Gnôme), parcellite (LXDE) et klipper (KDE) sont des gestionnaires de presse-papiers, peut-être wmcliphist aussi xcb affiche le contenu des tampons de coupe et permet à l'utilisateur de les manipuler xsélection,

xclip , xsel et xcopy sont des programmes en ligne de commande qui copient des données vers ou depuis la sélection X. xcopy a une option de verbosité qui aide à déboguer les problèmes de Xselection. parcellite a également la capacité de lire et d'écrire dans des sélections X spécifiques à partir de la ligne de commande.

synergy est un outil multiplateforme qui vous permet de partager un presse-papiers sur plusieurs ordinateurs exécutant plusieurs systèmes d'exploitation

xfce4-clipman-plugin est un "plugin d'historique du presse-papiers pour Xfce4panel" et également un gestionnaire de presse-papiers xtranslate recherche les mots dans la sélection X dans un dictionnaire multilingue autocutsel synchronise le tampon de coupe et le tampon de sélection

En bref, en théorie, X11 a 2 "presse-papiers":en fait un clavier et pour les sélections - le texte que vous avez sélectionné immédiatement peut être collé où vous voulez en appuyant sur le bouton central de la souris tandis que le "clavier" réel est fait pour le presse-papiers principal / par défaut comme échange par différents types d'objets.

PS Je ne travaillerais plus avec x11 après mon expérience. Appréciez :)


Linux
  1. Commande Linux ls

  2. C++ obtient le nom de la distribution Linux\version

  3. image Linux du presse-papiers

  4. Comment trouver le chemin complet du programme Linux C++ de l'intérieur ?

  5. C++ Linux :obtenir le taux de rafraîchissement d'un moniteur

Afficher des citations aléatoires à partir de la ligne de commande sous Linux

Comment obtenir votre géolocalisation à partir de la ligne de commande sous Linux

Wikit - Obtenez des résumés de Wikipedia à partir de la ligne de commande sous Linux

Comment obtenir des nouvelles instantanément à partir de la ligne de commande sous Linux

Comment obtenir le nom de fichier à partir du chemin complet sous Linux

Obtenir la résolution d'affichage à partir de la ligne de commande pour Linux Desktop