ledit

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