swm

sigma window manager
git clone git://wolog.xyz/swm
Log | Files | Refs | README | LICENSE

client.c (12228B)


      1 /*
      2  * Copyright (C) Raymond Cole <rc@wolog.xyz>
      3  *
      4  * Licensed under the GNU General Public License; either version 3 of
      5  * the License, or (at your option) any later version.  See the LICENSE
      6  * file for details.
      7  *
      8  * Portions of the code originally belong to dwm as developed by Anselm
      9  * R. Garbe et al.  See LICENSE-dwm for the copyright and license details
     10  * of those portions.
     11  */
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <X11/Xatom.h>
     15 #include <X11/Xlib.h>
     16 #include <X11/Xutil.h>
     17 
     18 #include "swm.h"
     19 #include "util.h"
     20 
     21 static char *gettitle(Client *c, Atom atom);
     22 static Atom getatomprop(Client *c, Atom prop);
     23 static int xerrordummy(Display *dpy, XErrorEvent *ee);
     24 
     25 void
     26 attach(Client *c)
     27 {
     28 	c->next = c->mon->clients;
     29 	c->mon->clients = c;
     30 }
     31 
     32 void
     33 detach(Client *c)
     34 {
     35 	Client **tc;
     36 
     37 	for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next)
     38 		;
     39 	if (*tc)
     40 		*tc = c->next;
     41 }
     42 
     43 void
     44 attachstack(Client *c)
     45 {
     46 	c->snext = c->mon->stack;
     47 	c->mon->stack = c;
     48 }
     49 
     50 void
     51 detachstack(Client *c)
     52 {
     53 	Client **tc;
     54 
     55 	for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext)
     56 		;
     57 	if (*tc)
     58 		*tc = c->snext;
     59 }
     60 
     61 Client *
     62 wintoclient(Window w)
     63 {
     64 	Monitor *m;
     65 	Client *c;
     66 
     67 	for (m = mons; m; m = m->next)
     68 		for (c = m->stack; c; c = c->snext)
     69 			if (c->win == w)
     70 				return c;
     71 	return NULL;
     72 }
     73 
     74 Client *
     75 nextclient(Client *c)
     76 {
     77 	Client *r = c;
     78 
     79 	do {
     80 		r = r->next ? r->next : c->mon->clients;
     81 	} while (r != c && !ISVISIBLE(r));
     82 	return r;
     83 }
     84 
     85 Client *
     86 prevclient(Client *c)
     87 {
     88 	Client *r = c, *t;
     89 
     90 	for (t = c->mon->clients; t != c; t = t->next)
     91 		if (ISVISIBLE(t))
     92 			r = t;
     93 	if (r == c)
     94 		for (t = c->next; t; t = t->next)
     95 			if (ISVISIBLE(t))
     96 				r = t;
     97 	return r;
     98 }
     99 
    100 void
    101 notifyconfigure(Client *c)
    102 {
    103 	XConfigureEvent ce;
    104 
    105 	ce.type = ConfigureNotify;
    106 	ce.display = dpy;
    107 	ce.event = c->win;
    108 	ce.window = c->win;
    109 	if (c->isfullscreen) {
    110 		ce.x = c->mon->x;
    111 		ce.y = c->mon->y;
    112 		ce.width = c->mon->w;
    113 		ce.height = c->mon->h;
    114 		ce.border_width = 0;
    115 	} else {
    116 		ce.x = c->x;
    117 		ce.y = c->y;
    118 		ce.width = c->w;
    119 		ce.height = c->h;
    120 		ce.border_width = borderw;
    121 	}
    122 	if (c->ishidden)
    123 		ce.x = -WIDTH(c);
    124 	ce.above = None;
    125 	ce.override_redirect = False;
    126 	XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
    127 }
    128 
    129 void
    130 configure(Client *c, int resize)
    131 {
    132 	int mask = CWX|CWY|CWBorderWidth;
    133 	XWindowChanges wc;
    134 
    135 	wc.border_width = borderw;
    136 	if (c->isfullscreen) {
    137 		wc.x = c->mon->x;
    138 		wc.y = c->mon->y;
    139 		wc.width = c->mon->w;
    140 		wc.height = c->mon->h;
    141 		wc.border_width = 0;
    142 	} else {
    143 		wc.x = c->x;
    144 		wc.y = c->y;
    145 		wc.width = c->w;
    146 		wc.height = c->h;
    147 		wc.border_width = borderw;
    148 	}
    149 	if ((c->ishidden = !ISVISIBLE(c)))
    150 		wc.x = -WIDTH(c);
    151 	if (resize)
    152 		mask |= CWWidth|CWHeight;
    153 	XConfigureWindow(dpy, c->win, mask, &wc);
    154 	if (!resize)
    155 		notifyconfigure(c);
    156 }
    157 
    158 char *
    159 gettitle(Client *c, Atom prop)
    160 {
    161 	size_t max = c->bytes + sizeof(c->bytes) - c->title;
    162 	char **list = NULL;
    163 	XTextProperty text;
    164 	int n;
    165 
    166 	if (!XGetTextProperty(dpy, c->win, &text, prop) || !text.nitems)
    167 		return NULL;
    168 	if (text.encoding == XA_STRING) {
    169 		*stpncpy(c->title, (char *)text.value, max - 1) = '\0';
    170 	} else if (XmbTextPropertyToTextList(dpy, &text, &list, &n) >= Success
    171 	           && n > 0 && *list) {
    172 		*stpncpy(c->title, *list, max - 1) = '\0';
    173 		XFreeStringList(list);
    174 	} else {
    175 		XFree(text.value);
    176 		return NULL;
    177 	}
    178 	XFree(text.value);
    179 	return c->title;
    180 }
    181 
    182 void
    183 updatetitle(Client *c)
    184 {
    185 	if (!gettitle(c, netatom[NetWMName]) && !gettitle(c, XA_WM_NAME))
    186 		*stpncpy(c->title, "broken",
    187 		         c->bytes + sizeof(c->bytes) - c->title - 1) = '\0';
    188 }
    189 
    190 Atom
    191 getatomprop(Client *c, Atom prop)
    192 {
    193 	int di;
    194 	unsigned long dl;
    195 	unsigned char *p = NULL;
    196 	Atom da, atom = None;
    197 
    198 	if (XGetWindowProperty(dpy, c->win, prop, 0L,
    199 	                       sizeof(atom), False, XA_ATOM,
    200 	                       &da, &di, &dl, &dl, &p) == Success && p) {
    201 		atom = *(Atom *)p;
    202 		XFree(p);
    203 	}
    204 	return atom;
    205 }
    206 
    207 void
    208 updatewindowtype(Client *c)
    209 {
    210 	Atom state = getatomprop(c, netatom[NetWMState]);
    211 	Atom wtype = getatomprop(c, netatom[NetWMWindowType]);
    212 
    213 	if (state == netatom[NetWMStateFullscreen])
    214 		setfullscreen(c, 1);
    215 	if (wtype == netatom[NetWMWindowTypeDialog])
    216 		setfloating(c, 1);
    217 }
    218 
    219 void
    220 updatewmhints(Client *c)
    221 {
    222 	XWMHints *wmh;
    223 
    224 	if ((wmh = XGetWMHints(dpy, c->win))) {
    225 		if (c != selmon->sel || !(wmh->flags & XUrgencyHint))
    226 			seturgent(c, wmh->flags & XUrgencyHint);
    227 		c->neverfocus = wmh->flags & InputHint ? !wmh->input : 0;
    228 		XFree(wmh);
    229 	} else {
    230 		seturgent(c, 0);
    231 		c->neverfocus = 0;
    232 	}
    233 }
    234 
    235 void
    236 updatesizehints(Client *c)
    237 {
    238 	long msize;
    239 	XSizeHints size;
    240 
    241 	if (!XGetWMNormalHints(dpy, c->win, &size, &msize))
    242 		size.flags = 0;
    243 	if (size.flags & PBaseSize) {
    244 		c->basew = size.base_width;
    245 		c->baseh = size.base_height;
    246 	} else {
    247 		c->basew = c->baseh = 0;
    248 	}
    249 	if (size.flags & PMinSize) {
    250 		c->minw = size.min_width;
    251 		c->minh = size.min_height;
    252 	} else {
    253 		c->minw = c->basew;
    254 		c->minh = c->baseh;
    255 	}
    256 	if (size.flags & PMaxSize) {
    257 		c->maxw = size.max_width;
    258 		c->maxh = size.max_height;
    259 	} else {
    260 		c->maxw = c->maxh = 0;
    261 	}
    262 	if (size.flags & PResizeInc) {
    263 		c->incw = size.width_inc;
    264 		c->inch = size.height_inc;
    265 	} else {
    266 		c->incw = c->inch = 0;
    267 	}
    268 	if (size.flags & PAspect) {
    269 		c->mina = (float)size.min_aspect.x / size.min_aspect.y;
    270 		c->maxa = (float)size.max_aspect.x / size.max_aspect.y;
    271 	} else {
    272 		c->maxa = c->mina = -1;
    273 	}
    274 	c->hintsvalid = 1;
    275 	c->isfixed = c->maxw && c->maxw == c->minw
    276 	          && c->maxh && c->maxh == c->minh;
    277 	if (c->isfixed && !c->isfloating)
    278 		setfloating(c, 1);
    279 }
    280 
    281 void
    282 applysizehints(Client *c, int *w, int *h)
    283 {
    284 	int basew, baseh, t;
    285 
    286 	if (!c->hintsvalid)
    287 		updatesizehints(c);
    288 	basew = c->basew;
    289 	baseh = c->baseh;
    290 	if (*w < c->minw)
    291 		*w = c->minw;
    292 	if (c->maxw && *w > c->maxw)
    293 		*w = c->maxw;
    294 	if (*h < c->minh)
    295 		*h = c->minh;
    296 	if (c->maxh && *h > c->maxh)
    297 		*h = c->maxh;
    298 	if (*w > basew && *h > baseh) {
    299 		*w -= basew;
    300 		*h -= baseh;
    301 		if (c->maxa > 0 && *w > (t = *h * c->maxa))
    302 			*w = t;
    303 		if (c->mina > 0 && *h > (t = *w / c->mina))
    304 			*h = t;
    305 		*w += basew;
    306 		*h += baseh;
    307 	}
    308 	if (!basew)
    309 		basew = c->minw;
    310 	if (!baseh)
    311 		baseh = c->minh;
    312 	if (*w > basew && *h > baseh) {
    313 		*w -= basew;
    314 		*h -= baseh;
    315 		if (c->incw) {
    316 			*w -= t = *w % c->incw;
    317 			if (t > c->incw / 2
    318 			&& *w + c->incw <= c->maxw - basew)
    319 				*w += c->incw;
    320 		}
    321 		if (c->inch) {
    322 			*h -= t = *h % c->inch;
    323 			if (t > c->inch / 2
    324 			&& *h + c->inch <= c->maxh - baseh)
    325 				*h += c->inch;
    326 		}
    327 		*w += basew;
    328 		*h += baseh;
    329 	}
    330 }
    331 
    332 void
    333 elevate(Client *c)
    334 {
    335 	XWindowChanges wc;
    336 
    337 	detachstack(c);
    338 	attachstack(c);
    339 	if (c->isfullscreen || !c->mon->lyt || c->isfloating) {
    340 		XRaiseWindow(dpy, c->win);
    341 	} else {
    342 		wc.sibling = c->mon->barwin;
    343 		wc.stack_mode = Below;
    344 		XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc);
    345 	}
    346 }
    347 
    348 int
    349 sendevent(Client *c, Atom proto)
    350 {
    351 	int n;
    352 	Atom *protocols;
    353 	XEvent ev;
    354 
    355 	if (!XGetWMProtocols(dpy, c->win, &protocols, &n))
    356 		return 0;
    357 	while (n-- && protocols[n] != proto)
    358 		;
    359 	XFree(protocols);
    360 	if (n < 0)
    361 		return 0;
    362 	ev.type = ClientMessage;
    363 	ev.xclient.window = c->win;
    364 	ev.xclient.message_type = wmatom[WMProtocols];
    365 	ev.xclient.format = 32;
    366 	ev.xclient.data.l[0] = proto;
    367 	ev.xclient.data.l[1] = CurrentTime;
    368 	XSendEvent(dpy, c->win, False, NoEventMask, &ev);
    369 	return 1;
    370 }
    371 
    372 void
    373 setclientstate(Client *c, long state)
    374 {
    375 	long data[] = { state, None };
    376 
    377 	XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState],
    378 	                32, PropModeReplace, (unsigned char *)data, 2);
    379 }
    380 
    381 void
    382 setfloating(Client *c, int val)
    383 {
    384 	if ((c->isfloating = !!val)) {
    385 		applysizehints(c, &c->w, &c->h);
    386 		configure(c, 1);
    387 	}
    388 	parrange(c->mon);
    389 	if (c == c->mon->sel)
    390 		pdrawbar(c->mon);
    391 }
    392 
    393 void
    394 setfullscreen(Client *c, int val)
    395 {
    396 	if (val) {
    397 		XChangeProperty(dpy, c->win, netatom[NetWMState],
    398 		                XA_ATOM, 32, PropModeReplace,
    399 		                (unsigned char *)&netatom[NetWMStateFullscreen],
    400 		                1);
    401 		c->isfullscreen = 1;
    402 		configure(c, 1);
    403 		elevate(c);
    404 	} else {
    405 		XChangeProperty(dpy, c->win, netatom[NetWMState],
    406 		                XA_ATOM, 32, PropModeReplace, NULL, 0);
    407 		c->isfullscreen = 0;
    408 		configure(c, 1);
    409 		parrange(c->mon);
    410 		pdrawbar(c->mon);
    411 	}
    412 }
    413 
    414 void
    415 setscratch(Client *c, int val)
    416 {
    417 	if (!val && !(c->tags & TAGMASK))
    418 		return;
    419 	c->isscratch = !!val;
    420 	if (val && c->isfloating) {
    421 		c->x = c->mon->x + (c->mon->w - WIDTH(c)) / 2;
    422 		c->y = c->mon->y + (c->mon->h - HEIGHT(c)) / 2;
    423 		configure(c, 0);
    424 	}
    425 }
    426 
    427 void
    428 seturgent(Client *c, int val)
    429 {
    430 	c->isurgent = !!val;
    431 	if (c != selmon->sel)
    432 		XSetWindowBorder(dpy, c->win, val ? pixelurgent : pixelunfocus);
    433 	pdrawbar(c->mon);
    434 }
    435 
    436 void
    437 focus(Client *c)
    438 {
    439 	while (c && !ISVISIBLE(c))
    440 		c = c->snext;
    441 	if (selmon->sel) {
    442 		grabbuttons(selmon->sel, 0);
    443 		XSetWindowBorder(dpy, selmon->sel->win, selmon->sel->isurgent ?
    444 		                 pixelurgent : pixelunfocus);
    445 		selmon->sel = NULL;
    446 	}
    447 	if (c) {
    448 		if (selmon != c->mon) {
    449 			pdrawbar(selmon);
    450 			selmon = c->mon;
    451 		}
    452 		elevate(c);
    453 		grabbuttons(c, 1);
    454 		XSetWindowBorder(dpy, c->win, pixelfocus);
    455 		if (!c->neverfocus) {
    456 			XSetInputFocus(dpy, c->win, RevertToPointerRoot,
    457 			               CurrentTime);
    458 			XChangeProperty(dpy, root, netatom[NetActiveWindow],
    459 			                XA_WINDOW, 32, PropModeReplace,
    460 			                (unsigned char *)&(c->win), 1);
    461 		}
    462 		sendevent(c, wmatom[WMTakeFocus]);
    463 	} else {
    464 		XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
    465 		XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
    466 	}
    467 	selmon->sel = c;
    468 	pdrawbar(selmon);
    469 }
    470 
    471 void
    472 sendtomon(Client *c, Monitor *m)
    473 {
    474 	if (c->mon == m)
    475 		return;
    476 	parrange(c->mon);
    477 	parrange(m);
    478 	if (c == selmon->sel)
    479 		focus(c->snext);
    480 	detach(c);
    481 	detachstack(c);
    482 	c->mon = m;
    483 	c->tags = 1 << m->selws;
    484 	attach(c);
    485 	attachstack(c);
    486 }
    487 
    488 Client *
    489 manage(Window w, XWindowAttributes *wa)
    490 {
    491 	XClassHint ch = { NULL, NULL };
    492 	Client *c, *t = NULL;
    493 	int istrans;
    494 	Window tw;
    495 
    496 	if (!(c = malloc(sizeof(*c)))) {
    497 		warn("malloc:");
    498 		return NULL;
    499 	}
    500 	c->win = w;
    501 	c->x = wa->x;
    502 	c->y = wa->y;
    503 	c->w = wa->width;
    504 	c->h = wa->height;
    505 	c->isfloating = c->isfullscreen = c->isscratch = c->isurgent = 0;
    506 	istrans = !!XGetTransientForHint(dpy, w, &tw);
    507 	if (istrans && (t = wintoclient(tw))) {
    508 		c->mon = t->mon;
    509 		c->tags = t->tags ? t->tags : 1 << selmon->selws;
    510 	} else {
    511 		c->mon = selmon;
    512 		c->tags = 1 << selmon->selws;
    513 	}
    514 	if (c->x < 0)
    515 		c->x = 0;
    516 	else if (c->x > sw - WIDTH(c))
    517 		c->x = sw - WIDTH(c);
    518 	if (c->y < 0)
    519 		c->y = 0;
    520 	else if (c->y > sh - HEIGHT(c))
    521 		c->y = sh - HEIGHT(c);
    522 	XGetClassHint(dpy, c->win, &ch);
    523 	c->appclass = c->bytes;
    524 	c->appname = stpncpy(c->appclass, ch.res_class ? ch.res_class : "",
    525 	                     sizeof(c->bytes) - 3);
    526 	*c->appname++ = '\0';
    527 	c->title = stpncpy(c->appname, ch.res_name ? ch.res_name : "",
    528 	                   c->bytes + sizeof(c->bytes) - c->appname - 2);
    529 	*c->title++ = '\0';
    530 	if (ch.res_class)
    531 		XFree(ch.res_class);
    532 	if (ch.res_name)
    533 		XFree(ch.res_name);
    534 	updatetitle(c);
    535 	updatesizehints(c);
    536 	updatewmhints(c);
    537 	updatewindowtype(c);
    538 	if (istrans && !c->isfloating)
    539 		setfloating(c, 1);
    540 	attach(c);
    541 	attachstack(c);
    542 	applyrules(c);
    543 	XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask
    544 	                     |PropertyChangeMask|StructureNotifyMask);
    545 	setclientstate(c, NormalState);
    546 	configure(c, 0);
    547 	if (wa->map_state == IsViewable) {
    548 		parrange(c->mon);
    549 	} else {
    550 		arrange(c->mon);
    551 		XMapWindow(dpy, c->win);
    552 	}
    553 	focus(selmon->stack);
    554 	if (c != selmon->sel) {
    555 		grabbuttons(c, 0);
    556 		if (!c->isurgent)
    557 			XSetWindowBorder(dpy, w, pixelunfocus);
    558 	}
    559 	pdrawbar(c->mon);
    560 	return c;
    561 }
    562 
    563 int
    564 xerrordummy(Display *dpy, XErrorEvent *ee)
    565 {
    566 	return 0;
    567 }
    568 
    569 void
    570 unmanage(Client *c, int destroyed)
    571 {
    572 	XWindowChanges wc;
    573 	int (*xerror)(Display *, XErrorEvent *);
    574 
    575 	detach(c);
    576 	detachstack(c);
    577 	if (selmon->sel == c) {
    578 		selmon->sel = NULL;
    579 		focus(selmon->stack);
    580 	}
    581 	if (!destroyed) {
    582 		wc.border_width = 0;
    583 		XGrabServer(dpy); /* avoid race conditions */
    584 		xerror = XSetErrorHandler(xerrordummy);
    585 		XSelectInput(dpy, c->win, NoEventMask);
    586 		XConfigureWindow(dpy, c->win, CWBorderWidth, &wc);
    587 		XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
    588 		setclientstate(c, WithdrawnState);
    589 		XSync(dpy, False);
    590 		XSetErrorHandler(xerror);
    591 		XUngrabServer(dpy);
    592 	}
    593 	parrange(c->mon);
    594 	free(c);
    595 }