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 }