wmii

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

tray.c (10397B)


      1 /* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
      2  * See LICENSE file for license details.
      3  */
      4 #include "dat.h"
      5 #include <string.h>
      6 #include <strings.h>
      7 #include "fns.h"
      8 
      9 static Handlers handlers;
     10 static Handlers root_handlers;
     11 
     12 void
     13 restrut(Window *w, int orientation) {
     14 	enum { Left, Right, Top, Bottom };
     15 	Rectangle strut[4];
     16 	Rectangle r;
     17 
     18 	r = w->r;
     19 	memset(strut, 0, sizeof strut);
     20 	if(Dx(r) < Dx(scr.rect)/2 && orientation != OHorizontal) {
     21 		if(r.min.x <= scr.rect.min.x) {
     22 			strut[Left] = r;
     23 			strut[Left].min.x = 0;
     24 			strut[Left].max.x -= scr.rect.min.x;
     25 		}
     26 		if(r.max.x >= scr.rect.max.x) {
     27 			strut[Right] = r;
     28 			strut[Right].min.x -= scr.rect.max.x;
     29 			strut[Right].max.x = 0;
     30 		}
     31 	}
     32 	if(Dy(r) < Dy(scr.rect)/2 && orientation != OVertical) {
     33 		if(r.min.y <= scr.rect.min.y) {
     34 			strut[Top] = r;
     35 			strut[Top].min.y = 0;
     36 			strut[Top].max.y -= scr.rect.min.y;
     37 		}
     38 		if(r.max.y >= scr.rect.max.y) {
     39 			strut[Bottom] = r;
     40 			strut[Bottom].min.y -= scr.rect.max.y;
     41 			strut[Bottom].max.y = 0;
     42 		}
     43 	}
     44 
     45 #if 0
     46 #define pstrut(name) \
     47 	if(!eqrect(strut[name], ZR)) \
     48 		fprint(2, "strut["#name"] = %R\n", strut[name])
     49 	pstrut(Left);
     50 	pstrut(Right);
     51 	pstrut(Top);
     52 	pstrut(Bottom);
     53 #endif
     54 
     55 	ewmh_setstrut(w, strut);
     56 }
     57 
     58 void
     59 tray_init(void) {
     60 	WinAttr wa;
     61 	XWMHints hints = { 0, };
     62 
     63 	wa.background_pixmap = None;
     64 	wa.bit_gravity = NorthEastGravity;
     65 	wa.border_pixel = 0;
     66 	wa.event_mask = ExposureMask
     67 		      | ButtonPressMask
     68 		      | ButtonReleaseMask
     69 		      | StructureNotifyMask
     70 		      | SubstructureNotifyMask
     71 		      /* Disallow clients reconfiguring themselves. */
     72 		      | SubstructureRedirectMask;
     73 	tray.win = createwindow(&scr.root, Rect(0, 0, 1, 1), scr.depth, InputOutput,
     74 				       &wa, CWBackPixmap
     75 					  | CWBitGravity
     76 					  | CWEventMask);
     77 
     78 	sethandler(tray.win, &handlers);
     79 	pushhandler(&scr.root, &root_handlers, nil);
     80 	selectinput(&scr.root, scr.root.eventmask | PropertyChangeMask);
     81 
     82 
     83 	changeprop_string(tray.win, "_WMII_TAGS", tray.tags);
     84 
     85 	changeprop_ulong(tray.win, "XdndAware", "ATOM", (ulong[1]){ 5 }, 1);
     86 
     87 	changeprop_ulong(tray.selection->owner, Net("SYSTEM_TRAY_VISUAL"), "VISUALID",
     88 			 &scr.visual->visualid, 1);
     89 	changeprop_long(tray.win, Net("WM_WINDOW_TYPE"), "ATOM",
     90 			(long[1]){ TYPE("DOCK") }, 1);
     91 
     92 	changeprop_string(tray.win, Net("WM_NAME"), "witray");
     93 	changeprop_textlist(tray.win, "WM_NAME", "STRING",
     94 			    (char*[2]){ "witray", nil });
     95 	changeprop_textlist(tray.win, "WM_CLASS", "STRING",
     96 			    (char*[3]){ "witray", "witray", nil });
     97 	changeprop_textlist(tray.win, "WM_COMMAND", "STRING", program_args);
     98 
     99 	hints.flags = InputHint;
    100 	hints.input = false;
    101 	XSetWMHints(display, tray.win->xid, &hints);
    102 	tray_resize(tray.win->r);
    103 }
    104 
    105 static void
    106 tray_unmap(void) {
    107 	unmapwin(tray.win);
    108 	sendevent(&scr.root, false, SubstructureNotifyMask,
    109 		  &(XUnmapEvent){
    110 			.type = UnmapNotify,
    111 			.event = scr.root.xid,
    112 			.window = tray.win->xid
    113 		  });
    114 }
    115 
    116 static void
    117 tray_draw(Rectangle r) {
    118 	int borderwidth;
    119 
    120 	if(!tray.pixmap)
    121 		return;
    122 
    123 	borderwidth = 1;
    124 
    125 	r = rectsetorigin(r, ZP);
    126 	border(tray.pixmap, r, borderwidth, &tray.selcolors.border);
    127 	r = insetrect(r, borderwidth);
    128 	fill(tray.pixmap, r, &tray.selcolors.bg);
    129 	XClearWindow(display, tray.win->xid);
    130 }
    131 
    132 void
    133 tray_resize(Rectangle r) {
    134 	WinHints hints;
    135 	Image *oldimage;
    136 	WinAttr wa;
    137 
    138 	hints = ZWinHints;
    139 	hints.position = true;
    140 	hints.min = hints.max = Pt(Dx(r), Dy(r));
    141 	hints.grav = Pt(tray.edge & East ? 0 :
    142 			tray.edge & West ? 2 : 1,
    143 			tray.edge & North ? 0 :
    144 			tray.edge & South ? 2 : 1);
    145 	/* Not necessary, since we specify fixed size, but... */
    146 	// hints.base = Pt(2, 2);
    147 	// hints.inc  = Pt(tray.iconsize, tray.iconsize);
    148 	sethints(tray.win, &hints);
    149 
    150 	if(!eqrect(tray.win->r, r)) {
    151 		oldimage = tray.pixmap;
    152 
    153 		tray.pixmap = allocimage(Dx(r), Dy(r), tray.win->depth);
    154 		tray_draw(r);
    155 		wa.background_pixmap = tray.pixmap->xid;
    156 		setwinattr(tray.win, &wa, CWBackPixmap);
    157 
    158 		freeimage(oldimage);
    159 	}
    160 
    161 	tray.r = r;
    162 	tray.win->r = ZR; /* Force the configure event. */
    163 	reshapewin(tray.win, r);
    164 	restrut(tray.win, tray.orientation);
    165 }
    166 
    167 void
    168 tray_update(void) {
    169 	Rectangle r;
    170 	Point p, offset, padding;
    171 	Client *c;
    172 
    173 	r = Rect(0, 0, tray.iconsize, tray.iconsize);
    174 	padding = Pt(tray.padding, tray.padding);
    175 	offset = padding;
    176 	Dprint("tray_update()\n");
    177 	for(c=tray.clients; c; c=c->next) {
    178 		if(c->w.mapped) {
    179 			reshapewin(&c->w, rectaddpt(r, offset));
    180 			/* This seems, sadly, to be necessary. */
    181 			sendevent(&c->w, false, StructureNotifyMask, &(XEvent){
    182 				  .xconfigure = {
    183 					.type = ConfigureNotify,
    184 					.event = c->w.xid,
    185 					.window = c->w.xid,
    186 					.above = None,
    187 					.x = c->w.r.min.x,
    188 					.y = c->w.r.min.y,
    189 					.width = Dx(c->w.r),
    190 					.height = Dy(c->w.r),
    191 					.border_width = 0,
    192 				  }
    193 			  });
    194 
    195 			movewin(c->indicator, addpt(offset, Pt(2, 2)));
    196 			if(tray.orientation == OHorizontal)
    197 				offset.x += tray.iconsize + tray.padding;
    198 			else
    199 				offset.y += tray.iconsize + tray.padding;
    200 		}
    201 		if(c->w.mapped && client_hasmessage(c))
    202 			mapwin(c->indicator);
    203 		else
    204 			unmapwin(c->indicator);
    205 	}
    206 
    207 	if(eqpt(offset, padding))
    208 		tray_unmap();
    209 	else {
    210 		if(tray.orientation == OHorizontal)
    211 			offset.y += tray.iconsize + tray.padding;
    212 		else
    213 			offset.x += tray.iconsize + tray.padding;
    214 
    215 		r = Rpt(ZP, offset);
    216 		p = subpt(scr.rect.max, r.max);
    217 		if(tray.edge & East)
    218 			p.x = 0;
    219 		if(tray.edge & North)
    220 			p.y = 0;
    221 		tray_resize(rectaddpt(r, p));
    222 		mapwin(tray.win);
    223 	}
    224 
    225 	tray_draw(tray.win->r);
    226 }
    227 
    228 static bool
    229 config_event(Window *w, void *aux, XConfigureEvent *ev) {
    230 
    231 	USED(aux);
    232 	if(ev->send_event) {
    233 		/*
    234 		 * Per ICCCM §4.2.3, the window manager sends
    235 		 * synthetic configure events in the root coordinate
    236 		 * space when it changes the window configuration.
    237 		 * This code assumes wmii's generous behavior of
    238 		 * supplying all relevant members in every configure
    239 		 * notify event.
    240 		 */
    241 		w->r = rectaddpt(rectsetorigin(Rect(0, 0, ev->width, ev->height),
    242 					       Pt(ev->x, ev->y)),
    243 				 Pt(ev->border_width, ev->border_width));
    244 		restrut(w, tray.orientation);
    245 	}
    246 	return false;
    247 }
    248 
    249 static bool
    250 expose_event(Window *w, void *aux, XExposeEvent *ev) {
    251 
    252 	USED(w, aux, ev);
    253 	tray_draw(tray.win->r);
    254 	return false;
    255 }
    256 
    257 typedef struct Dnd Dnd;
    258 
    259 struct Dnd {
    260 	ulong	source;
    261 	ulong	dest;
    262 	long	data[4];
    263 	Point	p;
    264 	bool	have_actions;
    265 };
    266 
    267 static Dnd dnd;
    268 
    269 #define Point(l) Pt((ulong)(l) >> 16, (ulong)(l) & 0xffff)
    270 #define Long(p) ((long)(((ulong)(p).x << 16) | (ulong)(p).y))
    271 #define sendmessage(...) BLOCK( \
    272 		Dprint("(%W) %s 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx\n", __VA_ARGS__); \
    273 		sendmessage(__VA_ARGS__); \
    274        )
    275 
    276 static void
    277 dnd_updatestatus(ulong dest) {
    278 	if(dest == dnd.dest)
    279 		return;
    280 
    281 	if(dnd.dest && dnd.dest != ~0UL)
    282 		sendmessage(window(dnd.dest), "XdndLeave", tray.win->xid, 0, 0, 0, 0);
    283 	dnd.dest = dest;
    284 	if(dest)
    285 		sendmessage(window(dest), "XdndEnter", tray.win->xid,
    286 			    dnd.data[0], dnd.data[1], dnd.data[2], dnd.data[3]);
    287 	else
    288 		sendmessage(window(dnd.source), "XdndStatus", tray.win->xid, (1<<1),
    289 			    Long(tray.win->r.min), (Dx(tray.win->r)<<16) | Dy(tray.win->r), 0UL);
    290 }
    291 
    292 static void
    293 copyprop_long(Window *src, Window *dst, char *atom, char *type, long max) {
    294 	long *data;
    295 	long n;
    296 
    297 	/* Round trip. Really need to switch to XCB. */
    298 	if((n = getprop_long(src, atom, type, 0, &data, max)))
    299 		changeprop_long(dst, atom, type, data, n);
    300 	free(data);
    301 }
    302 
    303 static void
    304 copyprop_char(Window *src, Window *dst, char *atom, char *type, long max) {
    305 	uchar *data;
    306 	ulong actual, n;
    307 	int format;
    308 
    309 	n = getprop(src, atom, type, &actual, &format, 0, &data, max);
    310 	if(n > 0 && format == 8 && xatom(type) == actual)
    311 		changeprop_char(dst, atom, type, (char*)data, n);
    312 	free(data);
    313 }
    314 
    315 static bool
    316 message_event(Window *w, void *aux, XClientMessageEvent *e) {
    317 	Client *c;
    318 	long *l;
    319 	Rectangle r;
    320 	Point p;
    321 	ulong msg;
    322 
    323 	msg = e->message_type;
    324 	l = e->data.l;
    325 	Dprint("ClientMessage: %A\n", msg);
    326 	if(e->format == 32)
    327 		Dprint("\t0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx, 0x%ulx\n",
    328 		       l[0], l[1], l[2], l[3], l[4]);
    329 
    330 	if(msg == xatom("XdndEnter")) {
    331 		if(e->format != 32)
    332 			return false;
    333 		dnd = (Dnd){0};
    334 		dnd.dest = ~0UL;
    335 		dnd.source = l[0];
    336 		bcopy(&l[1], dnd.data, sizeof dnd.data);
    337 
    338 		copyprop_long(window(dnd.source), tray.win, "XdndSelection", "ATOM", 128);
    339 		if(l[1] & 0x01)
    340 			copyprop_long(window(dnd.source), tray.win, "XdndTypeList", "ATOM", 128);
    341 		return false;
    342 	}else
    343 	if(msg == xatom("XdndLeave")) {
    344 		if(e->format != 32)
    345 			return false;
    346 		dnd.source = 0UL;
    347 		if(dnd.dest)
    348 			sendmessage(window(dnd.dest), "XdndLeave", tray.win->xid, l[1], 0, 0, 0);
    349 		return false;
    350 	}else
    351 	if(msg == xatom("XdndPosition")) {
    352 		if(e->format != 32)
    353 			return false;
    354 
    355 		if(!dnd.have_actions && l[4] == xatom("XdndActionAsk")) {
    356 			dnd.have_actions = true;
    357 			copyprop_long(window(dnd.source), tray.win, "XdndActionList", "ATOM", 16);
    358 			copyprop_char(window(dnd.source), tray.win, "XdndActionDescription", "ATOM", 16 * 32);
    359 		}
    360 
    361 		dnd.p = subpt(Point(l[2]), tray.win->r.min);
    362 		for(c=tray.clients; c; c=c->next)
    363 			if(rect_haspoint_p(c->w.r, dnd.p)) {
    364 				dnd_updatestatus(c->w.xid);
    365 				sendmessage(&c->w, "XdndPosition", tray.win->xid, l[1], l[2], l[3], l[4]);
    366 				return false;
    367 			}
    368 		dnd_updatestatus(0UL);
    369 		return false;
    370 	}else
    371 	if(msg == xatom("XdndStatus")) {
    372 		if(e->format != 32)
    373 			return false;
    374 		if(l[0] != dnd.dest)
    375 			return false;
    376 
    377 		for(c=tray.clients; c; c=c->next)
    378 			if(c->w.xid == dnd.dest) {
    379 				p = Point(l[2]);
    380 				r = Rpt(p, addpt(p, Point(l[3])));
    381 				r = rect_intersection(r, rectaddpt(c->w.r, tray.win->r.min));
    382 
    383 				sendmessage(window(dnd.source), "XdndStatus", tray.win->xid, l[1],
    384 					    Long(r.min), (Dx(r)<<16) | Dy(r), l[4]);
    385 				break;
    386 			}
    387 		return false;
    388 	}else
    389 	if(msg == xatom("XdndDrop") || msg == xatom("XdndFinished")) {
    390 		if(e->format != 32)
    391 			return false;
    392 
    393 		for(c=tray.clients; c; c=c->next)
    394 			if(c->w.xid == dnd.dest) {
    395 				sendmessage(&c->w, atomname(msg),
    396 					    tray.win->xid, l[1], l[2], 0L, 0L);
    397 				break;
    398 			}
    399 		return false;
    400 	}
    401 
    402 	return true;
    403 }
    404 
    405 static Handlers handlers = {
    406 	.message = message_event,
    407 	.config = config_event,
    408 	.expose = expose_event,
    409 };
    410 
    411 static bool
    412 property_event(Window *w, void *aux, XPropertyEvent *ev) {
    413 	if(ev->atom == NET("CURRENT_DESKTOP"))
    414 		tray_resize(tray.r);
    415 	Debug if(ev->atom == NET("CURRENT_DESKTOP"))
    416 		print("property_event(_NET_CURRENT_DESKTOP)\n");
    417 	return false;
    418 }
    419 
    420 static Handlers root_handlers = {
    421 	.property = property_event,
    422 };
    423