Get Adobe Flash playerPlugin by wpburn.com wordpress themes

      Comment mettre des données dans le presse-papiers à partir d’un programme en console sous GNU/Linux ? Voici le problème que j’ai rencontré il y a peu de temps alors que je développais une application sans interface graphique en C. M’attendant à une solution triviale étant donné la simplicité du besoin, j’ai été surpris de ne pas trouver de réponse sur Google, malgré des recherches plutôt poussées.

      En effet, sous Windows par exemple, le snippet suivant suffit à placer une chaîne de caractères dans le presse-papiers :

  1.  
  2. char str[] = "chaine à copier";
  3.  
  4. HGLOBAL hText = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, sizeof(str));
  5. char *pText = (char *)GlobalLock(hText);
  6. strcpy(pText, str);
  7. GlobalUnlock(hText);
  8.  
  9. OpenClipboard(NULL);
  10. EmptyClipboard();
  11. SetClipboardData(CF_TEXT, hText);
  12. CloseClipboard();
  13.  
  14. GlobalFree(hText);
  15.  

Sous GNU/Linux, c’est assez différent et malheureusement plus complexe à mettre en oeuvre. Tout d’abord, il est nécessaire de connaître les points suivants :

  • En plus du presse-papiers, est accessible sous GNU/Linux un tampon de sélection primaire (accessible avec le bouton du milieu de la souris), ainsi qu’un tampon de sélection secondaire (généralement non-modifiable directement par l’utilisateur).
  • C’est le serveur X qui gère les mécanismes liés à ces tampons.
  • Le serveur X n’alloue pas un tampon mémoire général accessible à toutes les applications graphiques, mais le contenu des sélections et du presse-papiers sont communiqués entre clients (communication peer-to-peer).


      Toutefois, si notre application est en mode console, comment peut-on communiquer avec les autres applications X? Il faut donc premièrement se connecter au serveur X et créer une fenêtre non-mappée (invisible) :

  1.  
  2. Display *display;
  3. Window window, root;
  4. int black;
  5. char *display_name = NULL;
  6.  
  7. if(!(display = XOpenDisplay(display_name)))
  8.   fprintf(stderr, "Can’t open display: %s\n", display_name);
  9.  
  10. root = XDefaultRootWindow(display);
  11.  
  12. black = BlackPixel(display, DefaultScreen(display));
  13. window = XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, black, black);
  14.  

      A présent que notre application console est connectée au serveur X, il est nécessaire de :

  • Se notifier comme propriétaire du tampon désiré (sélection primaire, sélection secondaire, ou presse-papiers). Dans notre cas, nous nous intéresserons au tampon de sélection primaire, mais un changement d’une ligne de code suffit à manipuler le presse-papiers).
  • Recevoir et gérer les évènements X déclenchés par les autres clients X ; dans notre cas uniquement les évènements SelectionClear ainsi que SelectionRequest, ayant pour fonction, respectivement, de notifier que le propriétaire de la sélection n’est plus notre application, et de demander le conteu de la sélection.

      Voici la fonction permettant de devenir propriétaire du tampon de sélection primaire (note : le type Bool est déclaré dans X11/Xlib.h, autant l’utiliser) :

  1.  
  2. Bool own_selection(Atom selection)
  3. {
  4.   Bool ret = True;
  5.   Window owner;
  6.  
  7.   XSetSelectionOwner(display, selection, window, timestamp);
  8.   owner = XGetSelectionOwner(display, selection);
  9.  
  10.   if(owner != window)
  11.     ret = False;
  12.  
  13.   return ret;
  14. }
  15.  

      La variable selection, de type Atom (un Atom désigne un type d’élément, physique ou non), nous permet de sélectionner quel tampon nous voulons manipuler :

  1.  
  2. Atom selection;
  3.  
  4. /* Tampon de sélection primaire */
  5. selection = XA_PRIMARY;
  6. /* Tampon de sélection secondaire */
  7. selection = XA_SECONDARY;
  8. /* Presse-papiers */
  9. selection = XInternAtom(display, "CLIPBOARD", False);
  10.  

      Après avoir acquis la sélection, il nous faut répondre aux évènements provenant des actions des autres clients X. Ceci peut se faire un bout de code similaire à celui-ci :

  1.  
  2. /*
  3.  *  La boucle principale de notre programme devient celle de cette fonction
  4.  *  set_selection, qui attend un évènement X.
  5.  */
  6. void set_selection(Atom selection, unsigned char *sel)
  7. {
  8.   XEvent event;
  9.  
  10.   if(!own_selection(selection))
  11.     return;
  12.    
  13.   while(1) {
  14.     XNextEvent(display, &event);
  15.  
  16.     switch(event.type) {
  17.       case SelectionClear:
  18.         /*
  19.          * Dans le cas d’un SelectionClear, si le tampon devant être vidé est celui
  20.          * dont nous sommes propriétaires, il n’y a plus rien à gérer, on peut quitter.
  21.           */
  22.         if(event.xselectionclear.selection == selection)
  23.           return;
  24.       break;
  25.      
  26.       case SelectionRequest:
  27.         /*
  28.          * Dans le cas d’un SelectionRequest, si le tampon désiré est celui
  29.          * que l’on gère, alors on effectue le traitement avec la fonction
  30.          * handle_selection_request.
  31.          */
  32.         if(event.xselectionrequest.selection != selection)
  33.           break;
  34.          
  35.         handle_selection_request(event, sel);
  36.       break;
  37.      
  38.       default:
  39.       break;
  40.     }
  41.   }
  42. }
  43.  
  44. /*
  45.  * Traitement de la demande du contenu du tampon.
  46.  */
  47. void handle_selection_request(XEvent event, unsigned char * sel)
  48. {
  49.   XSelectionRequestEvent * xsr = &event.xselectionrequest;
  50.   XSelectionEvent ev;
  51.  
  52.   ev.type = SelectionNotify;
  53.   ev.display = xsr->display;
  54.   ev.requestor = xsr->requestor;
  55.   ev.selection = xsr->selection;
  56.   ev.time = xsr->time;
  57.   ev.target = xsr->target;
  58.  
  59.   if(xsr->property == None)
  60.     xsr->property = xsr->target;
  61.  
  62.   /*
  63.    * Si un client X demande une sélection dont le timestamp est inférieur à celui
  64.    * de notre sélection, alors on ignore la requête.
  65.    */
  66.   if(ev.time != CurrentTime && ev.time < timestamp) {
  67.     ev.property = None;
  68.   }
  69.  
  70.   /*
  71.    * Si la cible de l’évènement SelectionRequest est TIMESTAMP, alors le client X
  72.    * nous demande le timestamp de notre sélection.
  73.    */
  74.   else if(ev.target == targets[IDX_TIMESTAMP]) {
  75.     ev.property = xsr->property;
  76.     XChangeProperty(ev.display, ev.requestor, ev.property, XA_INTEGER, 32,
  77.              PropModeReplace, (unsigned char *)&timestamp, 1);
  78.   }
  79.  
  80.   /*
  81.    * Si la cible de l’évènement SelectionRequest est TARGETS, alors le client X
  82.    * nous demande la liste des Atom supportés par notre sélection. Dans notre cas
  83.    * c’est-à-dire une chaine de caractères, on renvoie notamment TEXT et UTF8_STRING
  84.    */
  85.   else if(ev.target == targets[IDX_TARGETS]) {
  86.     Atom *targets_cpy;
  87.    
  88.     ev.property = xsr->property;
  89.     targets_cpy = malloc(sizeof(targets));
  90.     memcpy(targets_cpy, targets, sizeof(targets));
  91.    
  92.     XChangeProperty(ev.display, ev.requestor, ev.property, XA_ATOM, 32,
  93.              PropModeReplace, (unsigned char *)targets_cpy,
  94.              NUM_TARGETS);
  95.   }
  96.  
  97.   /*
  98.    * Si la cible de l’évènement SelectionRequest est XA_STRING ou TEXT, alors le client X
  99.    * nous demande le contenu de notre sélection (pour un champ de texte).
  100.    */
  101.   else if(ev.target == XA_STRING || ev.target == targets[IDX_TEXT]) {
  102.     ev.property = xsr->property;
  103.     XChangeProperty(ev.display, ev.requestor, ev.property, XA_STRING, 8,
  104.              PropModeReplace, sel, strlen((char *)sel));
  105.   }
  106.  
  107.   /*
  108.    * Si la cible de l’évènement SelectionRequest est UTF8_STRING, alors le client X
  109.    * nous demande le contenu de notre sélection (pour un champ de texte).
  110.    */
  111.   else if(ev.target == targets[IDX_UTF8_STRING]) {
  112.     ev.property = xsr->property;
  113.     XChangeProperty(ev.display, ev.requestor, ev.property, targets[IDX_UTF8_STRING], 8,
  114.             PropModeReplace, sel, strlen((char *)sel));
  115.   }
  116.   else
  117.     ev.property = None;
  118.  
  119.   XSendEvent(display, ev.requestor, False, (unsigned long)NULL, (XEvent *)&ev);
  120. }
  121.  

      Etant donné que le coeur de l’application devient la boucle située dans la fonction set_selection, on peut soit opté pour un fork(), soit pour le lancement d’un thread. L’avantage d’un fork() est qu’il peut devenir un démon, et ainsi, lors de la fermeture de l’application, continuer son exécution tant que la sélection ne change pas. Toutefois, ce comportement peut s’avérer gênant, et en général la sélection est tout simplement perdue lors la fermeture de l’application.

      J’espère que ce post vous aura été utile, en tout cas il l’a été pour moi, bien qu’il m’ai demandé pas mal de lecture de code pour “si peu”. Voici un programme d’exemple (une sorte de xsel light, remanié par moi-même) :

  1.  
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <X11/Xlib.h>
  6. #include <X11/Xatom.h>
  7.  
  8. #define IDX_TIMESTAMP 0
  9. #define IDX_TARGETS 1
  10. #define IDX_NULL 2
  11. #define IDX_TEXT 3
  12. #define IDX_UTF8_STRING 4
  13.  
  14. #define NUM_TARGETS 5
  15.  
  16. Display *display;
  17. Window window;
  18. Time timestamp;
  19. Atom targets[NUM_TARGETS];
  20.  
  21. Time get_timestamp()
  22. {
  23.   XEvent event;
  24.   XChangeProperty(display, window, XA_WM_NAME, XA_STRING, 8, PropModeAppend, NULL, 0);
  25.  
  26.   while(1) {
  27.     XNextEvent(display, &event);
  28.  
  29.     if(event.type == PropertyNotify)
  30.       return event.xproperty.time;
  31.   }
  32. }
  33.  
  34. Bool own_selection(Atom selection)
  35. {
  36.   Bool ret = True;
  37.   Window owner;
  38.  
  39.   XSetSelectionOwner(display, selection, window, timestamp);
  40.   owner = XGetSelectionOwner(display, selection);
  41.  
  42.   if(owner != window)
  43.     ret = False;
  44.  
  45.   return ret;
  46. }
  47.  
  48. void handle_selection_request(XEvent event, unsigned char * sel)
  49. {
  50.   XSelectionRequestEvent * xsr = &event.xselectionrequest;
  51.   XSelectionEvent ev;
  52.  
  53.   ev.type = SelectionNotify;
  54.   ev.display = xsr->display;
  55.   ev.requestor = xsr->requestor;
  56.   ev.selection = xsr->selection;
  57.   ev.time = xsr->time;
  58.   ev.target = xsr->target;
  59.  
  60.   if(xsr->property == None)
  61.     xsr->property = xsr->target;
  62.  
  63.   if(ev.time != CurrentTime && ev.time < timestamp) {
  64.     ev.property = None;
  65.   }
  66.   else if(ev.target == targets[IDX_TIMESTAMP]) {
  67.     ev.property = xsr->property;
  68.     XChangeProperty(ev.display, ev.requestor, ev.property, XA_INTEGER, 32,
  69.              PropModeReplace, (unsigned char *)&timestamp, 1);
  70.   }
  71.   else if(ev.target == targets[IDX_TARGETS]) {
  72.     Atom *targets_cpy;
  73.    
  74.     ev.property = xsr->property;
  75.     targets_cpy = malloc(sizeof(targets));
  76.     memcpy(targets_cpy, targets, sizeof(targets));
  77.    
  78.     XChangeProperty(ev.display, ev.requestor, ev.property, XA_ATOM, 32,
  79.              PropModeReplace, (unsigned char *)targets_cpy,
  80.              NUM_TARGETS);
  81.   }
  82.   else if(ev.target == XA_STRING || ev.target == targets[IDX_TEXT]) {
  83.     ev.property = xsr->property;
  84.     XChangeProperty(ev.display, ev.requestor, ev.property, XA_STRING, 8,
  85.              PropModeReplace, sel, strlen((char *)sel));
  86.   }
  87.   else if(ev.target == targets[IDX_UTF8_STRING]) {
  88.     ev.property = xsr->property;
  89.     XChangeProperty(ev.display, ev.requestor, ev.property, targets[IDX_UTF8_STRING], 8,
  90.             PropModeReplace, sel, strlen((char *)sel));
  91.   }
  92.   else
  93.     ev.property = None;
  94.  
  95.   XSendEvent(display, ev.requestor, False, (unsigned long)NULL, (XEvent *)&ev);
  96. }
  97.  
  98. void set_selection(Atom selection, unsigned char * sel)
  99. {
  100.   XEvent event;
  101.  
  102.   if(!own_selection(selection))
  103.     return;
  104.    
  105.   while(1) {
  106.     XNextEvent(display, &event);
  107.  
  108.     switch(event.type) {
  109.       case SelectionClear:
  110.         if(event.xselectionclear.selection == selection)
  111.           return;
  112.       break;
  113.      
  114.       case SelectionRequest:
  115.         if(event.xselectionrequest.selection != selection)
  116.           break;
  117.          
  118.         handle_selection_request(event, sel);
  119.       break;
  120.      
  121.       default:
  122.       break;
  123.     }
  124.   }
  125. }
  126.  
  127. int main(int argc, char **argv)
  128. {
  129.   Window root;
  130.   Atom selection = XA_PRIMARY;
  131.   int black;
  132.   unsigned char sel[] = "exemple de chaine";
  133.   char *display_name = NULL;
  134.  
  135.   if(!(display = XOpenDisplay(display_name)))
  136.     fprintf(stderr, "Can’t open display: %s\n", display_name);
  137.  
  138.   root = XDefaultRootWindow(display);
  139.  
  140.   black = BlackPixel(display, DefaultScreen(display));
  141.   window = XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, black, black);
  142.  
  143.   XSelectInput(display, window, PropertyChangeMask);
  144.   timestamp = get_timestamp();
  145.  
  146.   targets[IDX_TIMESTAMP] = XInternAtom(display, "TIMESTAMP", False);
  147.   targets[IDX_TARGETS] = XInternAtom(display, "TARGETS", False);
  148.   targets[IDX_NULL] = XInternAtom(display, "NULL", False);
  149.   targets[IDX_TEXT] = XInternAtom(display, "TEXT", False);
  150.   targets[IDX_UTF8_STRING] = XInternAtom(display, "UTF8_STRING", False);
  151.  
  152.   if(targets[IDX_UTF8_STRING] == None)
  153.     targets[IDX_UTF8_STRING] = XA_STRING;
  154.  
  155.   set_selection(selection, sel);
  156.  
  157.   return 0;
  158. }
  159.  

Deimos

3 Responses to “De l’usage du presse-papiers et des sélections sous GNU/Linux”
  1. Kowaio says:

    Salut Deimos,

    j’ai lu votre article, et j’ai été heureux de voir qu’il y avait encore des gens qui n’hésitaient pas à partager leurs connaissances et trouvailles.

    Je suis en train d’essayer de mettre en place le copier/coller d’images pour une application linux et je rencontre quelques problèmes.
    je voulais donc savoir si vous aviez tenté la chose, ou si vous étiez rester exclusivement sur le texte.
    En effet, je tente de récupèrer un Atom qui devrait être un Pixmap, pour une image. Je dis tenter, car à vrai dire, je ne sais pas réellement ce qu’il y a dans le clipboard quand je fais copier une image sous firefox.
    Je voulais donc savoir ce que les données renvoyées par le XgetWindowProperty() contenaient. Idem, quel format d’image est donc mis dans ce clipboard ? Du BMP, PNG, JPG, format de l’image selectionnée ?

    Pas beaucoup d’infos ne circulent sur le net sur les bas fond de cette programmation X11. De plus, débutant surLinux, il m’est parfois assez difficile et pénible de trouver de bonnes docs et tutos.

    Ainsi, si vous pouviez m’éclairer sur la chose, ou si vous connaissez quelques exemples de copier/coller d’image du clipboard, je suis preneur.

    Merci à vous et encore bravo pour votre article et votre aide.

  2. Deimos says:

    Salut, et merci pour ton commentaire ;)

    En ce qui concerne la copie d’image, je ne sais plus comment ça se déroule étant donné que je me suis principalement intéressé à la copie de texte. Toutefois, si le clipboard ou un autre buffer de sélection contient une image, il me paraît “normal” (mais c’est bien sûr à vérifier =D) que celle-ci soit en bitmap et non dans un format compressé comme JPG ou PNG.

    Je peux tout de même t’aider en te donnant la Xlib Programmers Reference. Tu y trouveras dans “Supplementary Documents” le document “Inter-client Communication Conventions Manual”. La partie que tu devras lire est “2. Peer-to-Peer Communication by Means of Selections”. C’est celle que j’ai lu pour pouvoir coder ce snippet. Il y a tout ce qu’il faut savoir sur les buffers de sélection à priori, étant donné que c’est la doc officielle.

    En espérant t’avoir aidé =)

    Bonne chance pour ton code,

    Cya

  3. piR says:

    houla la faute
    acquérit -> acquis

  4.  
Trackbacks
  1.  
Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>