progman.exe^H^H^H^H
at master 1855 lines 53 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 <stdlib.h> 24#ifdef DEBUG 25#include <stdio.h> 26#endif 27#include <string.h> 28#include <err.h> 29#include <X11/Xatom.h> 30#include <X11/extensions/shape.h> 31#ifdef USE_GDK_PIXBUF 32#include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h> 33#endif 34#include "progman.h" 35#include "atom.h" 36 37static void init_geom(client_t *, strut_t *); 38static void reparent(client_t *, strut_t *); 39static void bevel(Window, geom_t, int); 40static void *word_wrap_xft(char *, char, XftFont *, int, int *); 41 42/* 43 * Set up a client structure for the new (not-yet-mapped) window. We have to 44 * ignore two unmap events if the client was already mapped but has IconicState 45 * set (for instance, when we are the second window manager in a session). 46 * That's because there's one for the reparent (which happens on all viewable 47 * windows) and then another for the unmapping itself. 48 */ 49client_t * 50new_client(Window w) 51{ 52 client_t *c; 53 XWindowAttributes attr; 54 55 c = malloc(sizeof *c); 56 memset(c, 0, sizeof(*c)); 57 58 c->name = get_wm_name(w); 59 c->icon_name = get_wm_icon_name(w); 60 c->win = w; 61 62 update_size_hints(c); 63 XGetTransientForHint(dpy, c->win, &c->trans); 64 65 ignore_xerrors++; 66 XGetWindowAttributes(dpy, c->win, &attr); 67 ignore_xerrors--; 68 c->geom.x = attr.x; 69 c->geom.y = attr.y; 70 c->geom.w = attr.width; 71 c->geom.h = attr.height; 72 c->cmap = attr.colormap; 73 c->old_bw = attr.border_width; 74 75 if (get_atoms(c->win, net_wm_desk, XA_CARDINAL, 0, &c->desk, 1, NULL)) { 76 if (c->desk == -1) 77 c->desk = DESK_ALL; /* FIXME */ 78 if (c->desk >= ndesks && c->desk != DESK_ALL) 79 c->desk = cur_desk; 80 } else { 81 set_atoms(c->win, net_wm_desk, XA_CARDINAL, &cur_desk, 1); 82 c->desk = cur_desk; 83 } 84 85 /* 86 * We are not actually keeping the stack one in order. However, every 87 * fancy panel uses it and nothing else, no matter what the spec says. 88 * (I'm not sure why, as rearranging the list every time the stacking 89 * changes would be distracting. GNOME's window list applet doesn't.) 90 */ 91 append_atoms(root, net_client_list, XA_WINDOW, &c->win, 1); 92 append_atoms(root, net_client_stack, XA_WINDOW, &c->win, 1); 93 94 if (opt_drag_button) 95 /* setup for mod+click dragging */ 96 XGrabButton(dpy, opt_drag_button, opt_drag_mod, c->win, True, 97 ButtonPressMask | ButtonReleaseMask, GrabModeAsync, 98 GrabModeAsync, None, move_curs); 99 100 check_states(c); 101 102 if (c->wm_hints) 103 XFree(c->wm_hints); 104 c->wm_hints = XGetWMHints(dpy, c->win); 105 if (c->wm_hints && (c->wm_hints->flags & StateHint) && 106 c->wm_hints->initial_state == IconicState) 107 c->state = STATE_ICONIFIED; 108 109#ifdef DEBUG 110 dump_name(c, __func__, "", c->name); 111 dump_geom(c, c->geom, "XGetWindowAttributes"); 112 dump_info(c); 113#endif 114 115 return c; 116} 117 118client_t * 119find_client(Window w, int mode) 120{ 121 client_t *c; 122 123 for (c = focused; c; c = c->next) { 124 switch (mode) { 125 case MATCH_ANY: 126 if (w == c->frame || w == c->win || w == c->resize_nw || 127 w == c->resize_w || w == c->resize_sw || 128 w == c->resize_s || w == c->resize_se || 129 w == c->resize_e || w == c->resize_ne || 130 w == c->resize_n || w == c->titlebar || 131 w == c->close || w == c->iconify || w == c->zoom || 132 w == c->icon || w == c->icon_label) 133 return c; 134 break; 135 case MATCH_FRAME: 136 if (w == c->frame || w == c->resize_nw || 137 w == c->resize_w || w == c->resize_sw || 138 w == c->resize_s || w == c->resize_se || 139 w == c->resize_e || w == c->resize_ne || 140 w == c->resize_n || w == c->titlebar || 141 w == c->close || w == c->iconify || w == c->zoom || 142 w == c->icon || w == c->icon_label) 143 return c; 144 break; 145 case MATCH_WINDOW: 146 if (w == c->win) 147 return c; 148 } 149 } 150 151 return NULL; 152} 153 154client_t * 155find_client_at_coords(Window w, int x, int y) 156{ 157 unsigned int nwins, i; 158 Window qroot, qparent, *wins; 159 XWindowAttributes attr; 160 client_t *c, *foundc = NULL; 161 162 XQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins); 163 for (i = nwins - 1; i > 0; i--) { 164 ignore_xerrors++; 165 XGetWindowAttributes(dpy, wins[i], &attr); 166 ignore_xerrors--; 167 if (!(c = find_client(wins[i], MATCH_ANY))) 168 continue; 169 170 if (c->state & STATE_ICONIFIED) { 171 if (x >= c->icon_geom.x && 172 x <= c->icon_geom.x + c->icon_geom.w && 173 y >= c->icon_geom.y && 174 y <= c->icon_geom.y + c->icon_geom.h) { 175 foundc = c; 176 break; 177 } 178 if (x >= c->icon_label_geom.x && 179 x <= c->icon_label_geom.x + c->icon_label_geom.w && 180 y >= c->icon_label_geom.y && 181 y <= c->icon_label_geom.y + c->icon_label_geom.h) { 182 foundc = c; 183 break; 184 } 185 } else { 186 if (x >= c->frame_geom.x && 187 x <= c->frame_geom.x + c->frame_geom.w && 188 y >= c->frame_geom.y && 189 y <= c->frame_geom.y + c->frame_geom.h) { 190 foundc = c; 191 break; 192 } 193 } 194 } 195 XFree(wins); 196 197 return foundc; 198} 199 200client_t * 201top_client(void) 202{ 203 unsigned int nwins, i; 204 Window qroot, qparent, *wins; 205 XWindowAttributes attr; 206 client_t *c, *foundc = NULL; 207 208 XQueryTree(dpy, root, &qroot, &qparent, &wins, &nwins); 209 for (i = nwins - 1; i > 0; i--) { 210 ignore_xerrors++; 211 XGetWindowAttributes(dpy, wins[i], &attr); 212 ignore_xerrors--; 213 if ((c = find_client(wins[i], MATCH_FRAME)) && 214 !(c->state & STATE_ICONIFIED)) { 215 foundc = c; 216 break; 217 } 218 } 219 XFree(wins); 220 221 return foundc; 222} 223 224void 225map_client(client_t *c) 226{ 227 strut_t s = { 0 }; 228 int want_raise = 0; 229 230 XGrabServer(dpy); 231 232 collect_struts(c, &s); 233 init_geom(c, &s); 234 235 /* this also builds (but does not map) the frame windows */ 236 reparent(c, &s); 237 238 constrain_frame(c); 239 240 if (shape_support) 241 set_shape(c); 242 243 if (c->state & STATE_ICONIFIED) { 244 c->ignore_unmap++; 245 set_wm_state(c, IconicState); 246 XUnmapWindow(dpy, c->win); 247 iconify_client(c); 248 } else { 249 /* we're not allowing WithdrawnState */ 250 set_wm_state(c, NormalState); 251 if (!((c->state & STATE_DOCK) || 252 has_win_type(c, net_wm_type_notif))) 253 want_raise = 1; 254 } 255 256 if (c->name) 257 XFree(c->name); 258 c->name = get_wm_name(c->win); 259 260 if (c->icon_name) 261 XFree(c->icon_name); 262 c->icon_name = get_wm_icon_name(c->win); 263 264 if (c->state & STATE_ICONIFIED) { 265 XResizeWindow(dpy, c->win, c->geom.w, c->geom.h); 266 send_config(c); 267 adjust_client_order(c, ORDER_ICONIFIED_TOP); 268 } else { 269 /* we haven't drawn anything yet, setup at the right place */ 270 recalc_frame(c); 271 XMoveResizeWindow(dpy, c->frame, 272 c->frame_geom.x, c->frame_geom.y, 273 c->frame_geom.w, c->frame_geom.h); 274 XMoveResizeWindow(dpy, c->win, 275 c->geom.x - c->frame_geom.x, c->geom.y - c->frame_geom.y, 276 c->geom.w, c->geom.h); 277 278 if (want_raise) { 279 XMapWindow(dpy, c->frame); 280 XMapWindow(dpy, c->win); 281 focus_client(c, FOCUS_FORCE); 282 } else { 283 XMapWindow(dpy, c->frame); 284 XMapWindow(dpy, c->win); 285 adjust_client_order(c, ORDER_BOTTOM); 286 redraw_frame(c, None); 287 } 288 289 send_config(c); 290 flush_expose_client(c); 291 } 292 293 XSync(dpy, False); 294 XUngrabServer(dpy); 295} 296 297void 298update_size_hints(client_t *c) 299{ 300 long supplied; 301 302 XGetWMNormalHints(dpy, c->win, &c->size_hints, &supplied); 303 304 /* Discard bogus hints */ 305 if ((c->size_hints.flags & PAspect) && 306 (c->size_hints.min_aspect.x < 1 || c->size_hints.min_aspect.y < 1 || 307 c->size_hints.max_aspect.x < 1 || c->size_hints.max_aspect.y < 1)) 308 c->size_hints.flags &= ~PAspect; 309 if ((c->size_hints.flags & PMaxSize) && c->size_hints.max_width < 1) 310 c->size_hints.flags &= ~PMaxSize; 311 if ((c->size_hints.flags & PMinSize) && c->size_hints.min_width < 1) 312 c->size_hints.flags &= ~PMinSize; 313 if ((c->size_hints.flags & PResizeInc) && 314 (c->size_hints.width_inc < 1 || c->size_hints.height_inc < 1)) 315 c->size_hints.flags &= ~PResizeInc; 316 if ((c->size_hints.flags & (USSize | PSize)) && 317 (c->size_hints.width < 1 || c->size_hints.height < 1)) 318 c->size_hints.flags &= ~(USSize|PSize); 319} 320 321/* 322 * When we're ready to map, we have two things to consider: the literal 323 * geometry of the window (what the client passed to XCreateWindow), and the 324 * size hints (what they set with XSetWMSizeHints, if anything). Generally, the 325 * client doesn't care, and leaves the literal geometry at +0+0. If the client 326 * wants to be mapped in a particular place, though, they either set this 327 * geometry to something different or set a size hint. The size hint is the 328 * recommended method, and takes precedence. If there is already something in 329 * c->geom, though, we just leave it. 330 */ 331static void 332init_geom(client_t *c, strut_t *s) 333{ 334#ifdef DEBUG 335 geom_t size_flags = { 0 }; 336#endif 337 unsigned long win_type, read, left; 338 int screen_x = DisplayWidth(dpy, screen); 339 int screen_y = DisplayHeight(dpy, screen); 340 int wmax = screen_x - s->left - s->right; 341 int hmax = screen_y - s->top - s->bottom; 342 int mouse_x, mouse_y; 343 int i; 344 345 if (c->state & (STATE_ZOOMED | STATE_FULLSCREEN)) { 346 /* 347 * For zoomed windows, we'll adjust later to accommodate the 348 * titlebar. 349 */ 350 c->geom.x = s->top; 351 c->geom.y = s->left; 352 c->geom.w = wmax; 353 c->geom.h = hmax; 354#ifdef DEBUG 355 dump_geom(c, c->geom, "init_geom zoom/fs"); 356#endif 357 return; 358 } 359 360 /* 361 * If size/position hints are zero but the initial XGetWindowAttributes 362 * reported non-zero, ignore these hint values 363 */ 364 if (c->size_hints.width == 0 && c->geom.w != 0 && 365 c->size_hints.height == 0 && c->geom.h != 0) 366 c->size_hints.flags &= ~(USSize|PSize); 367 if (c->size_hints.x == 0 && c->geom.x != 0 && 368 c->size_hints.y == 0 && c->geom.y != 0) 369 c->size_hints.flags &= ~(USPosition|PPosition); 370 371 /* 372 * Here, we merely set the values; they're in the same place regardless 373 * of whether the user or the program specified them. We'll distinguish 374 * between the two cases later, if we need to. 375 */ 376 if (c->size_hints.flags & (USSize|PSize)) { 377 if (c->size_hints.width >= 0) 378 c->geom.w = c->size_hints.width; 379 if (c->size_hints.height > 0) 380 c->geom.h = c->size_hints.height; 381 382#ifdef DEBUG 383 size_flags.w = c->size_hints.width; 384 size_flags.h = c->size_hints.height; 385 dump_geom(c, c->geom, "init_geom size_hints w/h"); 386#endif 387 } 388 389 if (c->size_hints.flags & (USPosition | PPosition)) { 390 if (c->size_hints.x >= 0) 391 c->geom.x = c->size_hints.x; 392 if (c->size_hints.y >= 0) 393 c->geom.y = c->size_hints.y; 394#ifdef DEBUG 395 size_flags.x = c->size_hints.x; 396 size_flags.y = c->size_hints.y; 397 dump_geom(c, c->geom, "init_geom size_hints x/y"); 398#endif 399 } 400 401#ifdef DEBUG 402 if (c->size_hints.flags & (USSize | PSize | USPosition | PPosition)) 403 dump_geom(c, size_flags, "init_geom size flags"); 404#endif 405 406 /* 407 * Several types of windows can put themselves wherever they want, but 408 * we need to read the size hints to get that position before 409 * returning. 410 */ 411 for (i = 0, left = 1; left; i += read) { 412 read = get_atoms(c->win, net_wm_wintype, XA_ATOM, i, &win_type, 413 1, &left); 414 if (!read) 415 break; 416 if (CAN_PLACE_SELF(win_type)) 417 return; 418 } 419 420 if (!c->placed) { 421 if (c->geom.x <= 0 && c->geom.y <= 0) { 422 /* Place the window near the cursor */ 423 get_pointer(&mouse_x, &mouse_y); 424 recalc_map(c, c->geom, mouse_x, mouse_y, mouse_x, 425 mouse_y, s, NULL); 426 } else { 427 /* 428 * Place the window's frame where the window requested 429 * to be 430 */ 431 recalc_frame(c); 432 c->geom.x += c->border_width; 433 c->geom.y += c->border_width + c->titlebar_geom.h; 434 } 435 } 436 437 /* 438 * In any case, if we got this far, we need to do a further sanity 439 * check and make sure that the window isn't overlapping any struts -- 440 * except for transients, because they might be a panel-type client 441 * popping up a notification window over themselves. 442 */ 443 if (c->geom.x + c->geom.w > screen_x - s->right) 444 c->geom.x = screen_x - s->right - c->geom.w; 445 if (c->geom.y + c->geom.h > screen_y - s->bottom) 446 c->geom.y = screen_y - s->bottom - c->geom.h; 447 if (c->geom.x < s->left || c->geom.w > wmax) 448 c->geom.x = s->left; 449 if (c->geom.y < s->top || c->geom.h > hmax) 450 c->geom.y = s->top; 451 452 recalc_frame(c); 453 454 /* only move already-placed windows if they're off-screen */ 455 if (c->placed && 456 (c->frame_geom.x < s->left || c->geom.y <= s->top)) { 457 c->geom.x += (c->geom.x - c->frame_geom.x); 458 c->geom.y += (c->geom.y - c->frame_geom.y); 459 recalc_frame(c); 460 } 461 462#ifdef DEBUG 463 dump_geom(c, c->geom, __func__); 464#endif 465} 466 467/* 468 * The frame window is not created until we actually do the reparenting here, 469 * and thus the Xft surface cannot exist until this runs. Anything that has to 470 * manipulate the client before we are called must make sure not to attempt to 471 * use either. 472 */ 473static void 474reparent(client_t *c, strut_t *s) 475{ 476 XSetWindowAttributes pattr; 477 478 recalc_frame(c); 479 480 pattr.override_redirect = True; 481 pattr.background_pixel = border_bg.pixel; 482 pattr.event_mask = SubMask | ButtonPressMask | ButtonReleaseMask | 483 ExposureMask | EnterWindowMask; 484 c->frame = XCreateWindow(dpy, root, 485 c->frame_geom.x, c->frame_geom.y, 486 c->frame_geom.w, c->frame_geom.h, 487 0, 488 DefaultDepth(dpy, screen), CopyFromParent, 489 DefaultVisual(dpy, screen), 490 CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr); 491 492 /* 493 * Init all windows to 1x1+1+1 because a width/height of 0 causes a 494 * BadValue error. redraw_frame moves them to the right size and 495 * position anyway, and some of these never even get mapped/shown. 496 */ 497 498#define _(x,y,z) x##y##z 499#define CREATE_RESIZE_WIN(DIR) \ 500 pattr.background_pixel = BlackPixel(dpy, screen); \ 501 pattr.cursor = _(resize_,DIR,_curs); \ 502 _(c->resize_,DIR,) = XCreateWindow(dpy, c->frame, 1, 1, 1, 1, \ 503 0, CopyFromParent, InputOutput, CopyFromParent, \ 504 CWOverrideRedirect | CWBackPixel | CWEventMask | CWCursor, \ 505 &pattr); \ 506 XReparentWindow(dpy, _(c->resize_,DIR,), c->frame, \ 507 _(c->resize_,DIR,_geom.x), _(c->resize_,DIR,_geom.y)); 508 509 CREATE_RESIZE_WIN(nw); 510 CREATE_RESIZE_WIN(n); 511 CREATE_RESIZE_WIN(ne); 512 CREATE_RESIZE_WIN(e); 513 CREATE_RESIZE_WIN(se); 514 CREATE_RESIZE_WIN(s); 515 CREATE_RESIZE_WIN(sw); 516 CREATE_RESIZE_WIN(w); 517#undef _ 518#undef CREATE_RESIZE_WIN 519 520 /* no CWCursor for these */ 521 c->close = XCreateWindow(dpy, c->frame, 1, 1, 1, 1, 522 0, CopyFromParent, InputOutput, CopyFromParent, 523 CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr); 524 XReparentWindow(dpy, c->close, c->frame, c->close_geom.x, 525 c->close_geom.y); 526 527 c->titlebar = XCreateWindow(dpy, c->frame, 1, 1, 1, 1, 528 0, CopyFromParent, InputOutput, CopyFromParent, 529 CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr); 530 XReparentWindow(dpy, c->titlebar, c->frame, c->titlebar_geom.x, 531 c->titlebar_geom.y); 532 533 c->iconify = XCreateWindow(dpy, c->frame, 1, 1, 1, 1, 534 0, CopyFromParent, InputOutput, CopyFromParent, 535 CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr); 536 XReparentWindow(dpy, c->iconify, c->frame, c->iconify_geom.x, 537 c->iconify_geom.y); 538 539 c->zoom = XCreateWindow(dpy, c->frame, 1, 1, 1, 1, 540 0, CopyFromParent, InputOutput, CopyFromParent, 541 CWOverrideRedirect | CWBackPixel | CWEventMask, &pattr); 542 XReparentWindow(dpy, c->zoom, c->frame, c->zoom_geom.x, 543 c->zoom_geom.y); 544 545 c->xftdraw = XftDrawCreate(dpy, (Drawable)c->titlebar, 546 DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); 547 548 if (shape_support) 549 XShapeSelectInput(dpy, c->win, ShapeNotifyMask); 550 551 XAddToSaveSet(dpy, c->win); 552 XSelectInput(dpy, c->win, ColormapChangeMask | PropertyChangeMask); 553 XSetWindowBorderWidth(dpy, c->win, 0); 554 XReparentWindow(dpy, c->win, c->frame, c->resize_w_geom.w, 555 c->titlebar_geom.y + c->titlebar_geom.h + 1); 556} 557 558int 559has_win_type(client_t *c, Atom type) 560{ 561 int i; 562 563 for (i = 0; i < (sizeof(c->win_type) / sizeof(c->win_type[0])); i++) { 564 if (c->win_type[i] == type) 565 return 1; 566 } 567 568 return 0; 569} 570 571void 572recalc_frame(client_t *c) 573{ 574 int buts = font->ascent + font->descent + (2 * opt_pad) + 2; 575 576 if (buts < close_pm_attrs.width) 577 buts = close_pm_attrs.width; 578 579 if (has_win_type(c, net_wm_type_dock) || 580 has_win_type(c, net_wm_type_menu) || 581 has_win_type(c, net_wm_type_splash) || 582 has_win_type(c, net_wm_type_desk) || 583 has_win_type(c, kde_net_wm_window_type_override)) 584 c->frame_style = FRAME_NONE; 585 else if (has_win_type(c, net_wm_type_notif)) 586 c->frame_style = FRAME_BORDER; 587 else if (has_win_type(c, net_wm_type_utility)) 588 c->frame_style = (FRAME_BORDER | FRAME_RESIZABLE | 589 FRAME_CLOSE | FRAME_TITLEBAR); 590 else if (c->state & (STATE_DOCK | STATE_FULLSCREEN)) 591 c->frame_style = FRAME_NONE; 592 else if (c->state & STATE_ZOOMED) 593 c->frame_style = FRAME_ALL & ~(FRAME_BORDER | FRAME_RESIZABLE); 594 else 595 c->frame_style = FRAME_ALL; 596 597 if ((c->size_hints.flags & PMinSize) && 598 (c->size_hints.flags & PMaxSize) && 599 c->size_hints.min_width == c->size_hints.max_width && 600 c->size_hints.min_height == c->size_hints.max_height) 601 c->frame_style &= ~(FRAME_RESIZABLE | FRAME_ZOOM | 602 FRAME_ICONIFY); 603 604 if (c->frame_style & FRAME_BORDER) 605 c->border_width = opt_bw + 2; 606 else 607 c->border_width = 0; 608 609 if (has_win_type(c, net_wm_type_utility)) { 610 /* use tiny titlebar with no window title */ 611 buts = (2 * opt_pad) + 2; 612 if (buts < utility_close_pm_attrs.width) 613 buts = utility_close_pm_attrs.width; 614 if (c->frame_style & FRAME_RESIZABLE) 615 c->border_width = (opt_bw / 2) + 2; 616 } 617 618 if (c->frame_style & FRAME_RESIZABLE) { 619 c->resize_nw_geom.x = 0; 620 c->resize_nw_geom.y = 0; 621 c->resize_nw_geom.w = c->border_width + buts; 622 c->resize_nw_geom.h = c->border_width + buts; 623 } else 624 memset(&c->resize_nw_geom, 0, sizeof(geom_t)); 625 626 if (c->frame_style & FRAME_CLOSE) { 627 c->close_geom.x = c->border_width - 1; 628 c->close_geom.y = c->border_width - 1; 629 c->close_geom.w = buts + 1; 630 c->close_geom.h = buts + 1; 631 if (!(c->frame_style & FRAME_RESIZABLE)) { 632 c->close_geom.x++; 633 c->close_geom.y++; 634 c->close_geom.h--; 635 c->close_geom.w--; 636 } 637 } else 638 memset(&c->close_geom, 0, sizeof(geom_t)); 639 640 if (c->frame_style & FRAME_RESIZABLE) { 641 c->resize_n_geom.x = c->border_width + buts; 642 c->resize_n_geom.y = 0; 643 c->resize_n_geom.w = c->geom.w - buts - buts; 644 c->resize_n_geom.h = c->border_width; 645 } else 646 memset(&c->resize_n_geom, 0, sizeof(geom_t)); 647 648 if (c->frame_style & FRAME_RESIZABLE) { 649 c->resize_ne_geom.x = c->border_width + c->geom.w - buts; 650 c->resize_ne_geom.y = 0; 651 c->resize_ne_geom.w = c->border_width + buts; 652 c->resize_ne_geom.h = c->border_width + buts; 653 } else 654 memset(&c->resize_ne_geom, 0, sizeof(geom_t)); 655 656 if (c->frame_style & FRAME_ZOOM) { 657 c->zoom_geom.x = c->border_width + c->geom.w - buts; 658 c->zoom_geom.y = c->border_width - 1; 659 c->zoom_geom.w = buts + 1; 660 c->zoom_geom.h = buts + 1; 661 } else 662 memset(&c->zoom_geom, 0, sizeof(geom_t)); 663 664 if (c->frame_style & FRAME_ICONIFY) { 665 c->iconify_geom.x = c->border_width + c->geom.w - buts; 666 c->iconify_geom.y = c->border_width - 1; 667 c->iconify_geom.w = buts + 1; 668 c->iconify_geom.h = buts + 1; 669 if (c->frame_style & FRAME_ZOOM) 670 c->iconify_geom.x -= c->zoom_geom.w - 1; 671 } else 672 memset(&c->iconify_geom, 0, sizeof(geom_t)); 673 674 if (c->frame_style & FRAME_TITLEBAR) { 675 c->titlebar_geom.x = c->border_width + c->close_geom.w; 676 if (c->frame_style & FRAME_CLOSE) 677 c->titlebar_geom.x--; 678 c->titlebar_geom.y = c->border_width; 679 c->titlebar_geom.w = c->geom.w; 680 if (c->frame_style & FRAME_CLOSE) 681 c->titlebar_geom.w -= c->close_geom.w - 1; 682 if (c->frame_style & FRAME_ICONIFY) 683 c->titlebar_geom.w -= c->iconify_geom.w - 2; 684 if (c->frame_style & FRAME_ZOOM) 685 c->titlebar_geom.w -= c->zoom_geom.w - 2; 686 if ((c->frame_style & FRAME_ZOOM) && 687 (c->frame_style & FRAME_ICONIFY)) 688 c->titlebar_geom.w++; 689 c->titlebar_geom.h = buts; 690 } else 691 memset(&c->titlebar_geom, 0, sizeof(geom_t)); 692 693 if ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) { 694 c->resize_e_geom.x = c->border_width + c->geom.w; 695 c->resize_e_geom.y = c->border_width + buts; 696 c->resize_e_geom.w = c->border_width; 697 if (c->frame_style & FRAME_TITLEBAR) 698 c->resize_e_geom.h = c->geom.h - buts; 699 else 700 c->resize_e_geom.h = c->geom.h - buts - buts; 701 } else 702 memset(&c->resize_e_geom, 0, sizeof(geom_t)); 703 704 if ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) { 705 c->resize_se_geom.x = c->resize_ne_geom.x; 706 c->resize_se_geom.y = c->resize_e_geom.y + c->resize_e_geom.h; 707 c->resize_se_geom.w = c->border_width + buts; 708 c->resize_se_geom.h = c->border_width + buts; 709 } else 710 memset(&c->resize_se_geom, 0, sizeof(geom_t)); 711 712 if (c->frame_style & FRAME_RESIZABLE) { 713 if (c->state & STATE_SHADED) { 714 c->resize_s_geom.x = 0; 715 c->resize_s_geom.y = c->border_width + buts - 1; 716 c->resize_s_geom.w = c->border_width + c->geom.w + 717 c->border_width; 718 c->resize_s_geom.h = c->border_width; 719 } else { 720 c->resize_s_geom.x = c->resize_n_geom.x; 721 c->resize_s_geom.y = c->resize_se_geom.y + buts; 722 c->resize_s_geom.w = c->resize_n_geom.w; 723 c->resize_s_geom.h = c->border_width; 724 } 725 } else 726 memset(&c->resize_s_geom, 0, sizeof(geom_t)); 727 728 if ((c->frame_style & FRAME_RESIZABLE) && !(c->state & STATE_SHADED)) { 729 c->resize_sw_geom.x = 0; 730 c->resize_sw_geom.y = c->resize_se_geom.y; 731 c->resize_sw_geom.w = c->resize_se_geom.w; 732 c->resize_sw_geom.h = c->resize_se_geom.h; 733 } else 734 memset(&c->resize_sw_geom, 0, sizeof(geom_t)); 735 736 c->resize_w_geom.x = 0; 737 c->resize_w_geom.y = c->resize_e_geom.y; 738 c->resize_w_geom.w = c->resize_e_geom.w; 739 c->resize_w_geom.h = c->resize_e_geom.h; 740 741 c->frame_geom.x = c->geom.x - c->border_width; 742 c->frame_geom.y = c->geom.y - c->border_width - 743 ((c->frame_style & FRAME_TITLEBAR) ? buts : 0); 744 c->frame_geom.w = c->geom.w + c->border_width + c->border_width; 745 if (c->state & STATE_SHADED) 746 c->frame_geom.h = c->border_width + buts + c->border_width - 1; 747 else 748 c->frame_geom.h = c->geom.h + c->border_width + 749 ((c->frame_style & FRAME_TITLEBAR) ? buts : 0) + 750 c->border_width; 751} 752 753int 754set_wm_state(client_t *c, unsigned long state) 755{ 756 return set_atoms(c->win, wm_state, wm_state, &state, 1); 757} 758 759void 760check_states(client_t *c) 761{ 762 Atom state; 763 unsigned long read, left; 764 int i; 765 766 /* XXX: c->win is unmapped, we can't talk to it */ 767 if (c->state & STATE_ICONIFIED) 768 return; 769 770 c->state = STATE_NORMAL; 771 c->frame_style = FRAME_ALL; 772 773 for (i = 0; i < MAX_WIN_TYPE_ATOMS; i++) { 774 if (get_atoms(c->win, net_wm_wintype, XA_ATOM, i, 775 &c->win_type[i], 1, &left)) { 776#ifdef DEBUG 777 dump_name(c, __func__, "wm_wintype", XGetAtomName(dpy, 778 c->win_type[i])); 779#endif 780 if (c->win_type[i] == net_wm_type_dock) 781 c->state |= STATE_DOCK; 782 } 783 784 if (!left) 785 break; 786 787 if (left && i == MAX_WIN_TYPE_ATOMS - 1) 788 warnx("client has too many _NET_WM_WINDOW_TYPE atoms"); 789 } 790 791 if (get_wm_state(c->win) == IconicState) { 792#ifdef DEBUG 793 dump_name(c, __func__, "wm_state", "IconicState"); 794#endif 795 c->state |= STATE_ICONIFIED; 796 return; 797 } 798 799 for (i = 0, left = 1; left; i += read) { 800 read = get_atoms(c->win, net_wm_state, XA_ATOM, i, &state, 1, 801 &left); 802 if (!read) 803 break; 804#ifdef DEBUG 805 dump_name(c, __func__, "net_wm_state", XGetAtomName(dpy, 806 state)); 807#endif 808 if (state == net_wm_state_shaded) 809 c->state |= STATE_SHADED; 810 else if (state == net_wm_state_mh || state == net_wm_state_mv) 811 c->state |= STATE_ZOOMED; 812 else if (state == net_wm_state_fs) 813 c->state |= STATE_FULLSCREEN; 814 else if (state == net_wm_state_above) 815 c->state |= STATE_ABOVE; 816 else if (state == net_wm_state_below) 817 c->state |= STATE_BELOW; 818 } 819} 820 821/* If we frob the geom for some reason, we need to inform the client. */ 822void 823send_config(client_t *c) 824{ 825 XConfigureEvent ce; 826 827 ce.type = ConfigureNotify; 828 ce.event = c->win; 829 ce.window = c->win; 830 ce.x = c->geom.x; 831 ce.y = c->geom.y; 832 ce.width = c->geom.w; 833 ce.height = c->geom.h; 834 ce.border_width = 0; 835 ce.above = None; 836 ce.override_redirect = 0; 837 838 XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); 839} 840 841void 842redraw_frame(client_t *c, Window only) 843{ 844 XftColor *txft; 845 XGlyphInfo extents; 846 Pixmap *pm, *pm_mask; 847 XpmAttributes *pm_attrs; 848 int x, y, tw; 849 850 if (!c || (c->frame_style == FRAME_NONE) || !c->frame) 851 return; 852 853 if (!IS_ON_CUR_DESK(c)) 854 return; 855 856 if (c->state & STATE_ICONIFIED) { 857 redraw_icon(c, only); 858 return; 859 } 860 861#ifdef DEBUG 862 dump_name(c, __func__, frame_name(c, only), c->name); 863#endif 864 865 recalc_frame(c); 866 867 if (only == None) { 868 if ((c->frame_style & FRAME_BORDER) && 869 !(c->frame_style & FRAME_RESIZABLE)) { 870 if (c == focused) 871 XSetWindowBackground(dpy, c->frame, bg.pixel); 872 else 873 XSetWindowBackground(dpy, c->frame, 874 unfocused_bg.pixel); 875 XClearWindow(dpy, c->frame); 876 877 XSetForeground(dpy, DefaultGC(dpy, screen), 878 WhitePixel(dpy, screen)); 879 XDrawLine(dpy, c->frame, DefaultGC(dpy, screen), 880 c->border_width, c->border_width, 881 c->frame_geom.w - c->border_width, 882 c->border_width); 883 XDrawLine(dpy, c->frame, DefaultGC(dpy, screen), 884 c->frame_geom.w - c->border_width - 1, 885 c->border_width, 886 c->frame_geom.w - c->border_width - 1, 887 c->border_width + c->titlebar_geom.h); 888 } else 889 XSetWindowBackground(dpy, c->frame, 890 BlackPixel(dpy, screen)); 891 892 XMoveResizeWindow(dpy, c->frame, 893 c->frame_geom.x, c->frame_geom.y, 894 c->frame_geom.w, c->frame_geom.h); 895 896 if (c->state & STATE_SHADED) 897 /* keep win just below our shaded frame */ 898 XMoveResizeWindow(dpy, c->win, 899 c->geom.x - c->frame_geom.x, 900 c->geom.y - c->frame_geom.y + c->border_width + 1, 901 c->geom.w, c->geom.h); 902 else 903 XMoveResizeWindow(dpy, c->win, 904 c->geom.x - c->frame_geom.x, 905 c->geom.y - c->frame_geom.y, 906 c->geom.w, c->geom.h); 907 908 XSetForeground(dpy, DefaultGC(dpy, screen), border_fg.pixel); 909 XDrawRectangle(dpy, c->frame, DefaultGC(dpy, screen), 910 0, 0, c->frame_geom.w - 1, c->frame_geom.h - 1); 911 } 912 913 if (only == None || only == c->titlebar) { 914 if (c->frame_style & FRAME_TITLEBAR) { 915 if (c == focused) { 916 txft = &xft_fg; 917 XSetWindowBackground(dpy, c->titlebar, 918 bg.pixel); 919 } else { 920 txft = &xft_fg_unfocused; 921 XSetWindowBackground(dpy, c->titlebar, 922 unfocused_bg.pixel); 923 } 924 XMoveResizeWindow(dpy, c->titlebar, 925 c->titlebar_geom.x, c->titlebar_geom.y, 926 c->titlebar_geom.w, c->titlebar_geom.h); 927 XMapWindow(dpy, c->titlebar); 928 XClearWindow(dpy, c->titlebar); 929 930 if (c->name && !has_win_type(c, net_wm_type_utility)) { 931 XftTextExtentsUtf8(dpy, font, 932 (FcChar8 *)c->name, strlen(c->name), 933 &extents); 934 tw = extents.xOff; 935 x = opt_pad * 2; 936 937 if (tw < (c->titlebar_geom.w - (opt_pad * 2))) 938 /* center title */ 939 x = (c->titlebar_geom.w / 2) - (tw / 2); 940 941 y = opt_pad + font->ascent; 942 943 XftDrawStringUtf8(c->xftdraw, txft, font, x, 944 y, (unsigned char *)c->name, 945 strlen(c->name)); 946 } 947 if (!(c->frame_style & FRAME_RESIZABLE) && 948 (c->state & STATE_SHADED)) 949 XSetForeground(dpy, DefaultGC(dpy, screen), 950 WhitePixel(dpy, screen)); 951 else 952 XSetForeground(dpy, DefaultGC(dpy, screen), 953 border_fg.pixel); 954 XDrawLine(dpy, c->titlebar, DefaultGC(dpy, screen), 955 0, c->titlebar_geom.h - 1, c->titlebar_geom.w + 1, 956 c->titlebar_geom.h - 1); 957 958 if ((c->frame_style & FRAME_BORDER) && 959 !(c->frame_style & FRAME_RESIZABLE)) { 960 XSetForeground(dpy, DefaultGC(dpy, screen), 961 WhitePixel(dpy, screen)); 962 XDrawLine(dpy, c->titlebar, 963 DefaultGC(dpy, screen), 964 0, 0, c->titlebar_geom.w, 0); 965 XDrawLine(dpy, c->titlebar, 966 DefaultGC(dpy, screen), 967 c->titlebar_geom.w - 1, 0, 968 c->titlebar_geom.w - 1, 969 c->titlebar_geom.h - 1); 970 } 971 } else 972 XUnmapWindow(dpy, c->titlebar); 973 } 974 975 if (only == None || only == c->close) { 976 if (c->frame_style & FRAME_CLOSE) { 977 XSetWindowBackground(dpy, c->close, button_bg.pixel); 978 XClearWindow(dpy, c->close); 979 XMoveResizeWindow(dpy, c->close, 980 c->close_geom.x, c->close_geom.y, c->close_geom.w, 981 c->close_geom.h); 982 XMapWindow(dpy, c->close); 983 984 if (has_win_type(c, net_wm_type_utility)) { 985 pm = &utility_close_pm; 986 pm_mask = &utility_close_pm_mask; 987 pm_attrs = &utility_close_pm_attrs; 988 } else { 989 pm = &close_pm; 990 pm_mask = &close_pm_mask; 991 pm_attrs = &close_pm_attrs; 992 } 993 994 x = (c->close_geom.w / 2) - (pm_attrs->width / 2); 995 y = (c->close_geom.h / 2) - (pm_attrs->height / 2); 996 XSetClipMask(dpy, pixmap_gc, *pm_mask); 997 XSetClipOrigin(dpy, pixmap_gc, x, y); 998 XCopyArea(dpy, *pm, c->close, pixmap_gc, 0, 0, 999 pm_attrs->width, pm_attrs->height, x, y); 1000 if (c->close_pressed) 1001 XCopyArea(dpy, c->close, c->close, invert_gc, 1002 0, 0, c->close_geom.w, c->close_geom.h, 0, 1003 0); 1004 else 1005 XCopyArea(dpy, c->close, c->close, pixmap_gc, 1006 0, 0, c->close_geom.w, c->close_geom.h, 0, 1007 0); 1008 1009 XSetForeground(dpy, DefaultGC(dpy, screen), 1010 border_fg.pixel); 1011 XDrawRectangle(dpy, c->close, 1012 DefaultGC(dpy, screen), 1013 0, 0, c->close_geom.w - 1, 1014 c->close_geom.h - 1); 1015 1016 if ((c->frame_style & FRAME_BORDER) && 1017 !(c->frame_style & FRAME_RESIZABLE)) { 1018 XSetForeground(dpy, DefaultGC(dpy, screen), 1019 WhitePixel(dpy, screen)); 1020 XDrawLine(dpy, c->close, 1021 DefaultGC(dpy, screen), 1022 0, 0, c->close_geom.w, 0); 1023 XDrawLine(dpy, c->close, 1024 DefaultGC(dpy, screen), 1025 0, 0, 0, c->close_geom.h - 1); 1026 } 1027 } else 1028 XUnmapWindow(dpy, c->close); 1029 } 1030 1031 if (only == None || only == c->iconify) { 1032 if (c->frame_style & FRAME_ICONIFY) { 1033 XSetWindowBackground(dpy, c->iconify, button_bg.pixel); 1034 XClearWindow(dpy, c->iconify); 1035 XMoveResizeWindow(dpy, c->iconify, 1036 c->iconify_geom.x, c->iconify_geom.y, 1037 c->iconify_geom.w, 1038 c->iconify_geom.h); 1039 XMapWindow(dpy, c->iconify); 1040 x = (c->iconify_geom.w / 2) - 1041 (iconify_pm_attrs.width / 2) - (opt_bevel / 2); 1042 y = (c->iconify_geom.h / 2) - 1043 (iconify_pm_attrs.height / 2) - (opt_bevel / 2); 1044 if (c->iconify_pressed) { 1045 x += 2; 1046 y += 2; 1047 } 1048 XSetClipMask(dpy, pixmap_gc, iconify_pm_mask); 1049 XSetClipOrigin(dpy, pixmap_gc, x, y); 1050 XCopyArea(dpy, iconify_pm, c->iconify, pixmap_gc, 0, 0, 1051 iconify_pm_attrs.width, iconify_pm_attrs.height, x, 1052 y); 1053 bevel(c->iconify, c->iconify_geom, c->iconify_pressed); 1054 XSetForeground(dpy, DefaultGC(dpy, screen), 1055 border_fg.pixel); 1056 XDrawRectangle(dpy, c->iconify, DefaultGC(dpy, screen), 1057 0, 0, c->iconify_geom.w - 1, c->iconify_geom.h - 1); 1058 } else 1059 XUnmapWindow(dpy, c->iconify); 1060 } 1061 1062 if (only == None || only == c->zoom) { 1063 if (c->frame_style & FRAME_ZOOM) { 1064 XMoveResizeWindow(dpy, c->zoom, 1065 c->zoom_geom.x, c->zoom_geom.y, c->zoom_geom.w, 1066 c->zoom_geom.h); 1067 XSetWindowBackground(dpy, c->zoom, button_bg.pixel); 1068 XClearWindow(dpy, c->zoom); 1069 XMapWindow(dpy, c->zoom); 1070 1071 if (c->state & STATE_ZOOMED) { 1072 pm = &unzoom_pm; 1073 pm_mask = &unzoom_pm_mask; 1074 pm_attrs = &unzoom_pm_attrs; 1075 } else { 1076 pm = &zoom_pm; 1077 pm_mask = &zoom_pm_mask; 1078 pm_attrs = &zoom_pm_attrs; 1079 } 1080 1081 x = (c->zoom_geom.w / 2) - (pm_attrs->width / 2) - 1082 (opt_bevel / 2); 1083 y = (c->zoom_geom.h / 2) - (pm_attrs->height / 2) - 1084 (opt_bevel / 2); 1085 if (c->zoom_pressed) { 1086 x += 2; 1087 y += 2; 1088 } 1089 XSetClipMask(dpy, pixmap_gc, *pm_mask); 1090 XSetClipOrigin(dpy, pixmap_gc, x, y); 1091 XCopyArea(dpy, *pm, c->zoom, pixmap_gc, 0, 0, 1092 pm_attrs->width, pm_attrs->height, x, y); 1093 bevel(c->zoom, c->zoom_geom, c->zoom_pressed); 1094 XSetForeground(dpy, DefaultGC(dpy, screen), 1095 border_fg.pixel); 1096 XDrawRectangle(dpy, c->zoom, DefaultGC(dpy, screen), 1097 0, 0, c->zoom_geom.w - 1, c->zoom_geom.h - 1); 1098 } else 1099 XUnmapWindow(dpy, c->zoom); 1100 } 1101 1102 if (only == None || only == c->resize_nw) { 1103 if (c->frame_style & FRAME_RESIZABLE) { 1104 XSetWindowBackground(dpy, c->resize_nw, 1105 border_bg.pixel); 1106 XClearWindow(dpy, c->resize_nw); 1107 XMoveResizeWindow(dpy, c->resize_nw, 1108 c->resize_nw_geom.x, c->resize_nw_geom.y, 1109 c->resize_nw_geom.w, c->resize_nw_geom.h); 1110 XMapWindow(dpy, c->resize_nw); 1111 XSetForeground(dpy, DefaultGC(dpy, screen), 1112 border_fg.pixel); 1113 XDrawRectangle(dpy, c->resize_nw, 1114 DefaultGC(dpy, screen), 0, 0, 1115 c->resize_nw_geom.w - 1, c->resize_nw_geom.h - 1); 1116 1117 if (!(c->frame_style & FRAME_CLOSE)) { 1118 XSetForeground(dpy, DefaultGC(dpy, screen), 1119 border_fg.pixel); 1120 XDrawRectangle(dpy, c->resize_nw, 1121 DefaultGC(dpy, screen), 1122 c->resize_n_geom.h - 1, 1123 c->resize_n_geom.h - 1, 1124 c->resize_nw_geom.w - 1, 1125 c->resize_nw_geom.h - 1); 1126 XSetForeground(dpy, DefaultGC(dpy, screen), 1127 BlackPixel(dpy, screen)); 1128 XFillRectangle(dpy, c->resize_nw, 1129 DefaultGC(dpy, screen), 1130 c->resize_n_geom.h, 1131 c->resize_n_geom.h, 1132 c->resize_nw_geom.h - 2, 1133 c->resize_nw_geom.h - 2); 1134 } 1135 } else 1136 XUnmapWindow(dpy, c->resize_nw); 1137 } 1138 1139 if (only == None || only == c->resize_n) { 1140 if (c->frame_style & FRAME_RESIZABLE) { 1141 XSetWindowBackground(dpy, c->resize_n, border_bg.pixel); 1142 XClearWindow(dpy, c->resize_n); 1143 XMoveResizeWindow(dpy, c->resize_n, 1144 c->resize_n_geom.x, c->resize_n_geom.y, 1145 c->resize_n_geom.w, c->resize_n_geom.h); 1146 XMapWindow(dpy, c->resize_n); 1147 XSetForeground(dpy, DefaultGC(dpy, screen), 1148 border_fg.pixel); 1149 XDrawRectangle(dpy, c->resize_n, DefaultGC(dpy, screen), 1150 -1, 0, c->resize_n_geom.w + 1, 1151 c->resize_n_geom.h - 1); 1152 } else 1153 XUnmapWindow(dpy, c->resize_n); 1154 } 1155 1156 if (only == None || only == c->resize_ne) { 1157 if (c->frame_style & FRAME_RESIZABLE) { 1158 XSetWindowBackground(dpy, c->resize_ne, 1159 border_bg.pixel); 1160 XMapWindow(dpy, c->resize_ne); 1161 XClearWindow(dpy, c->resize_ne); 1162 XMoveResizeWindow(dpy, c->resize_ne, 1163 c->resize_ne_geom.x, c->resize_ne_geom.y, 1164 c->resize_ne_geom.w, c->resize_ne_geom.h); 1165 XSetForeground(dpy, DefaultGC(dpy, screen), 1166 border_fg.pixel); 1167 XDrawRectangle(dpy, c->resize_ne, 1168 DefaultGC(dpy, screen), 0, 0, 1169 c->resize_ne_geom.w - 1, c->resize_ne_geom.h - 1); 1170 if (!(c->frame_style & (FRAME_ICONIFY | FRAME_ZOOM))) { 1171 XSetForeground(dpy, DefaultGC(dpy, screen), 1172 border_fg.pixel); 1173 XDrawRectangle(dpy, c->resize_ne, 1174 DefaultGC(dpy, screen), 1175 0, c->resize_n_geom.h - 1, 1176 c->resize_ne_geom.w - c->resize_n_geom.h, 1177 c->resize_ne_geom.h - 1); 1178 XSetForeground(dpy, DefaultGC(dpy, screen), 1179 BlackPixel(dpy, screen)); 1180 XFillRectangle(dpy, c->resize_ne, 1181 DefaultGC(dpy, screen), 1182 0, c->resize_n_geom.h, 1183 c->resize_ne_geom.w - c->resize_n_geom.h, 1184 c->resize_ne_geom.h - 2); 1185 } 1186 } else 1187 XUnmapWindow(dpy, c->resize_ne); 1188 } 1189 1190 if (only == None || only == c->resize_e) { 1191 if ((c->frame_style & FRAME_RESIZABLE) && 1192 !(c->state & STATE_SHADED)) { 1193 XSetWindowBackground(dpy, c->resize_e, border_bg.pixel); 1194 XMapWindow(dpy, c->resize_e); 1195 XClearWindow(dpy, c->resize_e); 1196 XMoveResizeWindow(dpy, c->resize_e, 1197 c->resize_e_geom.x, c->resize_e_geom.y, 1198 c->resize_e_geom.w, c->resize_e_geom.h); 1199 XSetForeground(dpy, DefaultGC(dpy, screen), 1200 border_fg.pixel); 1201 XDrawRectangle(dpy, c->resize_e, DefaultGC(dpy, screen), 1202 0, -1, c->resize_e_geom.w - 1, 1203 c->resize_e_geom.h + 1); 1204 } else 1205 XUnmapWindow(dpy, c->resize_e); 1206 } 1207 1208 if (only == None || only == c->resize_se) { 1209 if ((c->frame_style & FRAME_RESIZABLE) && 1210 !(c->state & STATE_SHADED)) { 1211 XSetWindowBackground(dpy, c->resize_se, 1212 border_bg.pixel); 1213 XClearWindow(dpy, c->resize_se); 1214 XMoveResizeWindow(dpy, c->resize_se, 1215 c->resize_se_geom.x, c->resize_se_geom.y, 1216 c->resize_se_geom.w, c->resize_se_geom.h); 1217 XMapWindow(dpy, c->resize_se); 1218 XSetForeground(dpy, DefaultGC(dpy, screen), 1219 border_fg.pixel); 1220 XDrawRectangle(dpy, c->resize_se, 1221 DefaultGC(dpy, screen), 0, 0, 1222 c->resize_se_geom.w - 1, 1223 c->resize_se_geom.h - 1); 1224 XDrawRectangle(dpy, c->resize_se, 1225 DefaultGC(dpy, screen), 0, 0, 1226 c->resize_se_geom.w - c->resize_s_geom.h, 1227 c->resize_se_geom.h - c->resize_s_geom.h); 1228 XSetForeground(dpy, DefaultGC(dpy, screen), 1229 BlackPixel(dpy, screen)); 1230 XFillRectangle(dpy, c->resize_se, 1231 DefaultGC(dpy, screen), 0, 0, 1232 c->resize_se_geom.w - c->resize_s_geom.h, 1233 c->resize_se_geom.h - c->resize_s_geom.h); 1234 } else 1235 XUnmapWindow(dpy, c->resize_se); 1236 } 1237 1238 if (only == None || only == c->resize_s) { 1239 if (c->frame_style & FRAME_RESIZABLE) { 1240 XSetWindowBackground(dpy, c->resize_s, border_bg.pixel); 1241 XClearWindow(dpy, c->resize_s); 1242 XMoveResizeWindow(dpy, c->resize_s, 1243 c->resize_s_geom.x, c->resize_s_geom.y, 1244 c->resize_s_geom.w, c->resize_s_geom.h); 1245 XMapWindow(dpy, c->resize_s); 1246 XSetForeground(dpy, DefaultGC(dpy, screen), 1247 border_fg.pixel); 1248 XDrawRectangle(dpy, c->resize_s, DefaultGC(dpy, screen), 1249 0, 0, c->resize_s_geom.w, 1250 c->resize_s_geom.h - 1); 1251 1252 if (c->state & STATE_SHADED) { 1253 XSetForeground(dpy, DefaultGC(dpy, screen), 1254 border_bg.pixel); 1255 XDrawLine(dpy, c->resize_s, 1256 DefaultGC(dpy, screen), 1, 0, 1257 c->resize_s_geom.h - 1, 0); 1258 XDrawLine(dpy, c->resize_s, 1259 DefaultGC(dpy, screen), 1260 c->resize_s_geom.w - c->resize_s_geom.h + 1, 1261 0, c->resize_s_geom.w - 1, 0); 1262 1263 XSetForeground(dpy, DefaultGC(dpy, screen), 1264 border_fg.pixel); 1265 XDrawLine(dpy, c->resize_s, 1266 DefaultGC(dpy, screen), 1267 c->resize_sw_geom.w, 0, 1268 c->resize_sw_geom.w, c->resize_s_geom.h); 1269 XDrawLine(dpy, c->resize_s, 1270 DefaultGC(dpy, screen), 1271 c->resize_s_geom.w - c->resize_sw_geom.w - 1272 1, 0, 1273 c->resize_s_geom.w - c->resize_sw_geom.w - 1274 1, c->resize_s_geom.h); 1275 } 1276 } else 1277 XUnmapWindow(dpy, c->resize_s); 1278 } 1279 1280 if (only == None || only == c->resize_sw) { 1281 if ((c->frame_style & FRAME_RESIZABLE) && 1282 !(c->state & STATE_SHADED)) { 1283 XSetWindowBackground(dpy, c->resize_sw, 1284 border_bg.pixel); 1285 XClearWindow(dpy, c->resize_sw); 1286 XMoveResizeWindow(dpy, c->resize_sw, 1287 c->resize_sw_geom.x, c->resize_sw_geom.y, 1288 c->resize_sw_geom.w, c->resize_sw_geom.h); 1289 XMapWindow(dpy, c->resize_sw); 1290 XSetForeground(dpy, DefaultGC(dpy, screen), 1291 border_fg.pixel); 1292 XDrawRectangle(dpy, c->resize_sw, 1293 DefaultGC(dpy, screen), 0, 0, c->resize_sw_geom.w, 1294 c->resize_sw_geom.h - 1); 1295 XDrawRectangle(dpy, c->resize_sw, 1296 DefaultGC(dpy, screen), 1297 c->resize_w_geom.w - 1, 0, 1298 c->resize_sw_geom.w, 1299 c->resize_sw_geom.h - c->resize_s_geom.h); 1300 XSetForeground(dpy, DefaultGC(dpy, screen), 1301 BlackPixel(dpy, screen)); 1302 XFillRectangle(dpy, c->resize_sw, 1303 DefaultGC(dpy, screen), c->resize_w_geom.w, 0, 1304 c->resize_sw_geom.w, 1305 c->resize_sw_geom.h - c->resize_s_geom.h); 1306 } else 1307 XUnmapWindow(dpy, c->resize_sw); 1308 } 1309 1310 if (only == None || only == c->resize_w) { 1311 if ((c->frame_style & FRAME_RESIZABLE) && 1312 !(c->state & STATE_SHADED)) { 1313 XSetWindowBackground(dpy, c->resize_w, border_bg.pixel); 1314 XClearWindow(dpy, c->resize_w); 1315 XMoveResizeWindow(dpy, c->resize_w, 1316 c->resize_w_geom.x, c->resize_w_geom.y, 1317 c->resize_w_geom.w, c->resize_w_geom.h); 1318 XMapWindow(dpy, c->resize_w); 1319 XSetForeground(dpy, DefaultGC(dpy, screen), 1320 border_fg.pixel); 1321 XDrawRectangle(dpy, c->resize_w, DefaultGC(dpy, screen), 1322 0, -1, c->resize_w_geom.w - 1, 1323 c->resize_w_geom.h + 1); 1324 } else 1325 XUnmapWindow(dpy, c->resize_w); 1326 } 1327} 1328 1329static void 1330bevel(Window win, geom_t geom, int pressed) 1331{ 1332 int x; 1333 1334 XSetForeground(dpy, DefaultGC(dpy, screen), bevel_dark.pixel); 1335 1336 if (pressed) { 1337 for (x = 0; x < opt_bevel - 1; x++) { 1338 XDrawLine(dpy, win, DefaultGC(dpy, screen), 1339 x, x, geom.w - x, x); 1340 XDrawLine(dpy, win, DefaultGC(dpy, screen), 1341 x, x, x, geom.h - x); 1342 } 1343 } else { 1344 for (x = 1; x <= opt_bevel; x++) { 1345 XDrawLine(dpy, win, DefaultGC(dpy, screen), 1346 geom.w - 1 - x, x, 1347 geom.w - 1 - x, geom.h - 1); 1348 XDrawLine(dpy, win, DefaultGC(dpy, screen), 1349 x, geom.h - 1 - x, 1350 geom.w - 1, geom.h - x - 1); 1351 } 1352 1353 XSetForeground(dpy, DefaultGC(dpy, screen), bevel_light.pixel); 1354 for (x = 1; x <= opt_bevel - 1; x++) { 1355 XDrawLine(dpy, win, DefaultGC(dpy, screen), 1356 1, x, 1357 geom.w - 1 - x, x); 1358 XDrawLine(dpy, win, DefaultGC(dpy, screen), 1359 x, 1, 1360 x, geom.h - 1 - x); 1361 } 1362 } 1363} 1364 1365void 1366get_client_icon(client_t *c) 1367{ 1368 Window junkw; 1369 unsigned long w, h; 1370 unsigned int depth; 1371 int junki; 1372 1373 /* try through atom */ 1374 if (get_atoms(c->win, net_wm_icon, XA_CARDINAL, 0, &w, 1, NULL) && 1375 get_atoms(c->win, net_wm_icon, XA_CARDINAL, 1, &h, 1, NULL)) { 1376 /* TODO */ 1377 } 1378 1379 if (c->icon_managed) { 1380 if (c->icon_pixmap) 1381 XFreePixmap(dpy, c->icon_pixmap); 1382 if (c->icon_mask) 1383 XFreePixmap(dpy, c->icon_mask); 1384 c->icon_managed = 0; 1385 } 1386 1387 /* fallback to WMHints */ 1388 if (c->wm_hints) 1389 XFree(c->wm_hints); 1390 c->wm_hints = XGetWMHints(dpy, c->win); 1391 if (!c->wm_hints || !(c->wm_hints->flags & IconPixmapHint)) { 1392 c->icon_pixmap = default_icon_pm; 1393 c->icon_depth = DefaultDepth(dpy, screen); 1394 c->icon_mask = default_icon_pm_mask; 1395 return; 1396 } 1397 1398 XGetGeometry(dpy, c->wm_hints->icon_pixmap, &junkw, &junki, &junki, 1399 (unsigned int *)&c->icon_geom.w, (unsigned int *)&c->icon_geom.h, 1400 (unsigned int *)&junki, &depth); 1401 c->icon_pixmap = c->wm_hints->icon_pixmap; 1402 c->icon_depth = depth; 1403 1404 if (c->wm_hints->flags & IconMaskHint) 1405 c->icon_mask = c->wm_hints->icon_mask; 1406 else 1407 c->icon_mask = None; 1408 1409#ifdef USE_GDK_PIXBUF 1410 if (c->icon_geom.w > icon_size || c->icon_geom.h > icon_size) { 1411 GdkPixbuf *gp, *mask, *scaled; 1412 int sh, sw; 1413 1414 if (c->icon_geom.w > c->icon_geom.h) { 1415 sw = icon_size; 1416 sh = (icon_size / (double)c->icon_geom.w) * 1417 c->icon_geom.h; 1418 } else { 1419 sh = icon_size; 1420 sw = (icon_size / (double)c->icon_geom.h) * 1421 c->icon_geom.w; 1422 } 1423 1424 gp = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 1425 c->icon_geom.w, c->icon_geom.h); 1426 1427 if (!gdk_pixbuf_xlib_get_from_drawable(gp, c->icon_pixmap, 1428 c->cmap, DefaultVisual(dpy, screen), 0, 0, 0, 0, 1429 c->icon_geom.w, c->icon_geom.h)) { 1430 warnx("failed to load pixmap with gdk pixbuf"); 1431 g_object_unref(gp); 1432 return; 1433 } 1434 1435 /* manually mask image, ugh */ 1436 if (c->icon_mask != None) { 1437 guchar *px, *pxm; 1438 int rs, rsm, dm, ch, x, y; 1439 1440 mask = gdk_pixbuf_xlib_get_from_drawable(NULL, 1441 c->icon_mask, c->cmap, DefaultVisual(dpy, screen), 1442 0, 0, 0, 0, c->icon_geom.w, c->icon_geom.h); 1443 if (!mask) { 1444 warnx("failed to load mask with gdk pixbuf"); 1445 g_object_unref(gp); 1446 return; 1447 } 1448 1449 px = gdk_pixbuf_get_pixels(gp); 1450 pxm = gdk_pixbuf_get_pixels(mask); 1451 rs = gdk_pixbuf_get_rowstride(gp); 1452 rsm = gdk_pixbuf_get_rowstride(mask); 1453 dm = gdk_pixbuf_get_bits_per_sample(mask); 1454 ch = gdk_pixbuf_get_n_channels(mask); 1455 1456 for (y = 0; y < c->icon_geom.h; y++) { 1457 guchar *tr = px + (y * rs); 1458 guchar *trm = pxm + (y * rsm); 1459 for (x = 0; x < c->icon_geom.w; x++) { 1460 guchar al = 0xff; 1461 switch (dm) { 1462 case 1: 1463 al = trm[x * ch / 8]; 1464 al >>= (x % 8); 1465 al = al ? 0xff : 0; 1466 break; 1467 case 8: 1468 al = (trm[(x * ch) + 2]) ? 0xff 1469 : 0; 1470 break; 1471 } 1472 1473 tr[(x * 4) + 3] = al; 1474 } 1475 } 1476 1477 g_object_unref(mask); 1478 } 1479 1480 scaled = gdk_pixbuf_scale_simple(gp, sw, sh, 1481 GDK_INTERP_BILINEAR); 1482 if (!scaled) { 1483 warnx("failed to scale icon with gdk pixbuf"); 1484 g_object_unref(gp); 1485 return; 1486 } 1487 1488 if (c->icon_managed) { 1489 if (c->icon_pixmap) 1490 XFreePixmap(dpy, c->icon_pixmap); 1491 if (c->icon_mask) 1492 XFreePixmap(dpy, c->icon_mask); 1493 } 1494 1495 gdk_pixbuf_xlib_render_pixmap_and_mask(scaled, 1496 &c->icon_pixmap, &c->icon_mask, 1); 1497 c->icon_geom.w = sw; 1498 c->icon_geom.h = sh; 1499 c->icon_managed = 1; 1500 1501 g_object_unref(scaled); 1502 g_object_unref(gp); 1503 } 1504#endif 1505} 1506 1507void 1508redraw_icon(client_t *c, Window only) 1509{ 1510 XftColor *txft; 1511 void *xft_lines; 1512 int label_pad = 2 * opt_scale; 1513 int nlines, x; 1514 1515#ifdef DEBUG 1516 dump_name(c, __func__, frame_name(c, only), c->name); 1517#endif 1518 1519 if (only == None || only == c->icon) { 1520 XClearWindow(dpy, c->icon); 1521 XSetWindowBackground(dpy, c->icon, WhitePixel(dpy, screen)); 1522 XMoveResizeWindow(dpy, c->icon, 1523 c->icon_geom.x + ((icon_size - c->icon_geom.w) / 2), 1524 c->icon_geom.y + ((icon_size - c->icon_geom.h) / 2), 1525 c->icon_geom.w, c->icon_geom.h); 1526 if (c->icon_mask) { 1527 XShapeCombineMask(dpy, c->icon, ShapeBounding, 0, 0, 1528 c->icon_mask, ShapeSet); 1529 XSetClipMask(dpy, c->icon_gc, c->icon_mask); 1530 XSetClipOrigin(dpy, c->icon_gc, 0, 0); 1531 } 1532 if (c->icon_depth == DefaultDepth(dpy, screen)) 1533 XCopyArea(dpy, c->icon_pixmap, c->icon, c->icon_gc, 1534 0, 0, c->icon_geom.w, c->icon_geom.h, 0, 0); 1535 else { 1536 XSetBackground(dpy, c->icon_gc, 1537 BlackPixel(dpy, screen)); 1538 XSetForeground(dpy, c->icon_gc, 1539 WhitePixel(dpy, screen)); 1540 XCopyPlane(dpy, c->icon_pixmap, c->icon, c->icon_gc, 1541 0, 0, c->icon_geom.w, c->icon_geom.h, 0, 0, 1); 1542 } 1543 } 1544 1545 if (only != None && only != c->icon_label) 1546 return; 1547 1548 if (c == focused) { 1549 txft = &xft_fg; 1550 XSetWindowBackground(dpy, c->icon_label, bg.pixel); 1551 XSetForeground(dpy, DefaultGC(dpy, screen), fg.pixel); 1552 } else { 1553 txft = &xft_fg_unfocused; 1554 XSetWindowBackground(dpy, c->icon_label, unfocused_bg.pixel); 1555 XSetForeground(dpy, DefaultGC(dpy, screen), 1556 unfocused_fg.pixel); 1557 } 1558 1559 XClearWindow(dpy, c->icon_label); 1560 1561 if (!c->icon_name) 1562 c->icon_name = strdup("(Unknown)"); 1563 1564 xft_lines = word_wrap_xft(c->icon_name, ' ', iconfont, 1565 (icon_size * 2) - (label_pad * 2), &nlines); 1566 1567 c->icon_label_geom.y = c->icon_geom.y + icon_size + 10; 1568 c->icon_label_geom.h = label_pad; 1569 c->icon_label_geom.w = label_pad; 1570 1571 for (x = 0; x < nlines; x++) { 1572 struct xft_line_t *line = xft_lines + 1573 (sizeof(struct xft_line_t) * x); 1574 int w = label_pad + line->xft_width + label_pad; 1575 if (w > c->icon_label_geom.w) 1576 c->icon_label_geom.w = w; 1577 c->icon_label_geom.h += iconfont->ascent + iconfont->descent; 1578 } 1579 1580 c->icon_label_geom.h += label_pad; 1581 c->icon_label_geom.x = c->icon_geom.x - 1582 ((c->icon_label_geom.w - icon_size) / 2); 1583 1584 XMoveResizeWindow(dpy, c->icon_label, 1585 c->icon_label_geom.x, c->icon_label_geom.y, 1586 c->icon_label_geom.w, c->icon_label_geom.h); 1587 1588 int ly = label_pad; 1589 for (x = 0; x < nlines; x++) { 1590 struct xft_line_t *line = xft_lines + 1591 (sizeof(struct xft_line_t) * x); 1592 int lx = ((c->icon_label_geom.w - line->xft_width) / 2); 1593 1594 ly += iconfont->ascent; 1595 XftDrawStringUtf8(c->icon_xftdraw, txft, iconfont, lx, ly, 1596 (FcChar8 *)line->str, line->len); 1597 ly += iconfont->descent; 1598 } 1599 1600 free(xft_lines); 1601} 1602 1603void 1604collect_struts(client_t *c, strut_t *s) 1605{ 1606 client_t *p; 1607 XWindowAttributes attr; 1608 strut_t temp; 1609 1610 for (p = focused; p; p = p->next) { 1611 if (!IS_ON_CUR_DESK(p) || p == c) 1612 continue; 1613 1614 ignore_xerrors++; 1615 XGetWindowAttributes(dpy, p->win, &attr); 1616 ignore_xerrors--; 1617 if (attr.map_state == IsViewable && get_strut(p->win, &temp)) { 1618 if (temp.left > s->left) 1619 s->left = temp.left; 1620 if (temp.right > s->right) 1621 s->right = temp.right; 1622 if (temp.top > s->top) 1623 s->top = temp.top; 1624 if (temp.bottom > s->bottom) 1625 s->bottom = temp.bottom; 1626 } 1627 } 1628} 1629 1630/* 1631 * Well, the man pages for the shape extension say nothing, but I was able to 1632 * find a shape.PS.Z on the x.org FTP site. What we want to do here is make the 1633 * window shape be a boolean OR (or union) of the client's shape and our bar 1634 * for the name. The bar requires both a bound and a clip because it has a 1635 * border; the server will paint the border in the region between the two. 1636 */ 1637void 1638set_shape(client_t *c) 1639{ 1640 int n, order; 1641 XRectangle temp, *rects; 1642 1643 rects = XShapeGetRectangles(dpy, c->win, ShapeBounding, &n, &order); 1644 1645 if (n > 1) { 1646 /* window contents */ 1647 XShapeCombineShape(dpy, c->frame, ShapeBounding, 1648 c->resize_w_geom.w, 1649 c->resize_n_geom.h + c->titlebar_geom.h, 1650 c->win, ShapeBounding, ShapeSet); 1651 1652 /* titlebar */ 1653 temp.x = 0; 1654 temp.y = 0; 1655 temp.width = c->frame_geom.w; 1656 temp.height = c->resize_n_geom.h + c->titlebar_geom.h; 1657 XShapeCombineRectangles(dpy, c->frame, ShapeBounding, 1658 0, 0, &temp, 1, ShapeUnion, YXBanded); 1659 1660 /* bottom border */ 1661 temp.height = c->resize_s_geom.h; 1662 XShapeCombineRectangles(dpy, c->frame, ShapeBounding, 1663 0, c->frame_geom.h - c->resize_s_geom.h, &temp, 1, 1664 ShapeUnion, YXBanded); 1665 1666 /* left border */ 1667 temp.width = c->resize_w_geom.w; 1668 temp.height = c->frame_geom.h; 1669 XShapeCombineRectangles(dpy, c->frame, ShapeBounding, 1670 0, 0, &temp, 1, ShapeUnion, YXBanded); 1671 /* right border */ 1672 XShapeCombineRectangles(dpy, c->frame, ShapeBounding, 1673 c->frame_geom.w - c->resize_e_geom.w, 0, &temp, 1, 1674 ShapeUnion, YXBanded); 1675 1676 c->shaped = 1; 1677 } else 1678 c->shaped = 0; 1679 1680 XFree(rects); 1681} 1682 1683/* 1684 * I've decided to carefully ignore any errors raised by this function, rather 1685 * that attempt to determine asychronously if a window is "valid". Xlib calls 1686 * should only fail here if that a window has removed itself completely before 1687 * the Unmap and Destroy events get through the queue to us. It's not pretty. 1688 * 1689 * The mode argument specifes if the client is actually destroying itself or 1690 * being destroyed by us, or if we are merely cleaning up its data structures 1691 * when we exit mid-session. 1692 */ 1693void 1694del_client(client_t *c, int mode) 1695{ 1696 client_t *next; 1697 1698 XSync(dpy, False); 1699 XGrabServer(dpy); 1700 ignore_xerrors++; 1701 1702#ifdef DEBUG 1703 dump_name(c, __func__, mode == DEL_WITHDRAW ? "withdraw" : "", c->name); 1704 dump_removal(c, mode); 1705#endif 1706 1707 if (mode == DEL_WITHDRAW) { 1708 set_wm_state(c, WithdrawnState); 1709 XUnmapWindow(dpy, c->frame); 1710 } else { 1711 if (c->state & STATE_ZOOMED) { 1712 c->geom.x = c->save.x; 1713 c->geom.y = c->save.y; 1714 c->geom.w = c->save.w; 1715 c->geom.h = c->save.h; 1716 XResizeWindow(dpy, c->win, c->geom.w, c->geom.h); 1717 } 1718 XMapWindow(dpy, c->win); 1719 XSetWindowBorderWidth(dpy, c->win, c->old_bw); 1720 } 1721 1722 remove_atom(root, net_client_list, XA_WINDOW, c->win); 1723 remove_atom(root, net_client_stack, XA_WINDOW, c->win); 1724 1725 if (c->xftdraw) 1726 XftDrawDestroy(c->xftdraw); 1727 1728 XReparentWindow(dpy, c->win, root, c->geom.x, c->geom.y); 1729 XRemoveFromSaveSet(dpy, c->win); 1730 XDestroyWindow(dpy, c->frame); 1731 1732 if (c->icon_xftdraw) 1733 XftDrawDestroy(c->icon_xftdraw); 1734 if (c->icon) { 1735 XDestroyWindow(dpy, c->icon); 1736 if (c->icon_label) 1737 XDestroyWindow(dpy, c->icon_label); 1738 } 1739 if (c->icon_gc) 1740 XFreeGC(dpy, c->icon_gc); 1741 if (c->icon_managed) { 1742 if (c->icon_pixmap) 1743 XFreePixmap(dpy, c->icon_pixmap); 1744 if (c->icon_mask) 1745 XFreePixmap(dpy, c->icon_mask); 1746 c->icon_managed = 0; 1747 } 1748 1749 if (c->name) 1750 XFree(c->name); 1751 if (c->icon_name) 1752 XFree(c->icon_name); 1753 1754 if (focused == c) { 1755 next = next_client_for_focus(focused); 1756 if (!next) 1757 next = focused->next; 1758 adjust_client_order(c, ORDER_OUT); 1759 if (next) 1760 focus_client(next, FOCUS_FORCE); 1761 else 1762 focused = NULL; 1763 } else 1764 adjust_client_order(c, ORDER_OUT); 1765 1766 free(c); 1767 1768 XSync(dpy, False); 1769 ignore_xerrors--; 1770 XUngrabServer(dpy); 1771} 1772 1773void * 1774word_wrap_xft(char *str, char delim, XftFont *font, int width, int *nlines) 1775{ 1776 XGlyphInfo extents; 1777 struct xft_line_t *lines = NULL; 1778 char *curstr; 1779 int x, lastdelim; 1780 int alloced = 10; 1781 int nline; 1782 1783 lines = realloc(lines, alloced * sizeof(struct xft_line_t)); 1784 if (lines == NULL) 1785 err(1, "realloc"); 1786 1787start_wrap: 1788 nline = 0; 1789 lastdelim = -1; 1790 curstr = str; 1791 1792 for (x = 0; ; x++) { 1793 struct xft_line_t *line = &lines[nline]; 1794 int tx; 1795 1796 if (curstr[x] != delim && curstr[x] != '\n' && 1797 curstr[x] != '\0') 1798 continue; 1799 1800 XftTextExtentsUtf8(dpy, font, (FcChar8 *)curstr, x, &extents); 1801 1802 if (curstr[x] == delim && extents.xOff < width) { 1803 /* keep eating words */ 1804 lastdelim = x; 1805 continue; 1806 } 1807 1808 if (extents.xOff > width) { 1809 if (lastdelim == -1) { 1810 /* 1811 * We can't break this long line, make 1812 * our label width this wide and start 1813 * over, since it may affect previous 1814 * wrapping 1815 */ 1816 width = extents.xOff; 1817 goto start_wrap; 1818 } 1819 x = lastdelim; 1820 } 1821 1822 /* trim leading and trailing spaces */ 1823 tx = x; 1824 while (curstr[tx - 1] == ' ') 1825 tx--; 1826 while (curstr[0] == ' ') { 1827 curstr++; 1828 tx--; 1829 } 1830 XftTextExtentsUtf8(dpy, font, (FcChar8 *)curstr, tx, &extents); 1831 1832 line->str = curstr; 1833 line->len = tx; 1834 line->xft_width = extents.xOff; 1835 1836 if (curstr[x] == '\0') 1837 break; 1838 1839 curstr = curstr + x + 1; 1840 x = 0; 1841 lastdelim = -1; 1842 nline++; 1843 1844 if (nline == alloced) { 1845 alloced += 10; 1846 lines = realloc(lines, 1847 alloced * sizeof(struct xft_line_t)); 1848 if (lines == NULL) 1849 err(1, "realloc"); 1850 } 1851 } 1852 1853 *nlines = nline + 1; 1854 return lines; 1855}