ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | 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 }