wmii

git clone git://oldgit.suckless.org/wmii/
Log | Files | Refs | README | LICENSE

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, &notify);
    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