swm

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

client.c (11959B)


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