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