xcontainer.c (51456B)
1 #include "shod.h" 2 3 #define DIV 15 /* see containerplace() for details */ 4 5 /* get next focused container after old on given monitor and desktop */ 6 struct Container * 7 getnextfocused(struct Monitor *mon, int desk) 8 { 9 struct Container *c; 10 11 TAILQ_FOREACH(c, &wm.focusq, entry) 12 if (containerisvisible(c, mon, desk)) 13 return c; 14 return NULL; 15 } 16 17 /* snap to edge */ 18 static void 19 snaptoedge(int *x, int *y, int w, int h) 20 { 21 struct Container *c; 22 23 if (config.snap <= 0) 24 return; 25 if (abs(*y - wm.selmon->wy) < config.snap) 26 *y = wm.selmon->wy; 27 if (abs(*y + h - wm.selmon->wy - wm.selmon->wh) < config.snap) 28 *y = wm.selmon->wy + wm.selmon->wh - h; 29 if (abs(*x - wm.selmon->wx) < config.snap) 30 *x = wm.selmon->wx; 31 if (abs(*x + w - wm.selmon->wx - wm.selmon->ww) < config.snap) 32 *x = wm.selmon->wx + wm.selmon->ww - w; 33 TAILQ_FOREACH(c, &wm.focusq, entry) { 34 if (!c->isminimized && containerisvisible(c, wm.selmon, wm.selmon->seldesk)) { 35 if (*x + w >= c->x && *x <= c->x + c->w) { 36 if (abs(*y + h - c->y) < config.snap) { 37 *y = c->y - h; 38 } 39 if (abs(*y - c->y) < config.snap) { 40 *y = c->y; 41 } 42 if (abs(*y + h - c->y - c->h) < config.snap) { 43 *y = c->y + c->h - h; 44 } 45 if (abs(*y - c->y - c->h) < config.snap) { 46 *y = c->y + c->h; 47 } 48 } 49 if (*y + h >= c->y && *y <= c->y + c->h) { 50 if (abs(*x + w - c->x) < config.snap) { 51 *x = c->x - w; 52 } 53 if (abs(*x - c->x) < config.snap) { 54 *x = c->x; 55 } 56 if (abs(*x + w - c->x - c->w) < config.snap) { 57 *x = c->x + c->w - w; 58 } 59 if (abs(*x - c->x - c->w) < config.snap) { 60 *x = c->x + c->w; 61 } 62 } 63 } 64 } 65 } 66 67 /* increment number of clients */ 68 static void 69 clientsincr(void) 70 { 71 wm.nclients++; 72 } 73 74 /* decrement number of clients */ 75 static void 76 clientsdecr(void) 77 { 78 wm.nclients--; 79 } 80 81 /* get decoration style (and state) of container */ 82 static int 83 containergetstyle(struct Container *c) 84 { 85 return (c == wm.focused) ? FOCUSED : UNFOCUSED; 86 } 87 88 /* get tab decoration style */ 89 static int 90 tabgetstyle(struct Tab *t) 91 { 92 if (t == NULL) 93 return UNFOCUSED; 94 if (t->isurgent) 95 return URGENT; 96 if (t->row->col->c == wm.focused) 97 return FOCUSED; 98 return UNFOCUSED; 99 } 100 101 /* clear window urgency */ 102 static void 103 tabclearurgency(struct Tab *tab) 104 { 105 XWMHints wmh = {0}; 106 107 XSetWMHints(dpy, tab->obj.win, &wmh); 108 tab->isurgent = 0; 109 } 110 111 /* commit tab size and position */ 112 static void 113 tabmoveresize(struct Tab *t) 114 { 115 XMoveResizeWindow(dpy, t->title, t->x, 0, t->w, config.titlewidth); 116 if (t->ptw != t->w) { 117 tabdecorate(t, 0); 118 } 119 winnotify(t->obj.win, t->row->col->c->x + t->row->col->x, t->row->col->c->y + t->row->y + config.titlewidth, t->winw, t->winh); 120 } 121 122 /* commit titlebar size and position */ 123 static void 124 titlebarmoveresize(struct Row *row, int x, int y, int w) 125 { 126 XMoveResizeWindow(dpy, row->bar, x, y, w, config.titlewidth); 127 XMoveWindow(dpy, row->bl, 0, 0); 128 XMoveWindow(dpy, row->br, w - config.titlewidth, 0); 129 } 130 131 /* calculate size of dialogs of a tab */ 132 static void 133 dialogcalcsize(struct Dialog *dial) 134 { 135 struct Tab *tab; 136 137 tab = dial->tab; 138 dial->w = max(1, min(dial->maxw, tab->winw - 2 * config.borderwidth)); 139 dial->h = max(1, min(dial->maxh, tab->winh - 2 * config.borderwidth)); 140 dial->x = tab->winw / 2 - dial->w / 2; 141 dial->y = tab->winh / 2 - dial->h / 2; 142 } 143 144 /* create new dialog */ 145 static struct Dialog * 146 dialognew(Window win, int maxw, int maxh, int ignoreunmap) 147 { 148 struct Dialog *dial; 149 150 dial = emalloc(sizeof(*dial)); 151 *dial = (struct Dialog){ 152 .pix = None, 153 .maxw = maxw, 154 .maxh = maxh, 155 .ignoreunmap = ignoreunmap, 156 .obj.win = win, 157 .obj.type = TYPE_DIALOG, 158 }; 159 dial->frame = XCreateWindow(dpy, root, 0, 0, maxw, maxh, 0, depth, CopyFromParent, visual, clientmask, &clientswa); 160 XReparentWindow(dpy, dial->obj.win, dial->frame, 0, 0); 161 XMapWindow(dpy, dial->obj.win); 162 return dial; 163 } 164 165 /* calculate position and width of tabs of a row */ 166 static void 167 rowcalctabs(struct Row *row) 168 { 169 struct Object *p, *q; 170 struct Dialog *d; 171 struct Tab *t; 172 int i, x; 173 174 if (TAILQ_EMPTY(&row->tabq)) 175 return; 176 x = config.titlewidth; 177 i = 0; 178 TAILQ_FOREACH(p, &row->tabq, entry) { 179 t = (struct Tab *)p; 180 t->winh = max(1, row->h - config.titlewidth); 181 t->winw = row->col->w; 182 t->w = max(1, ((i + 1) * (t->winw - 2 * config.titlewidth) / row->ntabs) - (i * (t->winw - 2 * config.titlewidth) / row->ntabs)); 183 t->x = x; 184 x += t->w; 185 TAILQ_FOREACH(q, &t->dialq, entry) { 186 d = (struct Dialog *)q; 187 dialogcalcsize(d); 188 } 189 i++; 190 } 191 } 192 193 /* calculate position and height of rows of a column */ 194 static void 195 colcalcrows(struct Column *col, int recalcfact) 196 { 197 struct Container *c; 198 struct Row *row; 199 int i, y, h, sumh; 200 int content; 201 int recalc; 202 203 c = col->c; 204 205 if (TAILQ_EMPTY(&col->rowq)) 206 return; 207 if (col->c->isfullscreen) { 208 TAILQ_FOREACH(row, &col->rowq, entry) { 209 row->y = -config.titlewidth; 210 row->h = col->c->h + config.titlewidth; 211 rowcalctabs(row); 212 } 213 return; 214 } 215 216 /* check if rows sum up the height of the container */ 217 content = columncontentheight(col); 218 sumh = 0; 219 recalc = 0; 220 TAILQ_FOREACH(row, &col->rowq, entry) { 221 if (!recalcfact) { 222 if (TAILQ_NEXT(row, entry) == NULL) { 223 row->h = content - sumh + config.titlewidth; 224 } else { 225 row->h = row->fact * content + config.titlewidth; 226 } 227 if (row->h < config.titlewidth) { 228 recalc = 1; 229 } 230 } 231 sumh += row->h - config.titlewidth; 232 } 233 if (sumh != content) 234 recalc = 1; 235 236 h = col->c->h - 2 * c->b - (col->nrows - 1) * config.divwidth; 237 y = c->b; 238 i = 0; 239 TAILQ_FOREACH(row, &col->rowq, entry) { 240 if (recalc) 241 row->h = max(config.titlewidth, ((i + 1) * h / col->nrows) - (i * h / col->nrows)); 242 if (recalc || recalcfact) 243 row->fact = (double)(row->h - config.titlewidth) / (double)(content); 244 row->y = y; 245 y += row->h + config.divwidth; 246 rowcalctabs(row); 247 i++; 248 } 249 } 250 251 /* create new tab */ 252 static struct Tab * 253 tabnew(Window win, Window leader, int ignoreunmap) 254 { 255 struct Tab *tab; 256 257 tab = emalloc(sizeof(*tab)); 258 *tab = (struct Tab){ 259 .ignoreunmap = ignoreunmap, 260 .pix = None, 261 .pixtitle = None, 262 .title = None, 263 .leader = leader, 264 .obj.win = win, 265 .obj.type = TYPE_NORMAL, 266 }; 267 TAILQ_INIT(&tab->dialq); 268 ((struct Object *)tab)->type = TYPE_NORMAL; 269 tab->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, CopyFromParent, visual, clientmask, &clientswa), 270 XReparentWindow(dpy, tab->obj.win, tab->frame, 0, 0); 271 XMapWindow(dpy, tab->obj.win); 272 icccmwmstate(win, NormalState); 273 clientsincr(); 274 return tab; 275 } 276 277 /* remove tab from row's tab queue */ 278 static void 279 tabremove(struct Row *row, struct Tab *tab) 280 { 281 if (row->seltab == tab) { 282 row->seltab = (struct Tab *)TAILQ_PREV((struct Object *)tab, Queue, entry); 283 if (row->seltab == NULL) { 284 row->seltab = (struct Tab *)TAILQ_NEXT((struct Object *)tab, entry); 285 } 286 } 287 row->ntabs--; 288 TAILQ_REMOVE(&row->tabq, (struct Object *)tab, entry); 289 tab->row = NULL; 290 } 291 292 /* delete tab */ 293 static void 294 tabdel(struct Tab *tab) 295 { 296 struct Dialog *dial; 297 298 while ((dial = (struct Dialog *)TAILQ_FIRST(&tab->dialq)) != NULL) { 299 XDestroyWindow(dpy, dial->obj.win); 300 unmanagedialog((struct Object *)dial, 0); 301 } 302 tabremove(tab->row, tab); 303 if (tab->pixtitle != None) 304 XFreePixmap(dpy, tab->pixtitle); 305 if (tab->pix != None) 306 XFreePixmap(dpy, tab->pix); 307 icccmdeletestate(tab->obj.win); 308 XReparentWindow(dpy, tab->obj.win, root, 0, 0); 309 XDestroyWindow(dpy, tab->title); 310 XDestroyWindow(dpy, tab->frame); 311 clientsdecr(); 312 free(tab->name); 313 free(tab); 314 } 315 316 /* create new row */ 317 struct Row * 318 rownew(void) 319 { 320 struct Row *row; 321 322 row = emalloc(sizeof(*row)); 323 *row = (struct Row){ 324 .pixbar = None, 325 .isunmapped = 0, 326 }; 327 TAILQ_INIT(&row->tabq); 328 row->frame = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, 329 depth, CopyFromParent, visual, 330 clientmask, &clientswa); 331 row->bar = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, 332 depth, CopyFromParent, visual, 333 clientmask, &clientswa); 334 row->bl = XCreateWindow(dpy, row->bar, 0, 0, config.titlewidth, config.titlewidth, 0, 335 depth, CopyFromParent, visual, 336 clientmask, &clientswa); 337 row->pixbl = XCreatePixmap(dpy, row->bl, config.titlewidth, config.titlewidth, depth); 338 row->br = XCreateWindow(dpy, row->bar, 0, 0, config.titlewidth, config.titlewidth, 0, 339 depth, CopyFromParent, visual, 340 clientmask, &clientswa); 341 row->div = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, 342 CopyFromParent, InputOnly, CopyFromParent, CWCursor, 343 &(XSetWindowAttributes){.cursor = wm.cursors[CURSOR_V]}); 344 row->pixbr = XCreatePixmap(dpy, row->bl, config.titlewidth, config.titlewidth, depth); 345 XMapWindow(dpy, row->bl); 346 XMapWindow(dpy, row->br); 347 XDefineCursor(dpy, row->bl, wm.cursors[CURSOR_HAND]); 348 XDefineCursor(dpy, row->br, wm.cursors[CURSOR_PIRATE]); 349 return row; 350 } 351 352 /* detach row from column */ 353 static void 354 rowdetach(struct Row *row, int recalc) 355 { 356 struct Column *col; 357 358 col = row->col; 359 if (col->selrow == row) { 360 col->selrow = TAILQ_PREV(row, RowQueue, entry); 361 if (col->selrow == NULL) { 362 col->selrow = TAILQ_NEXT(row, entry); 363 } 364 } 365 col->nrows--; 366 TAILQ_REMOVE(&col->rowq, row, entry); 367 if (recalc) { 368 colcalcrows(row->col, 1); 369 } 370 } 371 372 /* delete row */ 373 static void 374 rowdel(struct Row *row) 375 { 376 struct Tab *tab; 377 378 while ((tab = (struct Tab *)TAILQ_FIRST(&row->tabq)) != NULL) 379 tabdel(tab); 380 rowdetach(row, 1); 381 XDestroyWindow(dpy, row->frame); 382 XDestroyWindow(dpy, row->bar); 383 XDestroyWindow(dpy, row->bl); 384 XDestroyWindow(dpy, row->br); 385 XDestroyWindow(dpy, row->div); 386 if (row->pixbar != None) 387 XFreePixmap(dpy, row->pixbar); 388 XFreePixmap(dpy, row->pixbl); 389 XFreePixmap(dpy, row->pixbr); 390 free(row); 391 } 392 393 /* create new column */ 394 static struct Column * 395 colnew(void) 396 { 397 struct Column *col; 398 399 col = emalloc(sizeof(*col)); 400 *col = (struct Column){ 0 }; 401 TAILQ_INIT(&col->rowq); 402 col->div = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, 403 CopyFromParent, InputOnly, CopyFromParent, CWCursor, 404 &(XSetWindowAttributes){.cursor = wm.cursors[CURSOR_H]}); 405 return col; 406 } 407 408 /* detach column from container */ 409 static void 410 coldetach(struct Column *col) 411 { 412 struct Container *c; 413 414 c = col->c; 415 if (c->selcol == col) { 416 c->selcol = TAILQ_PREV(col, ColumnQueue, entry); 417 if (c->selcol == NULL) { 418 c->selcol = TAILQ_NEXT(col, entry); 419 } 420 } 421 c->ncols--; 422 TAILQ_REMOVE(&c->colq, col, entry); 423 containercalccols(col->c); 424 } 425 426 /* delete column */ 427 static void 428 coldel(struct Column *col) 429 { 430 struct Row *row; 431 432 while ((row = TAILQ_FIRST(&col->rowq)) != NULL) 433 rowdel(row); 434 coldetach(col); 435 XDestroyWindow(dpy, col->div); 436 free(col); 437 } 438 439 /* add row to column */ 440 static void 441 coladdrow(struct Column *col, struct Row *row, struct Row *prev) 442 { 443 struct Container *c; 444 struct Column *oldcol; 445 446 c = col->c; 447 oldcol = row->col; 448 row->col = col; 449 col->selrow = row; 450 col->nrows++; 451 if (prev == NULL || TAILQ_EMPTY(&col->rowq)) 452 TAILQ_INSERT_HEAD(&col->rowq, row, entry); 453 else 454 TAILQ_INSERT_AFTER(&col->rowq, prev, row, entry); 455 colcalcrows(col, 1); /* set row->y, row->h, etc */ 456 XReparentWindow(dpy, row->div, c->frame, col->x + col->w, c->b); 457 XReparentWindow(dpy, row->bar, c->frame, col->x, row->y); 458 XReparentWindow(dpy, row->frame, c->frame, col->x, row->y); 459 XMapWindow(dpy, row->bar); 460 XMapWindow(dpy, row->frame); 461 if (oldcol != NULL && oldcol->nrows == 0) { 462 coldel(oldcol); 463 } 464 } 465 466 /* add tab to row */ 467 static void 468 rowaddtab(struct Row *row, struct Tab *tab, struct Tab *prev) 469 { 470 struct Row *oldrow; 471 472 oldrow = tab->row; 473 tab->row = row; 474 row->seltab = tab; 475 row->ntabs++; 476 if (prev == NULL || TAILQ_EMPTY(&row->tabq)) 477 TAILQ_INSERT_HEAD(&row->tabq, (struct Object *)tab, entry); 478 else 479 TAILQ_INSERT_AFTER(&row->tabq, (struct Object *)prev, (struct Object *)tab, entry); 480 rowcalctabs(row); /* set tab->x, tab->w, etc */ 481 if (tab->title == None) { 482 tab->title = XCreateWindow(dpy, row->bar, tab->x, 0, tab->w, config.titlewidth, 0, 483 depth, CopyFromParent, visual, 484 clientmask, &clientswa); 485 } else { 486 XReparentWindow(dpy, tab->title, row->bar, tab->x, 0); 487 } 488 XReparentWindow(dpy, tab->frame, row->frame, 0, 0); 489 XMapWindow(dpy, tab->frame); 490 XMapWindow(dpy, tab->title); 491 if (oldrow != NULL) { /* deal with the row this tab came from */ 492 if (oldrow->ntabs == 0) { 493 rowdel(oldrow); 494 } else { 495 rowcalctabs(oldrow); 496 } 497 } 498 } 499 500 /* decorate dialog window */ 501 static void 502 dialogdecorate(struct Dialog *d) 503 { 504 int fullw, fullh; /* size of dialog window + borders */ 505 506 fullw = d->w + 2 * config.borderwidth; 507 fullh = d->h + 2 * config.borderwidth; 508 509 /* (re)create pixmap */ 510 if (d->pw != fullw || d->ph != fullh || d->pix == None) 511 pixmapnew(&d->pix, d->frame, fullw, fullh); 512 d->pw = fullw; 513 d->ph = fullh; 514 515 drawborders(d->pix, fullw, fullh, tabgetstyle(d->tab)); 516 drawcommit(d->pix, d->frame); 517 } 518 519 /* get focused fullscreen window in given monitor and desktop */ 520 static struct Container * 521 getfullscreen(struct Monitor *mon, int desk) 522 { 523 struct Container *c; 524 525 for (c = &wm.layers[LAYER_FULLSCREEN]; !ISDUMMY(c); c = TAILQ_NEXT(c, raiseentry)) 526 if (containerisvisible(c, mon, desk)) 527 return c; 528 return NULL; 529 } 530 531 /* add container into head of focus queue */ 532 static void 533 containerinsertfocus(struct Container *c) 534 { 535 TAILQ_INSERT_HEAD(&wm.focusq, c, entry); 536 } 537 538 /* add container into head of focus queue */ 539 static void 540 containerinsertraise(struct Container *c) 541 { 542 int layer; 543 544 layer = LAYER_NORMAL; 545 if (c->isfullscreen) 546 layer = LAYER_FULLSCREEN; 547 else if (c->abovebelow > 0) 548 layer = LAYER_ABOVE; 549 else if (c->abovebelow < 0) 550 layer = LAYER_BELOW; 551 TAILQ_INSERT_AFTER(&wm.stackq, &wm.layers[layer], c, raiseentry); 552 } 553 554 /* remove container from the focus list */ 555 static void 556 containerdelfocus(struct Container *c) 557 { 558 TAILQ_REMOVE(&wm.focusq, c, entry); 559 } 560 561 /* put container on beginning of focus list */ 562 static void 563 containeraddfocus(struct Container *c) 564 { 565 if (c == NULL || c->isminimized) 566 return; 567 containerdelfocus(c); 568 containerinsertfocus(c); 569 } 570 571 /* remove container from the raise list */ 572 static void 573 containerdelraise(struct Container *c) 574 { 575 TAILQ_REMOVE(&wm.stackq, c, raiseentry); 576 } 577 578 /* hide container */ 579 void 580 containerhide(struct Container *c, int hide) 581 { 582 struct Object *t, *d; 583 584 if (c == NULL) 585 return; 586 c->ishidden = hide; 587 if (hide) { 588 XUnmapWindow(dpy, c->frame); 589 } else { 590 XMapWindow(dpy, c->frame); 591 } 592 TAB_FOREACH_BEGIN(c, t) { 593 icccmwmstate(t->win, (hide ? IconicState : NormalState)); 594 TAILQ_FOREACH(d, &((struct Tab *)t)->dialq, entry) { 595 icccmwmstate(d->win, (hide ? IconicState : NormalState)); 596 } 597 }TAB_FOREACH_END 598 } 599 600 /* add column to container */ 601 static void 602 containeraddcol(struct Container *c, struct Column *col, struct Column *prev) 603 { 604 struct Container *oldc; 605 606 oldc = col->c; 607 col->c = c; 608 c->selcol = col; 609 c->ncols++; 610 if (prev == NULL || TAILQ_EMPTY(&c->colq)) 611 TAILQ_INSERT_HEAD(&c->colq, col, entry); 612 else 613 TAILQ_INSERT_AFTER(&c->colq, prev, col, entry); 614 XReparentWindow(dpy, col->div, c->frame, 0, 0); 615 containercalccols(c); 616 if (oldc != NULL && oldc->ncols == 0) { 617 containerdel(oldc); 618 } 619 } 620 621 /* send container to desktop and raise it; return nonzero if it was actually sent anywhere */ 622 static int 623 containersendtodesk(struct Container *c, struct Monitor *mon, unsigned long desk) 624 { 625 void containerstick(struct Container *c, int stick); 626 627 if (c == NULL || c->isminimized) 628 return 0; 629 if (desk == 0xFFFFFFFF) { 630 containerstick(c, ADD); 631 } else if ((int)desk < config.ndesktops) { 632 c->desk = (int)desk; 633 if (c->mon != mon) 634 containerplace(c, mon, desk, 1); 635 c->mon = mon; 636 c->issticky = 0; 637 if ((int)desk != mon->seldesk) /* container was sent to invisible desktop */ 638 containerhide(c, 1); 639 else 640 containerhide(c, 0); 641 containerraise(c, c->isfullscreen, c->abovebelow); 642 } else { 643 return 0; 644 } 645 ewmhsetwmdesktop(c); 646 ewmhsetstate(c); 647 return 1; 648 } 649 650 /* make a container occupy the whole monitor */ 651 static void 652 containerfullscreen(struct Container *c, int fullscreen) 653 { 654 if (fullscreen != REMOVE && !c->isfullscreen) 655 containerraise(c, 1, c->abovebelow); 656 else if (fullscreen != ADD && c->isfullscreen) 657 containerraise(c, 0, c->abovebelow); 658 else 659 return; 660 containercalccols(c); 661 containermoveresize(c, 1); 662 containerredecorate(c, NULL, NULL, 0); 663 ewmhsetstate(c); 664 } 665 666 /* maximize a container on the monitor */ 667 static void 668 containermaximize(struct Container *c, int maximize) 669 { 670 if (maximize != REMOVE && !c->ismaximized) 671 c->ismaximized = 1; 672 else if (maximize != ADD && c->ismaximized) 673 c->ismaximized = 0; 674 else 675 return; 676 containercalccols(c); 677 containermoveresize(c, 1); 678 containerredecorate(c, NULL, NULL, 0); 679 } 680 681 /* minimize container; optionally focus another container */ 682 static void 683 containerminimize(struct Container *c, int minimize, int focus) 684 { 685 struct Container *tofocus; 686 687 if (minimize != REMOVE && !c->isminimized) { 688 c->isminimized = 1; 689 containerhide(c, 1); 690 if (focus) { 691 if ((tofocus = getnextfocused(c->mon, c->desk)) != NULL) { 692 tabfocus(tofocus->selcol->selrow->seltab, 0); 693 containerraise(c, c->isfullscreen, c->abovebelow); 694 } else { 695 tabfocus(NULL, 0); 696 } 697 } 698 } else if (minimize != ADD && c->isminimized) { 699 (void)containersendtodesk(c, wm.selmon, wm.selmon->seldesk); 700 containermoveresize(c, 1); 701 tabfocus(c->selcol->selrow->seltab, 0); 702 containerraise(c, c->isfullscreen, c->abovebelow); 703 } 704 } 705 706 /* shade container title bar */ 707 static void 708 containershade(struct Container *c, int shade) 709 { 710 if (shade != REMOVE && !c->isshaded) { 711 c->isshaded = 1; 712 XDefineCursor(dpy, c->curswin[BORDER_NW], wm.cursors[CURSOR_W]); 713 XDefineCursor(dpy, c->curswin[BORDER_SW], wm.cursors[CURSOR_W]); 714 XDefineCursor(dpy, c->curswin[BORDER_NE], wm.cursors[CURSOR_E]); 715 XDefineCursor(dpy, c->curswin[BORDER_SE], wm.cursors[CURSOR_E]); 716 } else if (shade != ADD && c->isshaded) { 717 c->isshaded = 0; 718 XDefineCursor(dpy, c->curswin[BORDER_NW], wm.cursors[CURSOR_NW]); 719 XDefineCursor(dpy, c->curswin[BORDER_SW], wm.cursors[CURSOR_SW]); 720 XDefineCursor(dpy, c->curswin[BORDER_NE], wm.cursors[CURSOR_NE]); 721 XDefineCursor(dpy, c->curswin[BORDER_SE], wm.cursors[CURSOR_SE]); 722 } else { 723 return; 724 } 725 containercalccols(c); 726 containermoveresize(c, 1); 727 containerredecorate(c, NULL, NULL, 0); 728 if (c == wm.focused) { 729 tabfocus(c->selcol->selrow->seltab, 0); 730 } 731 } 732 733 /* stick a container on the monitor */ 734 void 735 containerstick(struct Container *c, int stick) 736 { 737 if (stick != REMOVE && !c->issticky) { 738 c->issticky = 1; 739 ewmhsetwmdesktop(c); 740 } else if (stick != ADD && c->issticky) { 741 c->issticky = 0; 742 (void)containersendtodesk(c, c->mon, c->mon->seldesk); 743 } else { 744 return; 745 } 746 } 747 748 /* raise container above others */ 749 static void 750 containerabove(struct Container *c, int above) 751 { 752 if (above != REMOVE && c->abovebelow != 1) 753 containerraise(c, c->isfullscreen, 1); 754 else if (above != ADD && c->abovebelow != 0) 755 containerraise(c, c->isfullscreen, 0); 756 else 757 return; 758 } 759 760 /* lower container below others */ 761 static void 762 containerbelow(struct Container *c, int below) 763 { 764 if (below != REMOVE && c->abovebelow != -1) 765 containerraise(c, c->isfullscreen, -1); 766 else if (below != ADD && c->abovebelow != 0) 767 containerraise(c, c->isfullscreen, 0); 768 else 769 return; 770 } 771 772 /* create new container */ 773 struct Container * 774 containernew(int x, int y, int w, int h, int state) 775 { 776 struct Container *c; 777 int i; 778 779 x -= config.borderwidth, 780 y -= config.borderwidth, 781 w += 2 * config.borderwidth, 782 h += 2 * config.borderwidth + config.titlewidth, 783 c = emalloc(sizeof *c); 784 *c = (struct Container) { 785 .x = x, .y = y, .w = w, .h = h, 786 .nx = x, .ny = y, .nw = w, .nh = h, 787 .b = config.borderwidth, 788 .pix = None, 789 .isfullscreen = (state & FULLSCREEN), 790 .ismaximized = (state & MAXIMIZED), 791 .isminimized = (state & MINIMIZED), 792 .issticky = (state & STICKY), 793 .isshaded = (state & SHADED), 794 .ishidden = 0, 795 .isobscured = 0, 796 .abovebelow = (state & ABOVE) ? +1 : (state & BELOW) ? -1 : 0, 797 }; 798 TAILQ_INIT(&c->colq); 799 c->frame = XCreateWindow(dpy, root, c->x, c->y, c->w, c->h, 0, depth, InputOutput, visual, clientmask, &clientswa); 800 c->curswin[BORDER_N] = XCreateWindow( 801 dpy, c->frame, 0, 0, 1, 1, 0, 802 CopyFromParent, InputOnly, CopyFromParent, 803 CWCursor, 804 &(XSetWindowAttributes){ 805 .cursor = wm.cursors[CURSOR_N], 806 } 807 ); 808 c->curswin[BORDER_S] = XCreateWindow( 809 dpy, c->frame, 0, 0, 1, 1, 0, 810 CopyFromParent, InputOnly, CopyFromParent, 811 CWCursor, 812 &(XSetWindowAttributes){ 813 .cursor = wm.cursors[CURSOR_S], 814 } 815 ); 816 c->curswin[BORDER_W] = XCreateWindow( 817 dpy, c->frame, 0, 0, 1, 1, 0, 818 CopyFromParent, InputOnly, CopyFromParent, 819 CWCursor, 820 &(XSetWindowAttributes){ 821 .cursor = wm.cursors[CURSOR_W], 822 } 823 ); 824 c->curswin[BORDER_E] = XCreateWindow( 825 dpy, c->frame, 0, 0, 1, 1, 0, 826 CopyFromParent, InputOnly, CopyFromParent, 827 CWCursor, 828 &(XSetWindowAttributes){ 829 .cursor = wm.cursors[CURSOR_E], 830 } 831 ); 832 c->curswin[BORDER_NW] = XCreateWindow( 833 dpy, c->frame, 0, 0, 1, 1, 0, 834 CopyFromParent, InputOnly, CopyFromParent, 835 CWCursor, 836 &(XSetWindowAttributes){ 837 .cursor = c->isshaded ? wm.cursors[CURSOR_W] : wm.cursors[CURSOR_NW], 838 } 839 ); 840 c->curswin[BORDER_SW] = XCreateWindow( 841 dpy, c->frame, 0, 0, 1, 1, 0, 842 CopyFromParent, InputOnly, CopyFromParent, 843 CWCursor, 844 &(XSetWindowAttributes){ 845 .cursor = c->isshaded ? wm.cursors[CURSOR_W] : wm.cursors[CURSOR_SW], 846 } 847 ); 848 c->curswin[BORDER_NE] = XCreateWindow( 849 dpy, c->frame, 0, 0, 1, 1, 0, 850 CopyFromParent, InputOnly, CopyFromParent, 851 CWCursor, 852 &(XSetWindowAttributes){ 853 .cursor = c->isshaded ? wm.cursors[CURSOR_E] : wm.cursors[CURSOR_NE], 854 } 855 ); 856 c->curswin[BORDER_SE] = XCreateWindow( 857 dpy, c->frame, 0, 0, 1, 1, 0, 858 CopyFromParent, InputOnly, CopyFromParent, 859 CWCursor, 860 &(XSetWindowAttributes){ 861 .cursor = c->isshaded ? wm.cursors[CURSOR_E] : wm.cursors[CURSOR_SE], 862 } 863 ); 864 for (i = 0; i < BORDER_LAST; i++) 865 XMapWindow(dpy, c->curswin[i]); 866 containerinsertfocus(c); 867 containerinsertraise(c); 868 return c; 869 } 870 871 /* delete container */ 872 void 873 containerdel(struct Container *c) 874 { 875 struct Column *col; 876 int i; 877 878 containerdelfocus(c); 879 containerdelraise(c); 880 if (wm.focused == c) 881 wm.focused = NULL; 882 TAILQ_REMOVE(&wm.focusq, c, entry); 883 while ((col = TAILQ_FIRST(&c->colq)) != NULL) 884 coldel(col); 885 if (c->pix != None) 886 XFreePixmap(dpy, c->pix); 887 XDestroyWindow(dpy, c->frame); 888 for (i = 0; i < BORDER_LAST; i++) 889 XDestroyWindow(dpy, c->curswin[i]); 890 free(c); 891 } 892 893 /* commit container size and position */ 894 void 895 containermoveresize(struct Container *c, int checkstack) 896 { 897 struct Object *t, *d; 898 struct Column *col; 899 struct Row *row; 900 struct Tab *tab; 901 struct Dialog *dial; 902 int rowy; 903 int isshaded; 904 905 if (c == NULL) 906 return; 907 XMoveResizeWindow(dpy, c->frame, c->x, c->y, c->w, c->h); 908 XMoveResizeWindow(dpy, c->curswin[BORDER_N], config.corner, 0, c->w - 2 * config.corner, c->b); 909 XMoveResizeWindow(dpy, c->curswin[BORDER_S], config.corner, c->h - c->b, c->w - 2 * config.corner, c->b); 910 XMoveResizeWindow(dpy, c->curswin[BORDER_W], 0, config.corner, c->b, c->h - 2 * config.corner); 911 XMoveResizeWindow(dpy, c->curswin[BORDER_E], c->w - c->b, config.corner, c->b, c->h - 2 * config.corner); 912 XMoveResizeWindow(dpy, c->curswin[BORDER_NW], 0, 0, config.corner, config.corner); 913 XMoveResizeWindow(dpy, c->curswin[BORDER_NE], c->w - config.corner, 0, config.corner, config.corner); 914 XMoveResizeWindow(dpy, c->curswin[BORDER_SW], 0, c->h - config.corner, config.corner, config.corner); 915 XMoveResizeWindow(dpy, c->curswin[BORDER_SE], c->w - config.corner, c->h - config.corner, config.corner, config.corner); 916 isshaded = containerisshaded(c); 917 TAILQ_FOREACH(col, &c->colq, entry) { 918 rowy = c->b; 919 if (TAILQ_NEXT(col, entry) != NULL) { 920 XMoveResizeWindow(dpy, col->div, col->x + col->w, c->b, config.divwidth, c->h - 2 * c->b); 921 XMapWindow(dpy, col->div); 922 } else { 923 XUnmapWindow(dpy, col->div); 924 } 925 TAILQ_FOREACH(row, &col->rowq, entry) { 926 if (!isshaded) { 927 if (TAILQ_NEXT(row, entry) != NULL) { 928 XMoveResizeWindow(dpy, row->div, col->x, row->y + row->h, col->w, config.divwidth); 929 XMapWindow(dpy, row->div); 930 } 931 titlebarmoveresize(row, col->x, row->y, col->w); 932 if (row->h - config.titlewidth > 0) { 933 XMoveResizeWindow(dpy, row->frame, col->x, row->y + config.titlewidth, col->w, row->h - config.titlewidth); 934 XMapWindow(dpy, row->frame); 935 row->isunmapped = 0; 936 } else { 937 XUnmapWindow(dpy, row->frame); 938 row->isunmapped = 1; 939 } 940 } else { 941 titlebarmoveresize(row, col->x, rowy, col->w); 942 XUnmapWindow(dpy, row->frame); 943 XUnmapWindow(dpy, row->div); 944 row->isunmapped = 1; 945 } 946 rowy += config.titlewidth; 947 TAILQ_FOREACH(t, &row->tabq, entry) { 948 tab = (struct Tab *)t; 949 XMoveResizeWindow(dpy, tab->frame, 0, 0, tab->winw, tab->winh); 950 TAILQ_FOREACH(d, &tab->dialq, entry) { 951 dial = (struct Dialog *)d; 952 dialogmoveresize(dial); 953 ewmhsetframeextents(dial->obj.win, c->b, 0); 954 } 955 XResizeWindow(dpy, tab->obj.win, tab->winw, tab->winh); 956 ewmhsetframeextents(tab->obj.win, c->b, TITLEWIDTH(c)); 957 tabmoveresize(tab); 958 } 959 } 960 } 961 if (!config.disablehidden && checkstack) { 962 wm.setclientlist = 1; 963 } 964 } 965 966 /* draw decoration on container frame */ 967 void 968 containerdecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, int recursive, enum Octant o) 969 { 970 struct Column *col; 971 struct Row *row; 972 struct Object *t, *d; 973 int style; 974 int isshaded; 975 976 if (c == NULL) 977 return; 978 isshaded = containerisshaded(c); 979 style = containergetstyle(c); 980 981 /* (re)create pixmap */ 982 if (c->pw != c->w || c->ph != c->h || c->pix == None) 983 pixmapnew(&c->pix, c->frame, c->w, c->h); 984 c->pw = c->w; 985 c->ph = c->h; 986 987 /* draw background */ 988 drawbackground(c->pix, 0, 0, c->w, c->h, style); 989 990 if (c->b > 0) 991 drawframe(c->pix, isshaded, c->w, c->h, o, style); 992 993 TAILQ_FOREACH(col, &c->colq, entry) { 994 /* draw column division */ 995 if (TAILQ_NEXT(col, entry) != NULL) 996 drawshadow(c->pix, col->x + col->w, c->b, config.divwidth, c->h - 2 * c->b, style, col == cdiv); 997 TAILQ_FOREACH(row, &col->rowq, entry) { 998 /* draw row division */ 999 if (TAILQ_NEXT(row, entry) != NULL) 1000 drawshadow(c->pix, col->x, row->y + row->h, col->w, config.divwidth, style, row == rdiv); 1001 1002 /* (re)create titlebar pixmap */ 1003 if (row->pw != col->w || row->pixbar == None) 1004 pixmapnew(&row->pixbar, row->bar, col->w, config.titlewidth); 1005 row->pw = col->w; 1006 1007 /* draw background of titlebar pixmap */ 1008 drawbackground(row->pixbar, 0, 0, col->w, config.titlewidth, style); 1009 drawcommit(row->pixbar, row->bar); 1010 1011 /* draw buttons */ 1012 buttonleftdecorate(row->bl, row->pixbl, style, 0); 1013 buttonrightdecorate(row->br, row->pixbr, style, 0); 1014 1015 /* decorate tabs, if necessary */ 1016 if (recursive) { 1017 TAILQ_FOREACH(t, &row->tabq, entry) { 1018 tabdecorate((struct Tab *)t, 0); 1019 TAILQ_FOREACH(d, &((struct Tab *)t)->dialq, entry) { 1020 dialogdecorate((struct Dialog *)d); 1021 } 1022 } 1023 } 1024 } 1025 } 1026 1027 drawcommit(c->pix, c->frame); 1028 } 1029 1030 /* check if container needs to be redecorated and redecorate it */ 1031 void 1032 containerredecorate(struct Container *c, struct Column *cdiv, struct Row *rdiv, enum Octant o) 1033 { 1034 if (c->pw != c->w || c->ph != c->h) { 1035 containerdecorate(c, cdiv, rdiv, 0, o); 1036 } 1037 } 1038 1039 /* calculate position and width of columns of a container */ 1040 void 1041 containercalccols(struct Container *c) 1042 { 1043 struct Column *col; 1044 int i, x, w; 1045 int sumw; 1046 int content; 1047 int recalc; 1048 1049 if (c->isfullscreen) { 1050 c->x = c->mon->mx; 1051 c->y = c->mon->my; 1052 c->w = c->mon->mw; 1053 c->h = c->mon->mh; 1054 c->b = 0; 1055 TAILQ_FOREACH(col, &c->colq, entry) { 1056 col->x = 0; 1057 col->w = c->w; 1058 colcalcrows(col, 0); 1059 } 1060 return; 1061 } else if (c->ismaximized) { 1062 c->x = c->mon->wx; 1063 c->y = c->mon->wy; 1064 c->w = c->mon->ww; 1065 c->h = c->mon->wh; 1066 c->b = config.borderwidth; 1067 } else { 1068 c->x = c->nx; 1069 c->y = c->ny; 1070 c->w = c->nw; 1071 c->h = c->nh; 1072 c->b = config.borderwidth; 1073 } 1074 if (containerisshaded(c)) { 1075 c->h = 0; 1076 } 1077 1078 /* check if columns sum up the width of the container */ 1079 content = containercontentwidth(c); 1080 sumw = 0; 1081 recalc = 0; 1082 TAILQ_FOREACH(col, &c->colq, entry) { 1083 if (TAILQ_NEXT(col, entry) == NULL) { 1084 col->w = content - sumw; 1085 } else { 1086 col->w = col->fact * content; 1087 } 1088 if (col->w == 0) { 1089 recalc = 1; 1090 } 1091 sumw += col->w; 1092 } 1093 if (sumw != content) 1094 recalc = 1; 1095 1096 w = c->w - 2 * c->b - (c->ncols - 1) * config.divwidth; 1097 x = c->b; 1098 i = 0; 1099 TAILQ_FOREACH(col, &c->colq, entry) { 1100 if (containerisshaded(c)) 1101 c->h = max(c->h, col->nrows * config.titlewidth); 1102 if (recalc) 1103 col->w = max(1, ((i + 1) * w / c->ncols) - (i * w / c->ncols)); 1104 if (recalc) 1105 col->fact = (double)col->w/(double)c->w; 1106 col->x = x; 1107 x += col->w + config.divwidth; 1108 colcalcrows(col, 0); 1109 i++; 1110 } 1111 if (containerisshaded(c)) { 1112 c->h += 2 * c->b; 1113 } 1114 } 1115 1116 /* send container to desktop and focus another on the original desktop */ 1117 void 1118 containersendtodeskandfocus(struct Container *c, struct Monitor *mon, unsigned long d) 1119 { 1120 struct Monitor *prevmon; 1121 int prevdesk, desk; 1122 1123 if (c == NULL) 1124 return; 1125 prevmon = c->mon; 1126 prevdesk = c->desk; 1127 desk = d; 1128 1129 /* is it necessary to send the container to the desktop */ 1130 if (c->mon != mon || c->desk != desk) { 1131 /* 1132 * Container sent to a desktop which is not the same 1133 * as the one it was originally at. 1134 */ 1135 if (!containersendtodesk(c, mon, d)) { 1136 /* 1137 * container could not be sent to given desktop; 1138 */ 1139 return; 1140 } 1141 } 1142 1143 /* is it necessary to focus something? */ 1144 if (mon == wm.selmon && desk == wm.selmon->seldesk) { 1145 /* 1146 * Container sent to the focused desktop. 1147 * Focus it, if visible. 1148 */ 1149 if (containerisvisible(c, mon, wm.selmon->seldesk)) { 1150 tabfocus(c->selcol->selrow->seltab, 0); 1151 } 1152 } else if (prevmon == wm.selmon && prevdesk == wm.selmon->seldesk) { 1153 /* 1154 * Container moved from the focused desktop. 1155 * Focus the next visible container, if existing; 1156 * or nothing, if there's no visible container. 1157 */ 1158 if ((c = getnextfocused(mon, prevdesk)) != NULL) { 1159 tabfocus(c->selcol->selrow->seltab, 0); 1160 } else { 1161 tabfocus(NULL, 0); 1162 } 1163 } 1164 } 1165 1166 /* move container x pixels to the right and y pixels down */ 1167 void 1168 containermove(struct Container *c, int x, int y, int relative) 1169 { 1170 struct Monitor *monto; 1171 struct Object *t; 1172 struct Tab *tab; 1173 1174 if (c == NULL || c->isminimized || c->ismaximized || c->isfullscreen) 1175 return; 1176 if (relative) { 1177 c->nx += x; 1178 c->ny += y; 1179 } else { 1180 c->nx = x; 1181 c->ny = y; 1182 } 1183 c->x = c->nx; 1184 c->y = c->ny; 1185 snaptoedge(&c->x, &c->y, c->w, c->h); 1186 XMoveWindow(dpy, c->frame, c->x, c->y); 1187 TAB_FOREACH_BEGIN(c, t){ 1188 tab = (struct Tab *)t; 1189 winnotify(tab->obj.win, c->x + col->x, c->y + row->y + config.titlewidth, tab->winw, tab->winh); 1190 }TAB_FOREACH_END 1191 if (!c->issticky) { 1192 monto = getmon(c->nx + c->nw / 2, c->ny + c->nh / 2); 1193 if (monto != NULL && monto != c->mon) { 1194 c->mon = monto; 1195 if (wm.focused == c) { 1196 deskupdate(monto, monto->seldesk); 1197 } 1198 } 1199 } 1200 } 1201 1202 /* raise container */ 1203 void 1204 containerraise(struct Container *c, int isfullscreen, int abovebelow) 1205 { 1206 struct Tab *tab; 1207 struct Menu *menu; 1208 struct Object *obj; 1209 Window wins[2]; 1210 int layer; 1211 1212 if (c == NULL || c->isminimized) 1213 return; 1214 containerdelraise(c); 1215 wins[1] = c->frame; 1216 layer = LAYER_NORMAL; 1217 if (isfullscreen) 1218 layer = LAYER_FULLSCREEN; 1219 else if (abovebelow > 0) 1220 layer = LAYER_ABOVE; 1221 else if (abovebelow < 0) 1222 layer = LAYER_BELOW; 1223 TAILQ_INSERT_AFTER(&wm.stackq, &wm.layers[layer], c, raiseentry); 1224 wins[0] = wm.layers[layer].frame; 1225 c->isfullscreen = isfullscreen; 1226 c->abovebelow = abovebelow; 1227 XRestackWindows(dpy, wins, 2); 1228 tab = c->selcol->selrow->seltab; 1229 1230 /* raise any menu for the container */ 1231 wins[0] = wm.layers[LAYER_MENU].frame; 1232 TAILQ_FOREACH(obj, &wm.menuq, entry) { 1233 menu = ((struct Menu *)obj); 1234 if (!istabformenu(tab, menu)) 1235 continue; 1236 menu = (struct Menu *)obj; 1237 wins[1] = menu->frame; 1238 XRestackWindows(dpy, wins, 2); 1239 wins[0] = menu->frame; 1240 } 1241 wm.setclientlist = 1; 1242 } 1243 1244 /* configure container size and position */ 1245 void 1246 containerconfigure(struct Container *c, unsigned int valuemask, XWindowChanges *wc) 1247 { 1248 if (c == NULL || c->isminimized || c->isfullscreen || c->ismaximized) 1249 return; 1250 if (valuemask & CWX) 1251 c->nx = wc->x; 1252 if (valuemask & CWY) 1253 c->ny = wc->y; 1254 if ((valuemask & CWWidth) && wc->width >= wm.minsize) 1255 c->nw = wc->width; 1256 if ((valuemask & CWHeight) && wc->height >= wm.minsize) 1257 c->nh = wc->height; 1258 containercalccols(c); 1259 containermoveresize(c, 1); 1260 containerredecorate(c, NULL, NULL, 0); 1261 } 1262 1263 /* set container state from client message */ 1264 void 1265 containersetstate(struct Tab *tab, Atom *props, unsigned long set) 1266 { 1267 struct Container *c; 1268 1269 if (tab == NULL) 1270 return; 1271 c = tab->row->col->c; 1272 if (props[0] == atoms[_NET_WM_STATE_MAXIMIZED_VERT] || 1273 props[0] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ] || 1274 props[1] == atoms[_NET_WM_STATE_MAXIMIZED_VERT] || 1275 props[1] == atoms[_NET_WM_STATE_MAXIMIZED_HORZ]) 1276 containermaximize(c, set); 1277 if (props[0] == atoms[_NET_WM_STATE_FULLSCREEN] || 1278 props[1] == atoms[_NET_WM_STATE_FULLSCREEN]) 1279 containerfullscreen(c, set); 1280 if (props[0] == atoms[_NET_WM_STATE_SHADED] || 1281 props[1] == atoms[_NET_WM_STATE_SHADED]) 1282 containershade(c, set); 1283 if (props[0] == atoms[_NET_WM_STATE_STICKY] || 1284 props[1] == atoms[_NET_WM_STATE_STICKY]) 1285 containerstick(c, set); 1286 if (props[0] == atoms[_NET_WM_STATE_HIDDEN] || 1287 props[1] == atoms[_NET_WM_STATE_HIDDEN]) 1288 containerminimize(c, set, (c == wm.focused)); 1289 if (props[0] == atoms[_NET_WM_STATE_ABOVE] || 1290 props[1] == atoms[_NET_WM_STATE_ABOVE]) 1291 containerabove(c, set); 1292 if (props[0] == atoms[_NET_WM_STATE_BELOW] || 1293 props[1] == atoms[_NET_WM_STATE_BELOW]) 1294 containerbelow(c, set); 1295 if (props[0] == atoms[_NET_WM_STATE_DEMANDS_ATTENTION] || 1296 props[1] == atoms[_NET_WM_STATE_DEMANDS_ATTENTION]) 1297 tabupdateurgency(tab, set == ADD || (set == TOGGLE && !tab->isurgent)); 1298 if (props[0] == atoms[_SHOD_WM_STATE_STRETCHED] || 1299 props[1] == atoms[_SHOD_WM_STATE_STRETCHED]) { 1300 rowstretch(tab->row->col, tab->row); 1301 tabfocus(tab->row->seltab, 0); 1302 } 1303 ewmhsetstate(c); 1304 } 1305 1306 /* fill placement grid for given rectangle */ 1307 static void 1308 fillgrid(struct Monitor *mon, int x, int y, int w, int h, int grid[DIV][DIV]) 1309 { 1310 int i, j; 1311 int ha, hb, wa, wb; 1312 int ya, yb, xa, xb; 1313 1314 for (i = 0; i < DIV; i++) { 1315 for (j = 0; j < DIV; j++) { 1316 ha = mon->wy + (mon->wh * i)/DIV; 1317 hb = mon->wy + (mon->wh * (i + 1))/DIV; 1318 wa = mon->wx + (mon->ww * j)/DIV; 1319 wb = mon->wx + (mon->ww * (j + 1))/DIV; 1320 ya = y; 1321 yb = y + h; 1322 xa = x; 1323 xb = x + w; 1324 if (ya <= hb && ha <= yb && xa <= wb && wa <= xb) { 1325 if (ya < ha && yb > hb) 1326 grid[i][j]++; 1327 if (xa < wa && xb > wb) 1328 grid[i][j]++; 1329 grid[i][j]++; 1330 } 1331 } 1332 } 1333 } 1334 1335 /* find best position to place a container on screen */ 1336 void 1337 containerplace(struct Container *c, struct Monitor *mon, int desk, int userplaced) 1338 { 1339 struct Container *tmp; 1340 struct Object *obj; 1341 struct Menu *menu; 1342 int grid[DIV][DIV] = {{0}, {0}}; 1343 int lowest; 1344 int i, j, k, w, h; 1345 int subx, suby; /* position of the larger subregion */ 1346 int subw, subh; /* larger subregion width and height */ 1347 1348 if (desk < 0 || desk >= config.ndesktops || c == NULL || c->isminimized) 1349 return; 1350 1351 fitmonitor(mon, &c->nx, &c->ny, &c->nw, &c->nh, 1.0); 1352 1353 /* if the user placed the window, we should not re-place it */ 1354 if (userplaced) 1355 return; 1356 1357 /* 1358 * The container area is the region of the screen where containers live, 1359 * that is, the area of the monitor not occupied by bars or the dock; it 1360 * corresponds to the region occupied by a maximized container. 1361 * 1362 * Shod tries to find an empty region on the container area or a region 1363 * with few containers in it to place a new container. To do that, shod 1364 * cuts the container area in DIV divisions horizontally and vertically, 1365 * creating DIV*DIV regions; shod then counts how many containers are on 1366 * each region; and places the new container on those regions with few 1367 * containers over them. 1368 * 1369 * After some trial and error, I found out that a DIV equals to 15 is 1370 * optimal. It is not too low to provide a incorrect placement, nor too 1371 * high to take so much computer time. 1372 */ 1373 1374 /* increment cells of grid a window is in */ 1375 TAILQ_FOREACH(tmp, &wm.focusq, entry) { 1376 if (tmp != c && containerisvisible(tmp, c->mon, c->desk)) { 1377 fillgrid(mon, tmp->nx, tmp->ny, tmp->nw, tmp->nh, grid); 1378 } 1379 } 1380 TAILQ_FOREACH(obj, &wm.menuq, entry) { 1381 menu = ((struct Menu *)obj); 1382 if (istabformenu(c->selcol->selrow->seltab, menu)) { 1383 fillgrid(mon, menu->x, menu->y, menu->w, menu->h, grid); 1384 } 1385 } 1386 1387 /* find biggest region in grid with less windows in it */ 1388 lowest = INT_MAX; 1389 subx = suby = 0; 1390 subw = subh = 0; 1391 for (i = 0; i < DIV; i++) { 1392 for (j = 0; j < DIV; j++) { 1393 if (grid[i][j] > lowest) 1394 continue; 1395 else if (grid[i][j] < lowest) { 1396 lowest = grid[i][j]; 1397 subw = subh = 0; 1398 } 1399 for (w = 0; j+w < DIV && grid[i][j + w] == lowest; w++) 1400 ; 1401 for (h = 1; i+h < DIV && grid[i + h][j] == lowest; h++) { 1402 for (k = 0; k < w && grid[i + h][j + k] == lowest; k++) 1403 ; 1404 if (k < w) { 1405 h--; 1406 break; 1407 } 1408 } 1409 if (w * h > subw * subh) { 1410 subw = w; 1411 subh = h; 1412 suby = i; 1413 subx = j; 1414 } 1415 } 1416 } 1417 subx = subx * mon->ww / DIV; 1418 suby = suby * mon->wh / DIV; 1419 subw = subw * mon->ww / DIV; 1420 subh = subh * mon->wh / DIV; 1421 c->nx = min(mon->wx + mon->ww - c->nw, max(mon->wx, mon->wx + subx + subw / 2 - c->nw / 2)); 1422 c->ny = min(mon->wy + mon->wh - c->nh, max(mon->wy, mon->wy + suby + subh / 2 - c->nh / 2)); 1423 containercalccols(c); 1424 } 1425 1426 /* check whether container is sticky or is on given desktop */ 1427 int 1428 containerisvisible(struct Container *c, struct Monitor *mon, int desk) 1429 { 1430 return !c->isminimized && c->mon == mon && (c->issticky || c->desk == desk); 1431 } 1432 1433 /* check if container can be shaded */ 1434 int 1435 containerisshaded(struct Container *c) 1436 { 1437 return c->isshaded && !c->isfullscreen; 1438 } 1439 1440 /* attach tab into row; return nonzero if an attachment was performed */ 1441 int 1442 tabattach(struct Container *c, struct Tab *det, int x, int y) 1443 { 1444 enum { CREATTAB = 0x0, CREATROW = 0x1, CREATCOL = 0x2 }; 1445 struct Column *col, *ncol; 1446 struct Row *row, *nrow; 1447 struct Tab *tab; 1448 struct Object *obj; 1449 int flag; 1450 1451 if (c == NULL) 1452 return 0; 1453 flag = CREATTAB; 1454 col = NULL; 1455 row = NULL; 1456 tab = NULL; 1457 if (x < config.borderwidth) { 1458 flag = CREATCOL | CREATROW; 1459 goto found; 1460 } 1461 if (x >= c->w - config.borderwidth) { 1462 flag = CREATCOL | CREATROW; 1463 col = TAILQ_LAST(&c->colq, ColumnQueue); 1464 goto found; 1465 } 1466 TAILQ_FOREACH(col, &c->colq, entry) { 1467 if (TAILQ_NEXT(col, entry) != NULL && x >= col->x + col->w && x < col->x + col->w + config.divwidth) { 1468 flag = CREATCOL | CREATROW; 1469 goto found; 1470 } 1471 if (x >= col->x && x < col->x + col->w) { 1472 if (y < config.borderwidth) { 1473 flag = CREATROW; 1474 goto found; 1475 } 1476 if (y >= c->h - config.borderwidth) { 1477 flag = CREATROW; 1478 row = TAILQ_LAST(&col->rowq, RowQueue); 1479 goto found; 1480 } 1481 TAILQ_FOREACH(row, &col->rowq, entry) { 1482 if (y > row->y && y <= row->y + config.titlewidth) { 1483 TAILQ_FOREACH_REVERSE(obj, &row->tabq, Queue, entry) { 1484 tab = (struct Tab *)obj; 1485 if (x > col->x + tab->x + tab->w / 2) { 1486 flag = CREATTAB; 1487 goto found; 1488 } 1489 } 1490 tab = NULL; 1491 goto found; 1492 } 1493 if (TAILQ_NEXT(row, entry) != NULL && y >= row->y + row->h && y < row->y + row->h + config.divwidth) { 1494 flag = CREATROW; 1495 goto found; 1496 } 1497 } 1498 } 1499 } 1500 return 0; 1501 found: 1502 ncol = NULL; 1503 nrow = NULL; 1504 if (flag & CREATCOL) { 1505 ncol = colnew(); 1506 containeraddcol(c, ncol, col); 1507 col = ncol; 1508 } 1509 if (flag & CREATROW) { 1510 nrow = rownew(); 1511 coladdrow(col, nrow, row); 1512 row = nrow; 1513 } 1514 rowaddtab(row, det, tab); 1515 if (ncol != NULL) 1516 containercalccols(c); 1517 else if (nrow != NULL) 1518 colcalcrows(col, 1); 1519 else 1520 rowcalctabs(row); 1521 tabfocus(det, 0); 1522 containerraise(c, c->isfullscreen, c->abovebelow); 1523 XMapSubwindows(dpy, c->frame); 1524 /* no need to call shodgrouptab() and shodgroupcontainer(); tabfocus() already calls them */ 1525 ewmhsetdesktop(det->obj.win, c->desk); 1526 containermoveresize(c, 0); 1527 containerredecorate(c, NULL, NULL, 0); 1528 return 1; 1529 } 1530 1531 /* delete row, then column if empty, then container if empty */ 1532 void 1533 containerdelrow(struct Row *row) 1534 { 1535 struct Container *c; 1536 struct Column *col; 1537 int recalc, redraw; 1538 1539 col = row->col; 1540 c = col->c; 1541 recalc = 1; 1542 redraw = 0; 1543 if (row->ntabs == 0) { 1544 rowdel(row); 1545 redraw = 1; 1546 } 1547 if (col->nrows == 0) { 1548 coldel(col); 1549 redraw = 1; 1550 } 1551 if (c->ncols == 0) { 1552 containerdel(c); 1553 recalc = 0; 1554 } 1555 if (recalc) { 1556 containercalccols(c); 1557 containermoveresize(c, 0); 1558 shodgrouptab(c); 1559 shodgroupcontainer(c); 1560 if (redraw) { 1561 containerdecorate(c, NULL, NULL, 0, 0); 1562 } 1563 } 1564 } 1565 1566 /* temporarily raise prev/next container and return it; also unhide it if hidden */ 1567 struct Container * 1568 containerraisetemp(struct Container *prevc, int backward) 1569 { 1570 struct Container *newc; 1571 Window wins[2]; 1572 1573 if (prevc == NULL) 1574 return NULL; 1575 if (backward) { 1576 for (newc = prevc; newc != NULL; newc = TAILQ_PREV(newc, ContainerQueue, entry)) { 1577 if (newc != prevc && 1578 newc->mon == prevc->mon && 1579 containerisvisible(newc, prevc->mon, prevc->desk)) { 1580 break; 1581 } 1582 } 1583 if (newc == NULL) { 1584 TAILQ_FOREACH_REVERSE(newc, &wm.focusq, ContainerQueue, entry) { 1585 if (newc != prevc && 1586 newc->mon == prevc->mon && 1587 containerisvisible(newc, prevc->mon, prevc->desk)) { 1588 break; 1589 } 1590 } 1591 } 1592 } else { 1593 for (newc = prevc; newc != NULL; newc = TAILQ_NEXT(newc, entry)) { 1594 if (newc != prevc && 1595 newc->mon == prevc->mon && 1596 containerisvisible(newc, prevc->mon, prevc->desk)) { 1597 break; 1598 } 1599 } 1600 if (newc == NULL) { 1601 TAILQ_FOREACH(newc, &wm.focusq, entry) { 1602 if (newc != prevc && 1603 newc->mon == prevc->mon && 1604 containerisvisible(newc, prevc->mon, prevc->desk)) { 1605 break; 1606 } 1607 } 1608 } 1609 } 1610 if (newc == NULL) 1611 newc = prevc; 1612 if (newc->ishidden) 1613 XMapWindow(dpy, newc->frame); 1614 /* we save the Z-axis position of the container with wm.restackwin */ 1615 wins[0] = newc->frame; 1616 wins[1] = wm.restackwin; 1617 XRestackWindows(dpy, wins, 2); 1618 XRaiseWindow(dpy, newc->frame); 1619 wm.focused = newc; 1620 containerdecorate(newc, NULL, NULL, 1, 0); 1621 ewmhsetactivewindow(newc->selcol->selrow->seltab->obj.win); 1622 return newc; 1623 } 1624 1625 /* revert container to its previous position after temporarily raised */ 1626 void 1627 containerbacktoplace(struct Container *c, int restack) 1628 { 1629 Window wins[2]; 1630 1631 if (c == NULL) 1632 return; 1633 wm.focused = NULL; 1634 containerdecorate(c, NULL, NULL, 1, 0); 1635 if (restack) { 1636 wins[0] = wm.restackwin; 1637 wins[1] = c->frame; 1638 XRestackWindows(dpy, wins, 2); 1639 } 1640 if (c->ishidden) 1641 XUnmapWindow(dpy, c->frame); 1642 XFlush(dpy); 1643 } 1644 1645 /* detach tab from row, placing it at x,y */ 1646 void 1647 tabdetach(struct Tab *tab, int x, int y) 1648 { 1649 struct Row *row; 1650 1651 row = tab->row; 1652 tabremove(row, tab); 1653 tab->ignoreunmap = IGNOREUNMAP; 1654 XReparentWindow(dpy, tab->title, root, x, y); 1655 rowcalctabs(row); 1656 } 1657 1658 /* focus tab */ 1659 void 1660 tabfocus(struct Tab *tab, int gotodesk) 1661 { 1662 struct Container *c; 1663 struct Dialog *dial; 1664 1665 wm.prevfocused = wm.focused; 1666 if (tab == NULL) { 1667 wm.focused = NULL; 1668 XSetInputFocus(dpy, wm.focuswin, RevertToParent, CurrentTime); 1669 ewmhsetactivewindow(None); 1670 } else { 1671 c = tab->row->col->c; 1672 if (!c->isfullscreen && getfullscreen(c->mon, c->desk) != NULL) 1673 return; /* we should not focus a client below a fullscreen client */ 1674 wm.focused = c; 1675 tab->row->seltab = tab; 1676 tab->row->col->selrow = tab->row; 1677 tab->row->col->c->selcol = tab->row->col; 1678 if (gotodesk) 1679 deskupdate(c->mon, c->issticky ? c->mon->seldesk : c->desk); 1680 if (tab->row->fact == 0.0) 1681 rowstretch(tab->row->col, tab->row); 1682 XRaiseWindow(dpy, tab->row->frame); 1683 XRaiseWindow(dpy, tab->frame); 1684 if (c->isshaded || tab->row->isunmapped) { 1685 XSetInputFocus(dpy, tab->row->bar, RevertToParent, CurrentTime); 1686 } else if (!TAILQ_EMPTY(&tab->dialq)) { 1687 dial = (struct Dialog *)TAILQ_FIRST(&tab->dialq); 1688 XRaiseWindow(dpy, dial->frame); 1689 XSetInputFocus(dpy, dial->obj.win, RevertToParent, CurrentTime); 1690 } else { 1691 XSetInputFocus(dpy, tab->obj.win, RevertToParent, CurrentTime); 1692 } 1693 ewmhsetactivewindow(tab->obj.win); 1694 tabclearurgency(tab); 1695 containeraddfocus(c); 1696 containerdecorate(c, NULL, NULL, 1, 0); 1697 c->isminimized = 0; 1698 containerhide(c, 0); 1699 shodgrouptab(c); 1700 shodgroupcontainer(c); 1701 ewmhsetstate(c); 1702 } 1703 if (wm.prevfocused != NULL && wm.prevfocused != wm.focused) { 1704 containerdecorate(wm.prevfocused, NULL, NULL, 1, 0); 1705 ewmhsetstate(wm.prevfocused); 1706 } 1707 menuupdate(); 1708 } 1709 1710 /* decorate tab */ 1711 void 1712 tabdecorate(struct Tab *t, int pressed) 1713 { 1714 int style; 1715 int drawlines = 0; 1716 1717 style = tabgetstyle(t); 1718 if (t->row != NULL && t != t->row->col->c->selcol->selrow->seltab) { 1719 pressed = 0; 1720 drawlines = 0; 1721 } else if (t->row != NULL && pressed) { 1722 pressed = 1; 1723 drawlines = 1; 1724 } else { 1725 pressed = 0; 1726 drawlines = 1; 1727 } 1728 1729 /* (re)create pixmap */ 1730 if (t->ptw != t->w || t->pixtitle == None) 1731 pixmapnew(&t->pixtitle, t->title, t->w, config.titlewidth); 1732 if (t->pw != t->winw || t->ph != t->winh || t->pix == None) 1733 pixmapnew(&t->pix, t->frame, t->winw, t->winh); 1734 t->ptw = t->w; 1735 t->pw = t->winw; 1736 t->ph = t->winh; 1737 1738 /* draw background */ 1739 drawbackground(t->pixtitle, 0, 0, t->w, config.titlewidth, style); 1740 1741 /* draw shadows */ 1742 drawshadow(t->pixtitle, 0, 0, t->w, config.titlewidth, style, pressed); 1743 1744 /* write tab title */ 1745 if (t->name != NULL) 1746 drawtitle(t->pixtitle, t->name, t->w, drawlines, style, pressed, 0); 1747 1748 /* draw frame background */ 1749 drawbackground(t->pix, 0, 0, t->winw, t->winh, style); 1750 1751 drawcommit(t->pixtitle, t->title); 1752 drawcommit(t->pix, t->frame); 1753 } 1754 1755 /* update tab urgency */ 1756 void 1757 tabupdateurgency(struct Tab *t, int isurgent) 1758 { 1759 int prev; 1760 1761 prev = t->isurgent; 1762 if (wm.focused != NULL && t == wm.focused->selcol->selrow->seltab) 1763 t->isurgent = False; 1764 else 1765 t->isurgent = isurgent; 1766 if (prev != t->isurgent) { 1767 tabdecorate(t, 0); 1768 } 1769 } 1770 1771 /* stretch row */ 1772 void 1773 rowstretch(struct Column *col, struct Row *row) 1774 { 1775 struct Row *r; 1776 double fact; 1777 int refact; 1778 1779 fact = 1.0 / (double)col->nrows; 1780 refact = (row->fact == 1.0); 1781 TAILQ_FOREACH(r, &col->rowq, entry) { 1782 if (refact) { 1783 r->fact = fact; 1784 } else if (r == row) { 1785 r->fact = 1.0; 1786 } else { 1787 r->fact = 0.0; 1788 } 1789 } 1790 colcalcrows(col, 0); 1791 containermoveresize(col->c, 0); 1792 } 1793 1794 /* configure dialog window */ 1795 void 1796 dialogconfigure(struct Dialog *d, unsigned int valuemask, XWindowChanges *wc) 1797 { 1798 if (d == NULL) 1799 return; 1800 if (valuemask & CWWidth) 1801 d->maxw = wc->width; 1802 if (valuemask & CWHeight) 1803 d->maxh = wc->height; 1804 dialogmoveresize(d); 1805 } 1806 1807 /* commit dialog size and position */ 1808 void 1809 dialogmoveresize(struct Dialog *dial) 1810 { 1811 struct Container *c; 1812 int dx, dy, dw, dh; 1813 1814 dialogcalcsize(dial); 1815 c = dial->tab->row->col->c; 1816 dx = dial->x - config.borderwidth; 1817 dy = dial->y - config.borderwidth; 1818 dw = dial->w + 2 * config.borderwidth; 1819 dh = dial->h + 2 * config.borderwidth; 1820 XMoveResizeWindow(dpy, dial->frame, dx, dy, dw, dh); 1821 XMoveResizeWindow(dpy, dial->obj.win, config.borderwidth, config.borderwidth, dial->w, dial->h); 1822 winnotify(dial->obj.win, c->x + dial->tab->row->col->x + dial->x, c->y + dial->tab->row->y + dial->y, dial->w, dial->h); 1823 if (dial->pw != dw || dial->ph != dh) { 1824 dialogdecorate(dial); 1825 } 1826 } 1827 1828 /* create container for tab */ 1829 void 1830 containernewwithtab(struct Tab *tab, struct Monitor *mon, int desk, XRectangle rect, int state) 1831 { 1832 struct Container *c; 1833 struct Column *col; 1834 struct Row *row; 1835 1836 if (tab == NULL) 1837 return; 1838 c = containernew(rect.x, rect.y, rect.width, rect.height, state); 1839 c->mon = mon; 1840 c->desk = desk; 1841 row = rownew(); 1842 col = colnew(); 1843 containeraddcol(c, col, NULL); 1844 coladdrow(col, row, NULL); 1845 rowaddtab(row, tab, NULL); 1846 containerredecorate(c, NULL, NULL, 0); 1847 XMapSubwindows(dpy, c->frame); 1848 containerplace(c, mon, desk, (state & USERPLACED)); 1849 containermoveresize(c, 0); 1850 if (containerisvisible(c, wm.selmon, wm.selmon->seldesk)) { 1851 containerraise(c, c->isfullscreen, c->abovebelow); 1852 containerhide(c, 0); 1853 tabfocus(tab, 0); 1854 } 1855 /* no need to call shodgrouptab() and shodgroupcontainer(); tabfocus() already calls them */ 1856 ewmhsetwmdesktop(c); 1857 wm.setclientlist = 1; 1858 } 1859 1860 /* create container for tab */ 1861 void 1862 managecontainer(struct Tab *prev, struct Monitor *mon, int desk, Window win, Window leader, XRectangle rect, int state, int ignoreunmap) 1863 { 1864 struct Tab *tab; 1865 struct Container *c; 1866 struct Row *row; 1867 1868 tab = tabnew(win, leader, ignoreunmap); 1869 winupdatetitle(tab->obj.win, &tab->name); 1870 if (prev == NULL) { 1871 containernewwithtab(tab, mon, desk, rect, state); 1872 } else { 1873 row = prev->row; 1874 c = row->col->c; 1875 rowaddtab(row, tab, prev); 1876 rowcalctabs(row); 1877 ewmhsetdesktop(win, c->desk); 1878 wm.setclientlist = 1; 1879 containermoveresize(c, 0); 1880 containerredecorate(c, NULL, NULL, 0); 1881 XMapSubwindows(dpy, c->frame); 1882 if (wm.focused == c) { 1883 tabfocus(tab, 1); 1884 } 1885 } 1886 } 1887 1888 /* create container for tab */ 1889 void 1890 managedialog(struct Tab *tab, struct Monitor *mon, int desk, Window win, Window leader, XRectangle rect, int state, int ignoreunmap) 1891 { 1892 struct Dialog *dial; 1893 1894 (void)mon; 1895 (void)desk; 1896 (void)leader; 1897 (void)state; 1898 dial = dialognew(win, rect.width, rect.height, ignoreunmap); 1899 dial->tab = tab; 1900 TAILQ_INSERT_HEAD(&tab->dialq, (struct Object *)dial, entry); 1901 XReparentWindow(dpy, dial->frame, tab->frame, 0, 0); 1902 icccmwmstate(dial->obj.win, NormalState); 1903 dialogmoveresize(dial); 1904 XMapRaised(dpy, dial->frame); 1905 if (wm.focused != NULL && wm.focused->selcol->selrow->seltab == tab) 1906 tabfocus(tab, 0); 1907 wm.setclientlist = 1; 1908 } 1909 1910 /* unmanage tab (and delete its row if it is the only tab); return whether deletion occurred */ 1911 int 1912 unmanagecontainer(struct Object *obj, int ignoreunmap) 1913 { 1914 struct Container *c, *next; 1915 struct Column *col; 1916 struct Row *row; 1917 struct Tab *t; 1918 struct Monitor *mon; 1919 int desk; 1920 int moveresize; 1921 int focus; 1922 1923 t = (struct Tab *)obj; 1924 if (ignoreunmap && t->ignoreunmap) { 1925 t->ignoreunmap--; 1926 return 0; 1927 } 1928 row = t->row; 1929 col = row->col; 1930 c = col->c; 1931 desk = c->desk; 1932 mon = c->mon; 1933 moveresize = 1; 1934 next = c; 1935 tabdel(t); 1936 focus = (c == wm.focused); 1937 if (row->ntabs == 0) { 1938 rowdel(row); 1939 if (col->nrows == 0) { 1940 coldel(col); 1941 if (c->ncols == 0) { 1942 containerdel(c); 1943 next = getnextfocused(mon, desk); 1944 moveresize = 0; 1945 } 1946 } 1947 } 1948 if (moveresize) { 1949 containercalccols(c); 1950 containermoveresize(c, 0); 1951 containerredecorate(c, NULL, NULL, 0); 1952 shodgrouptab(c); 1953 shodgroupcontainer(c); 1954 } 1955 if (focus) { 1956 tabfocus((next != NULL) ? next->selcol->selrow->seltab : NULL, 0); 1957 } 1958 return 1; 1959 } 1960 1961 /* delete dialog; return whether dialog was deleted */ 1962 int 1963 unmanagedialog(struct Object *obj, int ignoreunmap) 1964 { 1965 struct Dialog *dial; 1966 1967 dial = (struct Dialog *)obj; 1968 if (ignoreunmap && dial->ignoreunmap) { 1969 dial->ignoreunmap--; 1970 return 0; 1971 } 1972 TAILQ_REMOVE(&dial->tab->dialq, (struct Object *)dial, entry); 1973 if (dial->pix != None) 1974 XFreePixmap(dpy, dial->pix); 1975 icccmdeletestate(dial->obj.win); 1976 XReparentWindow(dpy, dial->obj.win, root, 0, 0); 1977 XDestroyWindow(dpy, dial->frame); 1978 free(dial); 1979 return 1; 1980 } 1981 1982 /* get height of column without borders, divisors, title bars, etc */ 1983 int 1984 columncontentheight(struct Column *col) 1985 { 1986 return col->c->h - col->nrows * config.titlewidth 1987 - (col->nrows - 1) * config.divwidth - 2 * col->c->b; 1988 } 1989 1990 /* get width of container without borders, divisors, etc */ 1991 int 1992 containercontentwidth(struct Container *c) 1993 { 1994 return c->w - (c->ncols - 1) * config.divwidth - 2 * c->b; 1995 }