ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

clipboard_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 }