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 }