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 }