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 }