progman.exe^H^H^H^H
at master 1407 lines 32 kB view raw
1/* 2 * Copyright 2020 joshua stein <jcs@jcs.org> 3 * Copyright 1998-2007 Decklin Foster <decklin@red-bean.com>. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to 7 * deal in the Software without restriction, including without limitation the 8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 * sell copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice shall be included in 13 * all copies or substantial portions of the Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 19 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 */ 22 23#include <err.h> 24#include <stdio.h> 25#include <stdlib.h> 26#include <string.h> 27#include <sys/types.h> 28#include <time.h> 29#include <X11/Xatom.h> 30#include <X11/extensions/shape.h> 31#include "progman.h" 32#include "atom.h" 33 34static void do_iconify(client_t *); 35static void do_shade(client_t *); 36static void maybe_toolbar_click(client_t *, Window); 37static void monitor_toolbar_click(client_t *, geom_t, int, int, int, int, 38 strut_t *, void *); 39 40static struct { 41 struct timespec tv; 42 client_t *c; 43 Window win; 44 int button; 45} last_click = { { 0, 0 }, NULL, 0 }; 46 47void 48user_action(client_t *c, Window win, int x, int y, int button, int down) 49{ 50 struct timespec now; 51 long long tdiff; 52 int double_click = 0; 53 54 if (!down) { 55 clock_gettime(CLOCK_MONOTONIC, &now); 56 57 if (last_click.button == button && c == last_click.c && 58 win == last_click.win) { 59 tdiff = (((now.tv_sec * 1000000000) + now.tv_nsec) - 60 ((last_click.tv.tv_sec * 1000000000) + 61 last_click.tv.tv_nsec)) / 1000000; 62 if (tdiff <= DOUBLE_CLICK_MSEC) 63 double_click = 1; 64 } 65 66 last_click.button = button; 67 last_click.c = c; 68 last_click.win = win; 69 memcpy(&last_click.tv, &now, sizeof(now)); 70 } 71 72#ifdef DEBUG 73 printf("%s(\"%s\", %lx, %d, %d, %d, %d) double:%d, c state %d\n", 74 __func__, c->name, win, x, y, button, down, double_click, c->state); 75 dump_info(c); 76#endif 77 78 if (c->state & STATE_ICONIFIED && 79 (win == c->icon || win == c->icon_label)) { 80 if (down && !double_click) { 81 focus_client(c, FOCUS_NORMAL); 82 move_client(c); 83 get_pointer(&x, &y); 84 user_action(c, win, x, y, button, 0); 85 } else if (!down && double_click) 86 uniconify_client(c); 87 } else if (win == c->titlebar) { 88 if (button == 1 && down) { 89 if (!(c->state & (STATE_ZOOMED | STATE_FULLSCREEN))) { 90 move_client(c); 91 /* sweep() eats the ButtonRelease event */ 92 get_pointer(&x, &y); 93 user_action(c, win, x, y, button, 0); 94 } 95 } else if (button == 1 && !down && double_click) { 96 if (c->state & STATE_ZOOMED) 97 unzoom_client(c); 98 else 99 zoom_client(c); 100 } else if (button == 3 && !down) { 101 if (c->state & STATE_SHADED) 102 unshade_client(c); 103 else 104 shade_client(c); 105 } 106 } else if (win == c->close) { 107 if (button == 1 && down) { 108 maybe_toolbar_click(c, win); 109 if (!c->close_pressed) 110 return; 111 112 c->close_pressed = False; 113 redraw_frame(c, c->close); 114 115 get_pointer(&x, &y); 116 user_action(c, win, x, y, button, 0); 117 } 118 119 if (double_click) 120 send_wm_delete(c); 121 } else if (IS_RESIZE_WIN(c, win)) { 122 if (button == 1 && down && !(c->state & STATE_SHADED)) 123 resize_client(c, win); 124 } else if (win == c->iconify) { 125 if (button == 1 && down) { 126 maybe_toolbar_click(c, win); 127 if (c->iconify_pressed) { 128 c->iconify_pressed = False; 129 redraw_frame(c, c->iconify); 130 if (c->state & STATE_ICONIFIED) 131 uniconify_client(c); 132 else 133 iconify_client(c); 134 } 135 } 136 } else if (win == c->zoom) { 137 if (button == 1 && down) { 138 maybe_toolbar_click(c, win); 139 if (c->zoom_pressed) { 140 c->zoom_pressed = False; 141 redraw_frame(c, c->zoom); 142 if (c->state & STATE_ZOOMED) 143 unzoom_client(c); 144 else 145 zoom_client(c); 146 } 147 } 148 } 149 150 if (double_click) 151 /* don't let a 3rd click keep counting as a double click */ 152 last_click.tv.tv_sec = last_click.tv.tv_nsec = 0; 153} 154 155Cursor 156cursor_for_resize_win(client_t *c, Window win) 157{ 158 if (win == c->resize_nw) 159 return resize_nw_curs; 160 else if (win == c->resize_w) 161 return resize_w_curs; 162 else if (win == c->resize_sw) 163 return resize_sw_curs; 164 else if (win == c->resize_s) 165 return resize_s_curs; 166 else if (win == c->resize_se) 167 return resize_se_curs; 168 else if (win == c->resize_e) 169 return resize_e_curs; 170 else if (win == c->resize_ne) 171 return resize_ne_curs; 172 else if (win == c->resize_n) 173 return resize_n_curs; 174 else 175 return None; 176} 177 178/* This can't do anything dangerous. */ 179void 180focus_client(client_t *c, int style) 181{ 182 client_t *prevfocused = NULL; 183 client_t *trans[10] = { NULL }; 184 client_t *p; 185 int transcount = 0; 186 187 if (!c) { 188 warnx("%s with no c", __func__); 189 abort(); 190 } 191 192 if (focused == c && style != FOCUS_FORCE) 193 return; 194 195#ifdef DEBUG 196 dump_name(c, __func__, NULL, c->name); 197#endif 198 199 if (c->state & STATE_ICONIFIED) { 200 set_atoms(root, net_active_window, XA_WINDOW, &c->icon, 1); 201 XSetInputFocus(dpy, c->icon, RevertToPointerRoot, CurrentTime); 202 } else { 203 set_atoms(root, net_active_window, XA_WINDOW, &c->win, 1); 204 XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); 205 XInstallColormap(dpy, c->cmap); 206 } 207 208 if (focused && focused != c) { 209 prevfocused = focused; 210 211 if (focused->state & STATE_ICONIFIED) 212 adjust_client_order(focused, 213 ORDER_ICONIFIED_TOP); 214 else 215 adjust_client_order(focused, ORDER_TOP); 216 } 217 218 adjust_client_order(c, ORDER_TOP); 219 220 /* raise any transients of this window */ 221 for (p = focused; p; p = p->next) { 222 if (p->trans == c->win) { 223 trans[transcount++] = p; 224 if (transcount == sizeof(trans) / (sizeof(trans[0]))) 225 break; 226 } 227 } 228 if (transcount != 0) { 229 for (transcount--; transcount >= 0; transcount--) { 230#ifdef DEBUG 231 dump_name(c, __func__, "transient", 232 trans[transcount]->name); 233#endif 234 adjust_client_order(trans[transcount], ORDER_TOP); 235 } 236 } 237 238 restack_clients(); 239 240 if (prevfocused) 241 redraw_frame(prevfocused, None); 242 243 redraw_frame(c, None); 244} 245 246void 247move_client(client_t *c) 248{ 249 strut_t s = { 0 }; 250 251 if (c->state & (STATE_ZOOMED | STATE_FULLSCREEN | STATE_DOCK)) 252 return; 253 254 collect_struts(c, &s); 255 dragging = c; 256 sweep(c, move_curs, recalc_move, NULL, &s); 257 dragging = NULL; 258 259 if (!(c->state & STATE_ICONIFIED)) 260 redraw_frame(c, None); 261 262 send_config(c); 263 flush_expose_client(c); 264} 265 266/* 267 * If we are resizing a client that was zoomed, we have to put it in an 268 * unzoomed state, but we need to start sweeping from the effective geometry 269 * rather than the "real" geometry that unzooming will restore. We get around 270 * this by blatantly cheating. 271 */ 272void 273resize_client(client_t *c, Window resize_win) 274{ 275 strut_t hold = { 0, 0, 0, 0 }; 276 XEvent junk; 277 278 if (c->state & STATE_ZOOMED) { 279 c->save = c->geom; 280 unzoom_client(c); 281 } 282 283 sweep(c, cursor_for_resize_win(c, resize_win), recalc_resize, 284 &resize_win, &hold); 285 286 if (c->shaped) { 287 /* flush ShapeNotify events */ 288 while (XCheckTypedWindowEvent(dpy, c->win, shape_event, &junk)) 289 ; 290 } 291} 292 293/* 294 * The user has clicked on a toolbar button but may mouse off of it and then 295 * let go, so only consider it a click if the mouse is still there when the 296 * mouse button is released. 297 */ 298void 299maybe_toolbar_click(client_t *c, Window win) 300{ 301 if (win == c->iconify) 302 c->iconify_pressed = True; 303 else if (win == c->zoom) 304 c->zoom_pressed = True; 305 else if (win == c->close) 306 c->close_pressed = True; 307 else 308 return; 309 310 redraw_frame(c, win); 311 sweep(c, None, monitor_toolbar_click, &win, NULL); 312 redraw_frame(c, win); 313} 314 315void 316monitor_toolbar_click(client_t *c, geom_t orig, int x0, int y0, int x1, int y1, 317 strut_t *s, void *arg) 318{ 319 Window win = *(Window *)arg; 320 geom_t *geom; 321 Bool was, *pr; 322 323 if (win == c->iconify) { 324 geom = &c->iconify_geom; 325 pr = &c->iconify_pressed; 326 } else if (win == c->zoom) { 327 geom = &c->zoom_geom; 328 pr = &c->zoom_pressed; 329 } else if (win == c->close) { 330 geom = &c->close_geom; 331 pr = &c->close_pressed; 332 } else 333 return; 334 335 was = *pr; 336 337 if (x1 >= (c->frame_geom.x + geom->x) && 338 x1 <= (c->frame_geom.x + geom->x + geom->w) && 339 y1 >= (c->frame_geom.y + geom->y) && 340 y1 <= (c->frame_geom.y + geom->y + geom->h)) 341 *pr = True; 342 else 343 *pr = False; 344 345 if (was != *pr) 346 redraw_frame(c, win); 347} 348 349 350/* Transients will be iconified when their owner is iconified. */ 351void 352iconify_client(client_t *c) 353{ 354 client_t *p; 355 356 for (p = focused; p; p = p->next) 357 if (p->trans == c->win) 358 do_iconify(p); 359 360 do_iconify(c); 361 362 focus_client(focused, FOCUS_FORCE); 363} 364 365void 366do_iconify(client_t *c) 367{ 368 XSetWindowAttributes attrs = { 0 }; 369 XGCValues gv; 370 371 adjust_client_order(c, ORDER_ICONIFIED_TOP); 372 373 if (!c->ignore_unmap) 374 c->ignore_unmap++; 375 XUnmapWindow(dpy, c->frame); 376 XUnmapWindow(dpy, c->win); 377 c->state |= STATE_ICONIFIED; 378 set_wm_state(c, IconicState); 379 380 get_client_icon(c); 381 382 if (c->icon_name) 383 XFree(c->icon_name); 384 c->icon_name = get_wm_icon_name(c->win); 385 386 if (c->icon_geom.w < 1) 387 c->icon_geom.w = icon_size; 388 if (c->icon_geom.h < 1) 389 c->icon_geom.h = icon_size; 390 391 attrs.background_pixel = BlackPixel(dpy, screen); 392 attrs.event_mask = ButtonPressMask | ButtonReleaseMask | 393 VisibilityChangeMask | ExposureMask | KeyPressMask | 394 EnterWindowMask | FocusChangeMask; 395 396 place_icon(c); 397 398 c->icon = XCreateWindow(dpy, root, c->icon_geom.x, c->icon_geom.h, 399 c->icon_geom.w, c->icon_geom.h, 0, CopyFromParent, CopyFromParent, 400 CopyFromParent, CWBackPixel | CWEventMask, &attrs); 401 set_atoms(c->icon, net_wm_wintype, XA_ATOM, &net_wm_type_desk, 1); 402 XMapWindow(dpy, c->icon); 403 404 c->icon_label = XCreateWindow(dpy, root, 0, 0, c->icon_geom.w, 405 c->icon_geom.h, 0, CopyFromParent, CopyFromParent, CopyFromParent, 406 CWBackPixel | CWEventMask, &attrs); 407 set_atoms(c->icon_label, net_wm_wintype, XA_ATOM, &net_wm_type_desk, 1); 408 XMapWindow(dpy, c->icon_label); 409 c->icon_xftdraw = XftDrawCreate(dpy, (Drawable)c->icon_label, 410 DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); 411 412 c->icon_gc = XCreateGC(dpy, c->icon, 0, &gv); 413 414 redraw_icon(c, None); 415 flush_expose_client(c); 416} 417 418void 419uniconify_client(client_t *c) 420{ 421 if (c->desk != cur_desk) { 422 c->desk = cur_desk; 423 set_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk, 1); 424 } 425 426 XMapWindow(dpy, c->win); 427 XMapRaised(dpy, c->frame); 428 c->state &= ~STATE_ICONIFIED; 429 set_wm_state(c, NormalState); 430 431 c->ignore_unmap++; 432 XDestroyWindow(dpy, c->icon); 433 c->icon = None; 434 c->ignore_unmap++; 435 if (c->icon_xftdraw) { 436 XftDrawDestroy(c->icon_xftdraw); 437 c->icon_xftdraw = None; 438 } 439 XDestroyWindow(dpy, c->icon_label); 440 c->icon_label = None; 441 442 focus_client(c, FOCUS_FORCE); 443} 444 445void 446place_icon(client_t *c) 447{ 448 strut_t s = { 0 }; 449 client_t *p; 450 int x, y, isize; 451 452 collect_struts(c, &s); 453 454 s.right = DisplayWidth(dpy, screen) - s.right; 455 s.bottom = DisplayHeight(dpy, screen) - s.bottom; 456 457 isize = icon_size * 2.25; 458 459 for (y = s.bottom - isize; y >= s.top; y -= isize) { 460 for (x = s.left + icon_size; x < s.right - isize; x += isize) { 461 int overlap = 0; 462 463 for (p = focused; p; p = p->next) { 464 if (p == c || !(p->state & STATE_ICONIFIED)) 465 continue; 466 467 if ((p->icon_geom.x + icon_size >= x && 468 p->icon_geom.x <= x) && 469 (p->icon_geom.y + icon_size >= y && 470 p->icon_geom.y <= y)) { 471 overlap = 1; 472 break; 473 } 474 } 475 476 if (overlap) 477 continue; 478 479 c->icon_geom.x = x; 480 c->icon_geom.y = y; 481#ifdef DEBUG 482 dump_geom(c, c->icon_geom, "place_icon"); 483#endif 484 return; 485 } 486 } 487 488 /* shrug */ 489 c->icon_geom.x = s.left; 490 c->icon_geom.y = s.top; 491} 492 493void 494shade_client(client_t *c) 495{ 496 if (c->state != STATE_NORMAL || (c->frame_style == FRAME_NONE)) 497 return; 498 499 c->state |= STATE_SHADED; 500 append_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_shaded, 1); 501 do_shade(c); 502} 503 504void 505unshade_client(client_t *c) 506{ 507 if (!(c->state & STATE_SHADED)) 508 return; 509 510 c->state &= ~(STATE_SHADED); 511 remove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_shaded); 512 do_shade(c); 513} 514 515static void 516do_shade(client_t *c) 517{ 518 if (c->frame) { 519 redraw_frame(c, None); 520 521 if (c->state & STATE_SHADED) { 522 XUndefineCursor(dpy, c->resize_nw); 523 XUndefineCursor(dpy, c->resize_n); 524 XUndefineCursor(dpy, c->resize_ne); 525 XUndefineCursor(dpy, c->resize_s); 526 } else { 527 XDefineCursor(dpy, c->resize_nw, resize_nw_curs); 528 XDefineCursor(dpy, c->resize_n, resize_n_curs); 529 XDefineCursor(dpy, c->resize_ne, resize_ne_curs); 530 XDefineCursor(dpy, c->resize_s, resize_s_curs); 531 } 532 } 533 send_config(c); 534 flush_expose_client(c); 535} 536 537void 538fullscreen_client(client_t *c) 539{ 540 int screen_x = DisplayWidth(dpy, screen); 541 int screen_y = DisplayHeight(dpy, screen); 542 543#ifdef DEBUG 544 dump_name(c, __func__, NULL, c->name); 545#endif 546 547 if (c->state & (STATE_FULLSCREEN | STATE_DOCK)) 548 return; 549 550 if (c->state & STATE_SHADED) 551 unshade_client(c); 552 553 c->save = c->geom; 554 c->geom.x = 0; 555 c->geom.y = 0; 556 c->geom.w = screen_x; 557 c->geom.h = screen_y; 558 c->state |= STATE_FULLSCREEN; 559 redraw_frame(c, None); 560 send_config(c); 561 flush_expose_client(c); 562} 563 564void 565unfullscreen_client(client_t *c) 566{ 567#ifdef DEBUG 568 dump_name(c, __func__, NULL, c->name); 569#endif 570 571 if (!(c->state & STATE_FULLSCREEN)) 572 return; 573 574 c->geom = c->save; 575 c->state &= ~STATE_FULLSCREEN; 576 577 recalc_frame(c); 578 redraw_frame(c, None); 579 send_config(c); 580 flush_expose_client(c); 581} 582 583/* 584 * When zooming a window, the old geom gets stuffed into c->save. Once we 585 * unzoom, this should be considered garbage. Despite the existence of vertical 586 * and horizontal hints, we only provide both at once. 587 * 588 * Zooming implies unshading, but the inverse is not true. 589 */ 590void 591zoom_client(client_t *c) 592{ 593 strut_t s = { 0 }; 594 595 if (c->state & STATE_DOCK) 596 return; 597 598 if (c->state & STATE_SHADED) 599 unshade_client(c); 600 601 c->save = c->geom; 602 c->state |= STATE_ZOOMED; 603 604 collect_struts(c, &s); 605 recalc_frame(c); 606 607 c->geom.x = s.left; 608 c->geom.y = s.top + c->titlebar_geom.h; 609 c->geom.w = DisplayWidth(dpy, screen) - s.left - s.right; 610 c->geom.h = DisplayHeight(dpy, screen) - s.top - s.bottom - c->geom.y; 611 612 append_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_mv, 1); 613 append_atoms(c->win, net_wm_state, XA_ATOM, &net_wm_state_mh, 1); 614 redraw_frame(c, None); 615 send_config(c); 616 flush_expose_client(c); 617} 618 619void 620unzoom_client(client_t *c) 621{ 622 if (!(c->state & STATE_ZOOMED)) 623 return; 624 625 c->geom = c->save; 626 c->state &= ~STATE_ZOOMED; 627 628 remove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_mv); 629 remove_atom(c->win, net_wm_state, XA_ATOM, net_wm_state_mh); 630 redraw_frame(c, None); 631 send_config(c); 632 flush_expose_client(c); 633} 634 635/* 636 * The name of this function is a little misleading: if the client doesn't 637 * listen to WM_DELETE then we just terminate it with extreme prejudice. 638 */ 639void 640send_wm_delete(client_t *c) 641{ 642 int i, n, found = 0; 643 Atom *protocols; 644 645 if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { 646 for (i = 0; i < n; i++) 647 if (protocols[i] == wm_delete) 648 found++; 649 XFree(protocols); 650 } 651 if (found) 652 send_xmessage(c->win, c->win, wm_protos, wm_delete, 653 NoEventMask); 654 else 655 XKillClient(dpy, c->win); 656} 657 658void 659goto_desk(int new_desk) 660{ 661 client_t *c, *newfocus = NULL; 662 663 if (new_desk >= ndesks || new_desk < 0) 664 return; 665 666 cur_desk = new_desk; 667 set_atoms(root, net_cur_desk, XA_CARDINAL, &cur_desk, 1); 668 669 for (c = focused; c; c = c->next) { 670 if (dragging == c) { 671 c->desk = cur_desk; 672 set_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk, 673 1); 674 } 675 676 if (IS_ON_CUR_DESK(c)) { 677 if (c->state & STATE_ICONIFIED) { 678 XMapWindow(dpy, c->icon); 679 XMapWindow(dpy, c->icon_label); 680 } else { 681 if (!newfocus && !(c->state & STATE_DOCK)) 682 newfocus = c; 683 684 XMapWindow(dpy, c->frame); 685 } 686 } else { 687 if (c->state & STATE_ICONIFIED) { 688 XUnmapWindow(dpy, c->icon); 689 XUnmapWindow(dpy, c->icon_label); 690 } else 691 XUnmapWindow(dpy, c->frame); 692 } 693 694 send_config(c); 695 } 696 697 restack_clients(); 698 699 if (newfocus) 700 focus_client(newfocus, FOCUS_FORCE); 701} 702 703void 704map_if_desk(client_t *c) 705{ 706 if (IS_ON_CUR_DESK(c) && get_wm_state(c->win) == NormalState) 707 XMapWindow(dpy, c->frame); 708 else 709 XUnmapWindow(dpy, c->frame); 710} 711 712static XEvent sweepev; 713void 714sweep(client_t *c, Cursor curs, sweep_func cb, void *cb_arg, strut_t *s) 715{ 716 geom_t orig = (c->state & STATE_ICONIFIED ? c->icon_geom : c->geom); 717 client_t *ec; 718 strut_t as = { 0 }; 719 int x0, y0, done = 0; 720 721 get_pointer(&x0, &y0); 722 collect_struts(c, &as); 723 recalc_frame(c); 724 725 if (XGrabPointer(dpy, root, False, MouseMask, GrabModeAsync, 726 GrabModeAsync, root, curs, CurrentTime) != GrabSuccess) 727 return; 728 729 cb(c, orig, x0, y0, x0, y0, s, cb_arg); 730 731 while (!done) { 732 XMaskEvent(dpy, ExposureMask | MouseMask | PointerMotionMask | 733 StructureNotifyMask | SubstructureNotifyMask | 734 KeyPressMask | KeyReleaseMask, &sweepev); 735#ifdef DEBUG 736 show_event(sweepev); 737#endif 738 switch (sweepev.type) { 739 case Expose: 740 if ((ec = find_client(sweepev.xexpose.window, 741 MATCH_FRAME))) 742 redraw_frame(ec, sweepev.xexpose.window); 743 break; 744 case MotionNotify: 745 cb(c, orig, x0, y0, sweepev.xmotion.x, 746 sweepev.xmotion.y, s, cb_arg); 747 break; 748 case ButtonRelease: 749 done = 1; 750 break; 751 case UnmapNotify: 752 if (c->win == sweepev.xunmap.window) { 753 done = 1; 754 XPutBackEvent(dpy, &sweepev); 755 break; 756 } 757 handle_unmap_event(&sweepev.xunmap); 758 break; 759 case KeyPress: 760 case KeyRelease: 761 /* to allow switching desktops while dragging */ 762 handle_key_event(&sweepev.xkey); 763 break; 764 } 765 } 766 767 XUngrabPointer(dpy, CurrentTime); 768} 769 770/* 771 * This is simple and dumb: if the cursor is in the center of the screen, 772 * center the window on the available space. If it's at the top left, then at 773 * the top left. As you go between, and to other edges, scale it. 774 */ 775void 776recalc_map(client_t *c, geom_t orig, int x0, int y0, int x1, int y1, 777 strut_t *s, void *arg) 778{ 779 int screen_x = DisplayWidth(dpy, screen); 780 int screen_y = DisplayHeight(dpy, screen); 781 int wmax = screen_x - s->left - s->right; 782 int hmax = screen_y - s->top - s->bottom; 783 784 c->geom.x = s->left + ((float) x1 / (float) screen_x) * 785 (wmax + 1 - c->geom.w - (2 * c->resize_nw_geom.w)); 786 c->geom.y = s->top + ((float) y1 / (float) screen_y) * 787 (hmax + 1 - c->geom.h - c->titlebar_geom.h - 788 (2 * c->resize_w_geom.w)); 789} 790 791void 792recalc_move(client_t *c, geom_t orig, int x0, int y0, int x1, int y1, 793 strut_t *s, void *arg) 794{ 795 int newx = orig.x + x1 - x0; 796 int newy = orig.y + y1 - y0; 797 int sw = DisplayWidth(dpy, screen); 798 int sh = DisplayHeight(dpy, screen); 799 geom_t tg; 800 801 if (c->state & STATE_ICONIFIED) { 802 int xd = newx - c->icon_geom.x; 803 int yd = newy - c->icon_geom.y; 804 805 c->icon_geom.x = newx; 806 c->icon_geom.y = newy; 807 c->icon_label_geom.x += xd; 808 c->icon_label_geom.y += yd; 809 810 XMoveWindow(dpy, c->icon, 811 c->icon_geom.x + ((icon_size - c->icon_geom.w) / 2), 812 c->icon_geom.y + ((icon_size - c->icon_geom.h) / 2)); 813 XMoveWindow(dpy, c->icon_label, c->icon_label_geom.x, 814 c->icon_label_geom.y); 815 send_config(c); 816 flush_expose_client(c); 817 return; 818 } 819 820 sw -= s->right; 821 sh -= s->bottom; 822 memcpy(&tg, &c->frame_geom, sizeof(c->frame_geom)); 823 824 /* provide some resistance at screen edges */ 825 if (x1 < x0) { 826 /* left edge */ 827 if (newx - c->resize_w_geom.w >= (long)s->left || 828 newx - c->resize_w_geom.w < (long)s->left - 829 opt_edge_resist) { 830 c->geom.x = newx; 831 } else { 832 c->geom.x = (long)s->left + c->resize_w_geom.w; 833 } 834 } else { 835 /* right edge */ 836 if (newx + c->geom.w + c->resize_e_geom.w <= sw || 837 newx + c->geom.w + c->resize_e_geom.w > sw + 838 opt_edge_resist) { 839 c->geom.x = newx; 840 } else { 841 c->geom.x = sw - c->geom.w - c->resize_e_geom.w; 842 } 843 } 844 845 if (y1 < y0) { 846 /* top edge */ 847 if (newy - c->resize_n_geom.h - c->titlebar_geom.h >= 848 (long)s->top || 849 newy - c->resize_n_geom.h - c->titlebar_geom.h < 850 (long)s->top - opt_edge_resist) { 851 c->geom.y = newy; 852 } else { 853 c->geom.y = (long)s->top + c->resize_n_geom.h + 854 c->titlebar_geom.h; 855 } 856 } else { 857 /* bottom edge */ 858 if (newy + c->geom.h + c->resize_s_geom.h <= sh || 859 newy + c->geom.h + c->resize_s_geom.h > sh + 860 opt_edge_resist) { 861 c->geom.y = newy; 862 } else { 863 c->geom.y = sh - c->geom.h - c->resize_s_geom.h; 864 } 865 } 866 867 recalc_frame(c); 868 869 if (c->frame_geom.x == tg.x && c->frame_geom.y == tg.y) 870 return; 871 872 XMoveWindow(dpy, c->frame, c->frame_geom.x, c->frame_geom.y); 873} 874 875void 876recalc_resize(client_t *c, geom_t orig, int x0, int y0, int x1, int y1, 877 strut_t *move, void *arg) 878{ 879 Window resize_pos = *(Window *)arg; 880 geom_t now = { c->geom.x, c->geom.y, c->geom.w, c->geom.h }; 881 882 if (resize_pos == c->resize_nw) 883 move->top = move->left = 1; 884 else if (resize_pos == c->resize_n) 885 move->top = 1; 886 else if (resize_pos == c->resize_ne) 887 move->top = move->right = 1; 888 else if (resize_pos == c->resize_e) 889 move->right = 1; 890 else if (resize_pos == c->resize_se) 891 move->right = move->bottom = 1; 892 else if (resize_pos == c->resize_s) 893 move->bottom = 1; 894 else if (resize_pos == c->resize_sw) 895 move->bottom = move->left = 1; 896 else if (resize_pos == c->resize_w) 897 move->left = 1; 898 899 if (move->left) 900 c->geom.w = orig.w + (x0 - x1); 901 if (move->top) 902 c->geom.h = orig.h + (y0 - y1); 903 if (move->right) { 904 c->geom.w = orig.w - (x0 - x1); 905 c->geom.x = orig.x - (x0 - x1); 906 } 907 if (move->bottom) { 908 c->geom.h = orig.h - (y0 - y1); 909 c->geom.y = orig.y - (y0 - y1); 910 } 911 912 fix_size(c); 913 914 if (move->left) 915 c->geom.x = orig.x + orig.w - c->geom.w; 916 if (move->top) 917 c->geom.y = orig.y + orig.h - c->geom.h; 918 if (move->right) 919 c->geom.x = orig.x; 920 if (move->bottom) 921 c->geom.y = orig.y; 922 923 fix_size(c); 924 925 if (c->geom.w != now.w || c->geom.h != now.h) { 926 redraw_frame(c, None); 927 if (c->shaped) 928 set_shape(c); 929 send_config(c); 930 } 931} 932 933/* 934 * If the window in question has a ResizeInc hint, then it wants to be resized 935 * in multiples of some (x,y). We constrain the values in c->geom based on that 936 * and any min/max size hints. 937 */ 938void 939fix_size(client_t *c) 940{ 941 int width_inc, height_inc; 942 int base_width, base_height; 943 944 if (c->size_hints.flags & PMinSize) { 945 if (c->geom.w < c->size_hints.min_width) 946 c->geom.w = c->size_hints.min_width; 947 if (c->geom.h < c->size_hints.min_height) 948 c->geom.h = c->size_hints.min_height; 949 } 950 if (c->size_hints.flags & PMaxSize) { 951 if (c->geom.w > c->size_hints.max_width) 952 c->geom.w = c->size_hints.max_width; 953 if (c->geom.h > c->size_hints.max_height) 954 c->geom.h = c->size_hints.max_height; 955 } 956 957 if (c->size_hints.flags & PResizeInc) { 958 width_inc = c->size_hints.width_inc ? 959 c->size_hints.width_inc : 1; 960 height_inc = c->size_hints.height_inc ? 961 c->size_hints.height_inc : 1; 962 base_width = (c->size_hints.flags & PBaseSize) ? 963 c->size_hints.base_width : 964 ((c->size_hints.flags & PMinSize) ? 965 c->size_hints.min_width : 0); 966 base_height = (c->size_hints.flags & PBaseSize) ? 967 c->size_hints.base_height : 968 (c->size_hints.flags & PMinSize) ? 969 c->size_hints.min_height : 0; 970 c->geom.w -= (c->geom.w - base_width) % width_inc; 971 c->geom.h -= (c->geom.h - base_height) % height_inc; 972 } 973} 974 975/* make sure a frame fits on the screen */ 976void 977constrain_frame(client_t *c) 978{ 979 strut_t s = { 0 }; 980 int h, w, delta; 981 982 if (c->state & STATE_FULLSCREEN) 983 return; 984 985#ifdef DEBUG 986 dump_geom(c, c->geom, "constrain_frame initial"); 987#endif 988 989 recalc_frame(c); 990 fix_size(c); 991 992 collect_struts(c, &s); 993 994 if (c->frame_geom.x < s.left) { 995 delta = s.left - c->frame_geom.x; 996 c->frame_geom.x += delta; 997 c->geom.x += delta; 998 } 999 if (c->frame_geom.y < s.top) { 1000 delta = s.top - c->frame_geom.y; 1001 c->frame_geom.y += delta; 1002 c->geom.y += delta; 1003 } 1004 1005 h = DisplayHeight(dpy, screen) - s.top - s.bottom; 1006 if (c->frame_geom.y + c->frame_geom.h > h) { 1007 delta = c->frame_geom.y + c->frame_geom.h - h; 1008 c->frame_geom.h -= delta; 1009 c->geom.h -= delta; 1010 } 1011 1012 w = DisplayWidth(dpy, screen) - s.left - s.right; 1013 if (c->frame_geom.x + c->frame_geom.w > w) { 1014 delta = c->frame_geom.x + c->frame_geom.w - w; 1015 c->frame_geom.w -= delta; 1016 c->geom.w -= delta; 1017 } 1018 1019 /* TODO: fix_size() again but don't allow enlarging */ 1020 1021 recalc_frame(c); 1022 1023#ifdef DEBUG 1024 dump_geom(c, c->geom, "constrain_frame final"); 1025#endif 1026} 1027 1028void 1029flush_expose_client(client_t *c) 1030{ 1031 if (c->resize_nw) 1032 flush_expose(c->resize_nw); 1033 if (c->resize_n) 1034 flush_expose(c->resize_n); 1035 if (c->resize_ne) 1036 flush_expose(c->resize_ne); 1037 if (c->resize_e) 1038 flush_expose(c->resize_e); 1039 if (c->resize_se) 1040 flush_expose(c->resize_se); 1041 if (c->resize_s) 1042 flush_expose(c->resize_s); 1043 if (c->resize_sw) 1044 flush_expose(c->resize_sw); 1045 if (c->resize_w) 1046 flush_expose(c->resize_w); 1047 if (c->close) 1048 flush_expose(c->close); 1049 if (c->iconify) 1050 flush_expose(c->iconify); 1051 if (c->zoom) 1052 flush_expose(c->zoom); 1053 if (c->titlebar) 1054 flush_expose(c->titlebar); 1055 if (c->icon) 1056 flush_expose(c->icon); 1057 if (c->icon_label) 1058 flush_expose(c->icon_label); 1059} 1060 1061/* remove expose events for a window from the event queue */ 1062void 1063flush_expose(Window win) 1064{ 1065 XEvent junk; 1066 while (XCheckTypedWindowEvent(dpy, win, Expose, &junk)) 1067 ; 1068} 1069 1070int 1071overlapping_geom(geom_t a, geom_t b) 1072{ 1073 if (a.x <= b.x + b.w && a.x + a.w >= b.x && 1074 a.y <= b.y + b.h && a.y + a.h >= b.y) 1075 return 1; 1076 1077 return 0; 1078} 1079 1080void 1081restack_clients(void) 1082{ 1083 Window *wins = NULL; 1084 client_t *p; 1085 int twins = 0, nwins = 0; 1086 1087 /* restack windows - ABOVE, normal, BELOW, ICONIFIED */ 1088 for (p = focused, twins = 0; p; p = p->next) 1089 twins += 2; 1090 1091 if (twins == 0) 1092 return; 1093 1094 wins = realloc(wins, twins * sizeof(Window)); 1095 if (wins == NULL) 1096 err(1, "realloc"); 1097 1098 /* STATE_ABOVE first */ 1099 for (p = focused; p; p = p->next) { 1100 if (!IS_ON_CUR_DESK(p)) 1101 continue; 1102 1103 if ((p->state & STATE_ABOVE) && !(p->state & STATE_ICONIFIED)) 1104 wins[nwins++] = p->frame; 1105 } 1106 1107 /* then non-iconified windows */ 1108 for (p = focused; p; p = p->next) { 1109 if (!IS_ON_CUR_DESK(p)) 1110 continue; 1111 1112 if (!(p->state & (STATE_ICONIFIED | STATE_BELOW | STATE_ABOVE | 1113 STATE_DOCK))) 1114 wins[nwins++] = p->frame; 1115 } 1116 1117 /* then BELOW windows */ 1118 for (p = focused; p; p = p->next) { 1119 if (!IS_ON_CUR_DESK(p)) 1120 continue; 1121 1122 if (p->state & (STATE_BELOW | STATE_DOCK)) 1123 wins[nwins++] = p->frame; 1124 } 1125 1126 /* then icons, taking from all desks */ 1127 for (p = focused; p; p = p->next) { 1128 if (p->state & STATE_ICONIFIED) { 1129 wins[nwins++] = p->icon; 1130 wins[nwins++] = p->icon_label; 1131 } 1132 } 1133 1134 if (nwins > twins) { 1135 warnx("%s allocated for %d windows, used %d", __func__, 1136 twins, nwins); 1137 abort(); 1138 } 1139 1140 XRestackWindows(dpy, wins, nwins); 1141 1142 free(wins); 1143 1144 /* TODO: update net_client_stack */ 1145} 1146 1147void 1148adjust_client_order(client_t *c, int where) 1149{ 1150 client_t *p, *pp; 1151 1152 if (c != NULL && focused == c && !c->next) 1153 return; 1154 1155 switch (where) { 1156 case ORDER_TOP: 1157 for (p = focused; p && p->next; p = p->next) { 1158 if (p->next == c) { 1159 p->next = c->next; 1160 break; 1161 } 1162 } 1163 1164 if (c != focused) { 1165 c->next = focused; 1166 focused = c; 1167 } 1168 break; 1169 case ORDER_ICONIFIED_TOP: 1170 /* remove first */ 1171 if (focused == c) 1172 focused = c->next; 1173 else { 1174 for (p = focused; p && p->next; p = p->next) 1175 if (p->next == c) { 1176 p->next = c->next; 1177 break; 1178 } 1179 } 1180 c->next = NULL; 1181 1182 p = focused; pp = NULL; 1183 while (p && p->next) { 1184 if (!(p->state & STATE_ICONIFIED)) { 1185 pp = p; 1186 p = p->next; 1187 continue; 1188 } 1189 1190 if (pp) 1191 /* place ahead of this first iconfied client */ 1192 pp->next = c; 1193 else 1194 /* no previous non-iconified clients */ 1195 focused = c; 1196 1197 if (c != p) 1198 c->next = p; 1199 break; 1200 } 1201 1202 if (!c->next) { 1203 /* no iconified clients, place at the bottom */ 1204 if (p) 1205 p->next = c; 1206 else 1207 focused = c; 1208 c->next = NULL; 1209 } 1210 break; 1211 case ORDER_BOTTOM: 1212 for (p = focused; p && p->next; p = p->next) { 1213 if (p->next == c) 1214 p->next = c->next; 1215 /* continue until p->next == NULL */ 1216 } 1217 1218 if (p) 1219 p->next = c; 1220 else 1221 focused = c; 1222 1223 c->next = NULL; 1224 break; 1225 case ORDER_OUT: 1226 if (c == focused) 1227 focused = c->next; 1228 else { 1229 for (p = focused; p && p->next; p = p->next) { 1230 if (p->next == c) { 1231 p->next = c->next; 1232 break; 1233 } 1234 } 1235 } 1236 break; 1237 case ORDER_INVERT: { 1238 client_t *pp, *n, *tail; 1239 1240 for (tail = focused; tail && tail->next; tail = tail->next) 1241 ; 1242 1243 for (p = focused, pp = NULL, n = tail; n; pp = p, p = n) { 1244 n = p->next; 1245 p->next = pp; 1246 } 1247 focused = tail; 1248 1249 break; 1250 } 1251 default: 1252 printf("unknown client sort option %d\n", where); 1253 } 1254} 1255 1256client_t * 1257next_client_for_focus(client_t *head) 1258{ 1259 client_t *n; 1260 1261 for (n = head->next; n; n = n->next) 1262 if (IS_ON_CUR_DESK(n) && !(n->state & STATE_DOCK)) 1263 return n; 1264 1265 return NULL; 1266} 1267 1268#ifdef DEBUG 1269char * 1270state_name(client_t *c) 1271{ 1272 int s = 30; 1273 char *res; 1274 1275 res = malloc(s); 1276 if (res == NULL) 1277 err(1, "malloc"); 1278 1279 res[0] = '\0'; 1280 1281 if (c->state == STATE_NORMAL) 1282 strlcat(res, "normal", s); 1283 if (c->state & STATE_ZOOMED) 1284 strlcat(res, "| zoomed", s); 1285 if (c->state & STATE_ICONIFIED) 1286 strlcat(res, "| iconified", s); 1287 if (c->state & STATE_SHADED) 1288 strlcat(res, "| shaded", s); 1289 if (c->state & STATE_FULLSCREEN) 1290 strlcat(res, "| fs", s); 1291 if (c->state & STATE_DOCK) 1292 strlcat(res, "| dock", s); 1293 1294 if (res[0] == '|') 1295 res = strdup(res + 2); 1296 1297 return res; 1298} 1299 1300const char * 1301frame_name(client_t *c, Window w) 1302{ 1303 if (w == None) 1304 return ""; 1305 if (w == c->frame) 1306 return "frame"; 1307 if (w == c->resize_nw) 1308 return "resize_nw"; 1309 if (w == c->resize_w) 1310 return "resize_w"; 1311 if (w == c->resize_sw) 1312 return "resize_sw"; 1313 if (w == c->resize_s) 1314 return "resize_s"; 1315 if (w == c->resize_se) 1316 return "resize_se"; 1317 if (w == c->resize_e) 1318 return "resize_e"; 1319 if (w == c->resize_ne) 1320 return "resize_ne"; 1321 if (w == c->resize_n) 1322 return "resize_n"; 1323 if (w == c->titlebar) 1324 return "titlebar"; 1325 if (w == c->close) 1326 return "close"; 1327 if (w == c->iconify) 1328 return "iconify"; 1329 if (w == c->zoom) 1330 return "zoom"; 1331 if (w == c->icon) 1332 return "icon"; 1333 if (w == c->icon_label) 1334 return "icon_label"; 1335 return "unknown"; 1336} 1337 1338static const char * 1339show_grav(client_t *c) 1340{ 1341 if (!(c->size_hints.flags & PWinGravity)) 1342 return "no grav (NW)"; 1343 1344 switch (c->size_hints.win_gravity) { 1345 SHOW(UnmapGravity) 1346 SHOW(NorthWestGravity) 1347 SHOW(NorthGravity) 1348 SHOW(NorthEastGravity) 1349 SHOW(WestGravity) 1350 SHOW(CenterGravity) 1351 SHOW(EastGravity) 1352 SHOW(SouthWestGravity) 1353 SHOW(SouthGravity) 1354 SHOW(SouthEastGravity) 1355 SHOW(StaticGravity) 1356 default: 1357 return "unknown grav"; 1358 } 1359} 1360 1361void 1362dump_name(client_t *c, const char *label, const char *detail, const char *name) 1363{ 1364 printf("%18.18s: %#010lx [%-9.9s] %-35.35s\n", label, 1365 c ? c->win : 0, detail == NULL ? "" : detail, 1366 name == NULL ? "" : name); 1367} 1368 1369void 1370dump_info(client_t *c) 1371{ 1372 char *s = state_name(c); 1373 1374 printf("%31s[i] ignore_unmap %d, trans 0x%lx, focus %d, shape %d\n", "", 1375 c->ignore_unmap, c->trans, focused == c ? 1 : 0, c->shaped ? 1 : 0); 1376 printf("%31s[i] desk %ld, state %s, %s\n", "", 1377 c->desk, s, show_grav(c)); 1378 1379 free(s); 1380} 1381 1382void 1383dump_geom(client_t *c, geom_t g, const char *label) 1384{ 1385 printf("%31s[g] %s %ldx%ld+%ld+%ld\n", "", 1386 label, g.w, g.h, g.x, g.y); 1387} 1388 1389void 1390dump_removal(client_t *c, int mode) 1391{ 1392 printf("%31s[r] %s, %d pending\n", "", 1393 mode == DEL_WITHDRAW ? "withdraw" : "remap", XPending(dpy)); 1394} 1395 1396void 1397dump_clients(void) 1398{ 1399 client_t *c; 1400 1401 for (c = focused; c; c = c->next) { 1402 dump_name(c, __func__, NULL, c->name); 1403 dump_geom(c, c->geom, "current"); 1404 dump_info(c); 1405 } 1406} 1407#endif