tclipboard_xlib.c - ltk - Socket-based GUI for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tclipboard_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 }