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