progman.exe^H^H^H^H
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}