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