URI: 
       tclipboard_xlib.c - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tclipboard_xlib.c (7570B)
       ---
            1 /* Copied almost exactly from ledit. */
            2 
            3 #include <time.h>
            4 #include <stdio.h>
            5 #include <stdlib.h>
            6 #include <string.h>
            7 
            8 #include <X11/Xlib.h>
            9 #include <X11/Xatom.h>
           10 
           11 #include "util.h"
           12 #include "memory.h"
           13 #include "graphics.h"
           14 #include "clipboard.h"
           15 #include "clipboard_xlib.h"
           16 #include "xlib_shared.h"
           17 #include "macros.h"
           18 #include "config.h"
           19 #include "ctrlsel.h"
           20 
           21 /* Some *inspiration* taken from SDL (https://libsdl.org), mainly
           22    the idea to create a separate window just for clipboard handling. */
           23 
           24 static Window get_clipboard_window(ltk_clipboard *clip);
           25 static Bool check_window(Display *dpy, XEvent *event, XPointer arg);
           26 static txtbuf *get_text(ltk_clipboard *clip, int primary);
           27 
           28 struct ltk_clipboard {
           29         txtbuf *primary;
           30         txtbuf *clipboard;
           31         txtbuf *rbuf;
           32         ltk_renderdata *renderdata;
           33         Window window;
           34         struct CtrlSelTarget starget;
           35         struct CtrlSelTarget rtarget;
           36         CtrlSelContext *scontext;
           37         Atom xtarget;
           38 };
           39 
           40 ltk_clipboard *
           41 ltk_clipboard_create(ltk_renderdata *renderdata) {
           42         ltk_clipboard *clip = ltk_malloc(sizeof(ltk_clipboard));
           43         clip->primary = txtbuf_new();
           44         clip->clipboard = txtbuf_new();
           45         clip->rbuf = txtbuf_new();
           46         clip->renderdata = renderdata;
           47         clip->window = None;
           48         clip->xtarget = None;
           49         #ifdef X_HAVE_UTF8_STRING
           50         clip->xtarget = XInternAtom(renderdata->dpy, "UTF8_STRING", False);
           51         #else
           52         clip->xtarget = XA_STRING;
           53         #endif
           54         clip->scontext = NULL;
           55         return clip;
           56 }
           57 
           58 void
           59 ltk_clipboard_destroy(ltk_clipboard *clip) {
           60         txtbuf_destroy(clip->primary);
           61         txtbuf_destroy(clip->clipboard);
           62         txtbuf_destroy(clip->rbuf);
           63         if (clip->scontext)
           64                 ctrlsel_disown(clip->scontext);
           65         if (clip->window != None)
           66                 XDestroyWindow(clip->renderdata->dpy, clip->window);
           67         free(clip);
           68 }
           69 
           70 static Window
           71 get_clipboard_window(ltk_clipboard *clip) {
           72         if (clip->window == None) {
           73                 clip->window = XCreateWindow(
           74                     clip->renderdata->dpy, DefaultRootWindow(clip->renderdata->dpy),
           75                     -10, -10, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, 0, NULL
           76                 );
           77                 XFlush(clip->renderdata->dpy);
           78         }
           79         return clip->window;
           80 }
           81 
           82 void
           83 ltk_clipboard_set_primary_text(ltk_clipboard *clip, char *text) {
           84         txtbuf_set_text(clip->primary, text);
           85         ltk_clipboard_set_primary_selection_owner(clip);
           86 }
           87 
           88 txtbuf *
           89 ltk_clipboard_get_primary_buffer(ltk_clipboard *clip) {
           90         return clip->primary;
           91 }
           92 
           93 void
           94 ltk_clipboard_set_primary_selection_owner(ltk_clipboard *clip) {
           95         Window window = get_clipboard_window(clip);
           96         if (clip->scontext)
           97                 ctrlsel_disown(clip->scontext);
           98         clip->scontext = NULL;
           99         /* FIXME: is it fine to cast to unsigned char everywhere? */
          100         ctrlsel_filltarget(clip->xtarget, clip->xtarget, 8, (unsigned char *)clip->primary->text, clip->primary->len, &clip->starget);
          101         /* FIXME: use proper time */
          102         clip->scontext = ctrlsel_setowner(clip->renderdata->dpy, window, XA_PRIMARY, CurrentTime, 0, &clip->starget, 1);
          103         if (!clip->scontext)
          104                 fprintf(stderr, "WARNING: Could not own primary selection.\n");
          105 }
          106 
          107 void
          108 ltk_clipboard_set_clipboard_text(ltk_clipboard *clip, char *text) {
          109         txtbuf_set_text(clip->clipboard, text);
          110         ltk_clipboard_set_clipboard_selection_owner(clip);
          111 }
          112 
          113 txtbuf *
          114 ltk_clipboard_get_clipboard_buffer(ltk_clipboard *clip) {
          115         return clip->clipboard;
          116 }
          117 
          118 void
          119 ltk_clipboard_set_clipboard_selection_owner(ltk_clipboard *clip) {
          120         Atom clip_atom;
          121         Window window = get_clipboard_window(clip);
          122         clip_atom = XInternAtom(clip->renderdata->dpy, "CLIPBOARD", False);
          123         if (clip->scontext)
          124                 ctrlsel_disown(clip->scontext);
          125         clip->scontext = NULL;
          126         /* FIXME: see clipboard_set_primary_selection_owner */
          127         ctrlsel_filltarget(clip->xtarget, clip->xtarget, 8, (unsigned char *)clip->clipboard->text, clip->clipboard->len, &clip->starget);
          128         /* FIXME: use proper time */
          129         clip->scontext = ctrlsel_setowner(clip->renderdata->dpy, window, clip_atom, CurrentTime, 0, &clip->starget, 1);
          130         if (!clip->scontext)
          131                 fprintf(stderr, "WARNING: Could not own clipboard selection.\n");
          132 }
          133 
          134 void
          135 ltk_clipboard_primary_to_clipboard(ltk_clipboard *clip) {
          136         if (clip->primary->len > 0) {
          137                 txtbuf_copy(clip->clipboard, clip->primary);
          138                 ltk_clipboard_set_clipboard_selection_owner(clip);
          139         }
          140 }
          141 
          142 int
          143 ltk_clipboard_filter_event(ltk_clipboard *clip, XEvent *e) {
          144         if (clip->window != None && e->xany.window == clip->window) {
          145                 if (clip->scontext)
          146                         ctrlsel_send(clip->scontext, e);
          147                 /* other events are discarded since there
          148                    was no request to get the clipboard text */
          149                 return 1;
          150         }
          151         return 0;
          152 }
          153 
          154 static Bool
          155 check_window(Display *dpy, XEvent *event, XPointer arg) {
          156         (void)dpy;
          157         return *(Window *)arg == event->xany.window;
          158 }
          159 
          160 /* WARNING: The returned txtbuf needs to be copied before further processing! */
          161 static txtbuf *
          162 get_text(ltk_clipboard *clip, int primary) {
          163         CtrlSelContext *context;
          164         Window window = get_clipboard_window(clip);
          165         ctrlsel_filltarget(clip->xtarget, clip->xtarget, 0, NULL, 0, &clip->rtarget);
          166         Atom clip_atom = primary ? XA_PRIMARY : XInternAtom(clip->renderdata->dpy, "CLIPBOARD", False);
          167         /* FIXME: use proper time here */
          168         context = ctrlsel_request(clip->renderdata->dpy, window, clip_atom, CurrentTime, &clip->rtarget, 1);
          169         /* FIXME: show error in window? */
          170         if (!context) {
          171                 fprintf(stderr, "WARNING: Unable to request selection.\n");
          172                 return NULL;
          173         }
          174 
          175         struct timespec now, elapsed, last, start, sleep_time;
          176         sleep_time.tv_sec = 0;
          177         clock_gettime(CLOCK_MONOTONIC, &start);
          178         last = start;
          179         XEvent event;
          180         while (1) {
          181                 /* FIXME: I have no idea how inefficient this is */
          182                 if (XCheckIfEvent(clip->renderdata->dpy, &event, &check_window, (XPointer)&window)) {
          183                         switch (ctrlsel_receive(context, &event)) {
          184                         case CTRLSEL_RECEIVED:
          185                                 goto done;
          186                         case CTRLSEL_ERROR:
          187                                 fprintf(stderr, "WARNING: Could not get selection.\n");
          188                                 ctrlsel_cancel(context);
          189                                 return NULL;
          190                         default:
          191                                 continue;
          192                         }
          193                 }
          194                 clock_gettime(CLOCK_MONOTONIC, &now);
          195                 ltk_timespecsub(&now, &start, &elapsed);
          196                 /* Timeout if it takes too long. When that happens, become the selection owner to
          197                    avoid further timeouts in the future (I think I copied this behavior from SDL). */
          198                 /* FIXME: configure timeout */
          199                 if (elapsed.tv_sec > 0) {
          200                         if (primary)
          201                                 ltk_clipboard_set_primary_text(clip, "");
          202                         else
          203                                 ltk_clipboard_set_clipboard_text(clip, "");
          204                         return NULL;
          205                 }
          206                 ltk_timespecsub(&now, &last, &elapsed);
          207                 /* FIXME: configure nanoseconds */
          208                 if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000) {
          209                         sleep_time.tv_nsec = 20000000 - elapsed.tv_nsec;
          210                         nanosleep(&sleep_time, NULL);
          211                 }
          212                 last = now;
          213         }
          214         return NULL;
          215 done:
          216         /* FIXME: this is a bit ugly because it fiddles around with txtbuf internals */
          217         free(clip->rbuf->text);
          218         clip->rbuf->cap = clip->rbuf->len = clip->rtarget.bufsize;
          219         /* FIXME: again weird conversion between char and unsigned char */
          220         clip->rbuf->text = (char *)clip->rtarget.buffer;
          221         clip->rtarget.buffer = NULL; /* important so ctrlsel_cancel doesn't free it */
          222         ctrlsel_cancel(context);
          223         return clip->rbuf;
          224 }
          225 
          226 txtbuf *
          227 ltk_clipboard_get_clipboard_text(ltk_clipboard *clip) {
          228         Atom clip_atom;
          229         clip_atom = XInternAtom(clip->renderdata->dpy, "CLIPBOARD", False);
          230         Window window = get_clipboard_window(clip);
          231         Window owner = XGetSelectionOwner(clip->renderdata->dpy, clip_atom);
          232         if (owner == None) {
          233                 return NULL;
          234         } else if (owner == window) {
          235                 return clip->clipboard;
          236         } else {
          237                 return get_text(clip, 0);
          238         }
          239 }
          240 
          241 txtbuf *
          242 ltk_clipboard_get_primary_text(ltk_clipboard *clip) {
          243         Window window = get_clipboard_window(clip);
          244         Window owner = XGetSelectionOwner(clip->renderdata->dpy, XA_PRIMARY);
          245         if (owner == None) {
          246                 return NULL;
          247         } else if (owner == window) {
          248                 return clip->primary;
          249         } else {
          250                 return get_text(clip, 1);
          251         }
          252 }