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 }