selection.c (5480B)
1 /* Copyright ©2010 Kris Maglione <maglione.k at Gmail> 2 * See LICENSE file for license details. 3 */ 4 #include "dat.h" 5 #include "fns.h" 6 7 static Handlers selection_handlers; 8 static Handlers steal_handlers; 9 10 static Selection* 11 _selection_create(char *selection, ulong time, 12 void (*request)(Selection*, XSelectionRequestEvent*), 13 void (*cleanup)(Selection*), 14 bool lazy) { 15 Selection *s; 16 17 if(time == 0) 18 time = event_xtime; 19 20 s = emallocz(sizeof *s); 21 s->owner = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, 22 InputOnly, nil, 0); 23 s->owner->aux = s; 24 s->request = request; 25 s->cleanup = cleanup; 26 s->time_start = time; 27 28 sethandler(s->owner, &selection_handlers); 29 30 if (!lazy) { 31 XSetSelectionOwner(display, xatom(selection), s->owner->xid, time); 32 33 /* 34 * There is a race here that ICCCM doesn't mention. It's 35 * possible that we've gained and lost the selection in this 36 * time, and a client's sent us a selection request. We're 37 * required to reply to it, but since we're destroying the 38 * window, we'll never hear about it. Since ICCCM doesn't 39 * mention it, we assume that other clients behave likewise, 40 * and therefore clients must be prepared to deal with such 41 * behavior regardless. 42 */ 43 if(XGetSelectionOwner(display, xatom(selection)) != s->owner->xid) { 44 destroywindow(s->owner); 45 free(s); 46 return nil; 47 } 48 } 49 50 s->selection = estrdup(selection); 51 return s; 52 } 53 54 Selection* 55 selection_create(char *selection, ulong time, 56 void (*request)(Selection*, XSelectionRequestEvent*), 57 void (*cleanup)(Selection*)) { 58 return _selection_create(selection, time, request, cleanup, false); 59 } 60 61 static void 62 _selection_manage(Selection *s) { 63 64 if (s->oldowner) { 65 Dprint("[selection] Grabbing.\n"); 66 XSetSelectionOwner(display, xatom(s->selection), s->owner->xid, s->time_start); 67 if(XGetSelectionOwner(display, xatom(s->selection)) != s->owner->xid) { 68 selection_release(s); 69 return; 70 } 71 } 72 73 Dprint("[selection] Notifying.\n"); 74 clientmessage(&scr.root, "MANAGER", SubstructureNotifyMask|StructureNotifyMask, 32, 75 (ClientMessageData){ .l = {s->time_start, xatom(s->selection), s->owner->xid} }); 76 } 77 78 static void 79 timeout(long timer, void *v) { 80 Selection *s; 81 82 s = v; 83 Dprint("[selection] Done waiting. Killing 0x%ulx.\n", s->oldowner); 84 s->timer = 0; 85 XKillClient(display, s->oldowner); 86 sync(); 87 } 88 89 Selection* 90 selection_manage(char *selection, ulong time, 91 void (*message)(Selection*, XClientMessageEvent*), 92 void (*cleanup)(Selection*), 93 bool steal) { 94 Selection *s; 95 Window *w; 96 XWindow old; 97 98 if((old = XGetSelectionOwner(display, xatom(selection)))) { 99 if (!steal) 100 return nil; 101 102 w = emallocz(sizeof *w); 103 w->type = WWindow; 104 w->xid = old; 105 selectinput(w, StructureNotifyMask); 106 107 /* Hack for broken Qt systray implementation. If it 108 * finds a new system tray running when the old one 109 * dies, it never selects the StructureNotify mask 110 * on it, and therefore never disassociates from it, 111 * and completely ignores any future MANAGER 112 * messages it receives. 113 */ 114 XSetSelectionOwner(display, xatom(selection), 0, time); 115 } 116 117 s = _selection_create(selection, time, nil, cleanup, old); 118 if(s) { 119 s->message = message; 120 s->oldowner = old; 121 if(!old) 122 _selection_manage(s); 123 else { 124 Dprint("[selection] Waiting for old owner %W to die...\n", w); 125 pushhandler(w, &steal_handlers, s); 126 s->timer = ixp_settimer(&srv, 2000, timeout, s); 127 } 128 } 129 130 return s; 131 } 132 133 void 134 selection_release(Selection *s) { 135 if(s->cleanup) 136 s->cleanup(s); 137 if(!s->time_end) 138 XSetSelectionOwner(display, xatom(s->selection), None, s->time_start); 139 destroywindow(s->owner); 140 free(s->selection); 141 free(s); 142 } 143 144 static void 145 selection_notify(Selection *s, XSelectionRequestEvent *ev, bool success) { 146 XSelectionEvent notify; 147 148 notify.type = SelectionNotify; 149 notify.requestor = ev->requestor; 150 notify.selection = ev->selection; 151 notify.target = ev->target; 152 notify.property = success ? ev->property : None; 153 notify.time = ev->time; 154 155 sendevent(window(ev->requestor), false, 0L, ¬ify); 156 } 157 158 static bool 159 message_event(Window *w, void *aux, XClientMessageEvent *ev) { 160 Selection *s; 161 162 s = aux; 163 if(s->message) 164 s->message(s, ev); 165 return false; 166 } 167 168 static bool 169 selectionclear_event(Window *w, void *aux, XSelectionClearEvent *ev) { 170 Selection *s; 171 172 USED(w, ev); 173 Dprint("[selection] Lost selection\n"); 174 s = aux; 175 s->time_end = ev->time; 176 selection_release(s); 177 return false; 178 } 179 180 static bool 181 selectionrequest_event(Window *w, void *aux, XSelectionRequestEvent *ev) { 182 Selection *s; 183 184 s = aux; 185 if(ev->property == None) 186 ev->property = ev->target; /* Per ICCCM §2.2. */ 187 188 Dprint("[selection] Request: %A\n", ev->target); 189 if(ev->target == xatom("TIMESTAMP")) { 190 /* Per ICCCM §2.6.2. */ 191 changeprop_ulong(window(ev->requestor), 192 atomname(ev->property), "TIMESTAMP", 193 &s->time_start, 1); 194 selection_notify(s, ev, true); 195 return false; 196 } 197 198 if(s->request) 199 s->request(s, ev); 200 else 201 selection_notify(s, ev, false); 202 return false; 203 } 204 205 static Handlers selection_handlers = { 206 .message = message_event, 207 .selectionclear = selectionclear_event, 208 .selectionrequest = selectionrequest_event, 209 }; 210 211 static bool 212 destroy_event(Window *w, void *aux, XDestroyWindowEvent *e) { 213 Selection *s; 214 215 Dprint("[selection] Old owner is dead.\n"); 216 s = aux; 217 if(s->timer) 218 ixp_unsettimer(&srv, s->timer); 219 s->timer = 0; 220 221 _selection_manage(s); 222 s->oldowner = 0; 223 return false; 224 } 225 226 static Handlers steal_handlers = { 227 .destroy = destroy_event, 228 }; 229