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 }