]> git.lizzy.rs Git - bspwm.git/blob - src/window.c
203f7ee64c74720498c2f8b9917a29d8e51c287e
[bspwm.git] / src / window.c
1 /* Copyright (c) 2012, Bastien Dejean
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice, this
8  *    list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright notice,
10  *    this list of conditions and the following disclaimer in the documentation
11  *    and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <stdbool.h>
28 #include <string.h>
29 #include "bspwm.h"
30 #include "ewmh.h"
31 #include "monitor.h"
32 #include "query.h"
33 #include "rule.h"
34 #include "settings.h"
35 #include "geometry.h"
36 #include "pointer.h"
37 #include "stack.h"
38 #include "tree.h"
39 #include "parse.h"
40 #include "window.h"
41
42 void schedule_window(xcb_window_t win)
43 {
44         coordinates_t loc;
45         uint8_t override_redirect = 0;
46         xcb_get_window_attributes_reply_t *wa = xcb_get_window_attributes_reply(dpy, xcb_get_window_attributes(dpy, win), NULL);
47
48         if (wa != NULL) {
49                 override_redirect = wa->override_redirect;
50                 free(wa);
51         }
52
53         if (override_redirect || locate_window(win, &loc)) {
54                 return;
55         }
56
57         /* ignore pending windows */
58         for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) {
59                 if (pr->win == win) {
60                         return;
61                 }
62         }
63
64         rule_consequence_t *csq = make_rule_conquence();
65         apply_rules(win, csq);
66         if (!schedule_rules(win, csq)) {
67                 manage_window(win, csq, -1);
68                 free(csq);
69         }
70 }
71
72 void manage_window(xcb_window_t win, rule_consequence_t *csq, int fd)
73 {
74         monitor_t *m = mon;
75         desktop_t *d = mon->desk;
76         node_t *f = mon->desk->focus;
77
78         parse_rule_consequence(fd, csq);
79
80         if (ewmh_handle_struts(win)) {
81                 for (monitor_t *m = mon_head; m != NULL; m = m->next) {
82                         arrange(m, m->desk);
83                 }
84         }
85
86         if (!csq->manage) {
87                 free(csq->layer);
88                 free(csq->state);
89                 window_show(win);
90                 return;
91         }
92
93         if (csq->node_desc[0] != '\0') {
94                 coordinates_t ref = {m, d, f};
95                 coordinates_t trg = {NULL, NULL, NULL};
96                 if (node_from_desc(csq->node_desc, &ref, &trg) == SELECTOR_OK) {
97                         m = trg.monitor;
98                         d = trg.desktop;
99                         f = trg.node;
100                 }
101         } else if (csq->desktop_desc[0] != '\0') {
102                 coordinates_t ref = {m, d, NULL};
103                 coordinates_t trg = {NULL, NULL, NULL};
104                 if (desktop_from_desc(csq->desktop_desc, &ref, &trg) == SELECTOR_OK) {
105                         m = trg.monitor;
106                         d = trg.desktop;
107                         f = trg.desktop->focus;
108                 }
109         } else if (csq->monitor_desc[0] != '\0') {
110                 coordinates_t ref = {m, NULL, NULL};
111                 coordinates_t trg = {NULL, NULL, NULL};
112                 if (monitor_from_desc(csq->monitor_desc, &ref, &trg) == SELECTOR_OK) {
113                         m = trg.monitor;
114                         d = trg.monitor->desk;
115                         f = trg.monitor->desk->focus;
116                 }
117         }
118
119         if (csq->sticky) {
120                 m = mon;
121                 d = mon->desk;
122                 f = mon->desk->focus;
123         }
124
125         if (csq->split_dir[0] != '\0' && f != NULL) {
126                 direction_t dir;
127                 if (parse_direction(csq->split_dir, &dir)) {
128                         presel_dir(m, d, f, dir);
129                 }
130         }
131
132         if (csq->split_ratio != 0 && f != NULL) {
133                 presel_ratio(m, d, f, csq->split_ratio);
134         }
135
136         node_t *n = make_node(win);
137         client_t *c = make_client();
138         c->border_width = csq->border ? d->border_width : 0;
139         n->client = c;
140         initialize_client(n);
141         initialize_floating_rectangle(n);
142
143         if (c->floating_rectangle.x == 0 && c->floating_rectangle.y == 0) {
144                 csq->center = true;
145         }
146
147         monitor_t *mm = monitor_from_client(c);
148         embrace_client(mm, c);
149         adapt_geometry(&mm->rectangle, &m->rectangle, n);
150
151         if (csq->center) {
152                 window_center(m, c);
153         }
154
155         snprintf(c->class_name, sizeof(c->class_name), "%s", csq->class_name);
156         snprintf(c->instance_name, sizeof(c->instance_name), "%s", csq->instance_name);
157
158         f = insert_node(m, d, n, f);
159         clients_count++;
160
161         put_status(SBSC_MASK_NODE_MANAGE, "node_manage 0x%08X 0x%08X 0x%08X 0x%08X\n", m->id, d->id, win, f!=NULL?f->id:0);
162
163         if (f != NULL && f->client != NULL && csq->state != NULL && *(csq->state) == STATE_FLOATING) {
164                 c->layer = f->client->layer;
165         }
166
167         if (csq->layer != NULL) {
168                 c->layer = *(csq->layer);
169         }
170
171         if (csq->state != NULL) {
172                 set_state(m, d, n, *(csq->state));
173         }
174
175         set_hidden(m, d, n, csq->hidden);
176         set_sticky(m, d, n, csq->sticky);
177         set_private(m, d, n, csq->private);
178         set_locked(m, d, n, csq->locked);
179
180         arrange(m, d);
181
182         uint32_t values[] = {CLIENT_EVENT_MASK | (focus_follows_pointer ? XCB_EVENT_MASK_ENTER_WINDOW : 0)};
183         xcb_change_window_attributes(dpy, win, XCB_CW_EVENT_MASK, values);
184         set_window_state(win, XCB_ICCCM_WM_STATE_NORMAL);
185         window_grab_buttons(win);
186
187         if (d == m->desk) {
188                 show_node(d, n);
189         } else {
190                 hide_node(d, n);
191         }
192
193         if (!csq->hidden && csq->focus) {
194                 if (d == mon->desk || csq->follow) {
195                         focus_node(m, d, n);
196                 } else {
197                         activate_node(m, d, n);
198                 }
199         } else {
200                 stack(d, n, false);
201                 draw_border(n, false, (m == mon));
202         }
203
204         ewmh_set_wm_desktop(n, d);
205         ewmh_update_client_list(false);
206         free(csq->layer);
207         free(csq->state);
208 }
209
210 void set_window_state(xcb_window_t win, xcb_icccm_wm_state_t state)
211 {
212         long data[] = {state, XCB_NONE};
213         xcb_change_property(dpy, XCB_PROP_MODE_REPLACE, win, WM_STATE, WM_STATE, 32, 2, data);
214 }
215
216 void unmanage_window(xcb_window_t win)
217 {
218         coordinates_t loc;
219         if (locate_window(win, &loc)) {
220                 put_status(SBSC_MASK_NODE_UNMANAGE, "node_unmanage 0x%08X 0x%08X 0x%08X\n", loc.monitor->id, loc.desktop->id, win);
221                 remove_node(loc.monitor, loc.desktop, loc.node);
222                 set_window_state(win, XCB_ICCCM_WM_STATE_WITHDRAWN);
223                 arrange(loc.monitor, loc.desktop);
224         } else {
225                 for (pending_rule_t *pr = pending_rule_head; pr != NULL; pr = pr->next) {
226                         if (pr->win == win) {
227                                 remove_pending_rule(pr);
228                                 return;
229                         }
230                 }
231         }
232 }
233
234 bool is_presel_window(xcb_window_t win)
235 {
236         xcb_icccm_get_wm_class_reply_t reply;
237         bool ret = false;
238         if (xcb_icccm_get_wm_class_reply(dpy, xcb_icccm_get_wm_class(dpy, win), &reply, NULL) == 1) {
239                 if (streq(BSPWM_CLASS_NAME, reply.class_name) && streq(PRESEL_FEEDBACK_I, reply.instance_name)) {
240                         ret = true;
241                 }
242                 xcb_icccm_get_wm_class_reply_wipe(&reply);
243         }
244         return ret;
245 }
246
247 void initialize_presel_feedback(node_t *n)
248 {
249         if (n == NULL || n->presel == NULL || n->presel->feedback != XCB_NONE) {
250                 return;
251         }
252
253         xcb_window_t win = xcb_generate_id(dpy);
254         uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_SAVE_UNDER | XCB_CW_EVENT_MASK;
255         uint32_t values[] = {get_color_pixel(presel_feedback_color), 1, focus_follows_pointer ? XCB_EVENT_MASK_ENTER_WINDOW : 0};
256         xcb_create_window(dpy, XCB_COPY_FROM_PARENT, win, root, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
257                                   XCB_COPY_FROM_PARENT, mask, values);
258
259         xcb_icccm_set_wm_class(dpy, win, sizeof(PRESEL_FEEDBACK_IC), PRESEL_FEEDBACK_IC);
260         window_grab_buttons(win);
261         stacking_list_t *s = stack_tail;
262         while (s != NULL && !IS_TILED(s->node->client)) {
263                 s = s->prev;
264         }
265         if (s != NULL) {
266                 window_above(win, s->node->id);
267         }
268         n->presel->feedback = win;
269 }
270
271 void draw_presel_feedback(monitor_t *m, desktop_t *d, node_t *n)
272 {
273         if (n == NULL || n->presel == NULL || d->layout == LAYOUT_MONOCLE) {
274                 return;
275         }
276
277         bool exists = (n->presel->feedback != XCB_NONE);
278         if (!exists) {
279                 initialize_presel_feedback(n);
280         }
281
282         int gap = gapless_monocle && IS_MONOCLE(d) ? 0 : d->window_gap;
283         presel_t *p = n->presel;
284         xcb_rectangle_t rect = n->rectangle;
285         rect.x = rect.y = 0;
286         rect.width -= gap;
287         rect.height -= gap;
288         xcb_rectangle_t presel_rect = rect;
289
290         switch (p->split_dir) {
291                 case DIR_NORTH:
292                         presel_rect.height = p->split_ratio * rect.height;
293                         break;
294                 case DIR_EAST:
295                         presel_rect.width = (1 - p->split_ratio) * rect.width;
296                         presel_rect.x = rect.width - presel_rect.width;
297                         break;
298                 case DIR_SOUTH:
299                         presel_rect.height = (1 - p->split_ratio) * rect.height;
300                         presel_rect.y = rect.height - presel_rect.height;
301                         break;
302                 case DIR_WEST:
303                         presel_rect.width = p->split_ratio * rect.width;
304                         break;
305         }
306
307         window_move_resize(p->feedback, n->rectangle.x + presel_rect.x, n->rectangle.y + presel_rect.y,
308                            presel_rect.width, presel_rect.height);
309
310         if (!exists && m->desk == d) {
311                 window_show(p->feedback);
312         }
313 }
314
315 void refresh_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n)
316 {
317         if (n == NULL) {
318                 return;
319         } else {
320                 if (n->presel != NULL) {
321                         draw_presel_feedback(m, d, n);
322                 }
323                 refresh_presel_feedbacks(m, d, n->first_child);
324                 refresh_presel_feedbacks(m, d, n->second_child);
325         }
326 }
327
328 void show_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n)
329 {
330         if (n == NULL) {
331                 return;
332         } else {
333                 if (n->presel != NULL) {
334                         window_show(n->presel->feedback);
335                 }
336                 show_presel_feedbacks(m, d, n->first_child);
337                 show_presel_feedbacks(m, d, n->second_child);
338         }
339 }
340
341 void hide_presel_feedbacks(monitor_t *m, desktop_t *d, node_t *n)
342 {
343         if (n == NULL) {
344                 return;
345         } else {
346                 if (n->presel != NULL) {
347                         window_hide(n->presel->feedback);
348                 }
349                 hide_presel_feedbacks(m, d, n->first_child);
350                 hide_presel_feedbacks(m, d, n->second_child);
351         }
352 }
353
354 void update_colors(void)
355 {
356         for (monitor_t *m = mon_head; m != NULL; m = m->next) {
357                 for (desktop_t *d = m->desk_head; d != NULL; d = d->next) {
358                         update_colors_in(d->root, d, m);
359                 }
360         }
361 }
362
363 void update_colors_in(node_t *n, desktop_t *d, monitor_t *m)
364 {
365         if (n == NULL) {
366                 return;
367         } else {
368                 if (n->presel != NULL) {
369                         uint32_t pxl = get_color_pixel(presel_feedback_color);
370                         xcb_change_window_attributes(dpy, n->presel->feedback, XCB_CW_BACK_PIXEL, &pxl);
371                         if (d == m->desk) {
372                                 /* hack to induce back pixel refresh */
373                                 window_hide(n->presel->feedback);
374                                 window_show(n->presel->feedback);
375                         }
376                 }
377                 if (n == d->focus) {
378                         draw_border(n, true, (m == mon));
379                 } else if (n->client != NULL) {
380                         draw_border(n, false, (m == mon));
381                 } else {
382                         update_colors_in(n->first_child, d, m);
383                         update_colors_in(n->second_child, d, m);
384                 }
385         }
386 }
387
388 void draw_border(node_t *n, bool focused_node, bool focused_monitor)
389 {
390         if (n == NULL) {
391                 return;
392         }
393
394         uint32_t border_color_pxl = get_border_color(focused_node, focused_monitor);
395         for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) {
396                 if (f->client != NULL && f->client->border_width > 0) {
397                         window_draw_border(f->id, border_color_pxl);
398                 }
399         }
400 }
401
402 void window_draw_border(xcb_window_t win, uint32_t border_color_pxl)
403 {
404         xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXEL, &border_color_pxl);
405 }
406
407 void adopt_orphans(void)
408 {
409         xcb_query_tree_reply_t *qtr = xcb_query_tree_reply(dpy, xcb_query_tree(dpy, root), NULL);
410         if (qtr == NULL) {
411                 return;
412         }
413
414         int len = xcb_query_tree_children_length(qtr);
415         xcb_window_t *wins = xcb_query_tree_children(qtr);
416
417         for (int i = 0; i < len; i++) {
418                 uint32_t idx;
419                 xcb_window_t win = wins[i];
420                 if (xcb_ewmh_get_wm_desktop_reply(ewmh, xcb_ewmh_get_wm_desktop(ewmh, win), &idx, NULL) == 1) {
421                         schedule_window(win);
422                 }
423         }
424
425         free(qtr);
426 }
427
428 uint32_t get_border_color(bool focused_node, bool focused_monitor)
429 {
430         if (focused_monitor && focused_node) {
431                 return get_color_pixel(focused_border_color);
432         } else if (focused_node) {
433                 return get_color_pixel(active_border_color);
434         } else {
435                 return get_color_pixel(normal_border_color);
436         }
437 }
438
439 void initialize_floating_rectangle(node_t *n)
440 {
441         client_t *c = n->client;
442
443         xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, n->id), NULL);
444
445         if (geo != NULL) {
446                 c->floating_rectangle = (xcb_rectangle_t) {geo->x, geo->y, geo->width, geo->height};
447         }
448
449         free(geo);
450 }
451
452 xcb_rectangle_t get_window_rectangle(node_t *n)
453 {
454         client_t *c = n->client;
455         if (c != NULL) {
456                 xcb_get_geometry_reply_t *g = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, n->id), NULL);
457                 if (g != NULL) {
458                         xcb_rectangle_t rect = (xcb_rectangle_t) {g->x, g->y, g->width, g->height};
459                         free(g);
460                         return rect;
461                 }
462         }
463         return (xcb_rectangle_t) {0, 0, screen_width, screen_height};
464 }
465
466 bool move_client(coordinates_t *loc, int dx, int dy)
467 {
468         node_t *n = loc->node;
469
470         if (n == NULL || n->client == NULL) {
471                 return false;
472         }
473
474         monitor_t *pm = NULL;
475
476         if (IS_TILED(n->client)) {
477                 if (!grabbing) {
478                         return false;
479                 }
480                 xcb_window_t pwin = XCB_NONE;
481                 query_pointer(&pwin, NULL);
482                 if (pwin == n->id) {
483                         return false;
484                 }
485                 coordinates_t dst;
486                 bool is_managed = (pwin != XCB_NONE && locate_window(pwin, &dst));
487                 if (is_managed && dst.monitor == loc->monitor && IS_TILED(dst.node->client)) {
488                         swap_nodes(loc->monitor, loc->desktop, n, loc->monitor, loc->desktop, dst.node);
489                         return true;
490                 } else {
491                         if (is_managed && dst.monitor == loc->monitor) {
492                                 return false;
493                         } else {
494                                 xcb_point_t pt = {0, 0};
495                                 query_pointer(NULL, &pt);
496                                 pm = monitor_from_point(pt);
497                         }
498                 }
499         } else {
500                 client_t *c = n->client;
501                 xcb_rectangle_t rect = c->floating_rectangle;
502                 int16_t x = rect.x + dx;
503                 int16_t y = rect.y + dy;
504
505                 window_move(n->id, x, y);
506
507                 c->floating_rectangle.x = x;
508                 c->floating_rectangle.y = y;
509                 if (!grabbing) {
510                         put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc->monitor->id, loc->desktop->id, loc->node->id, rect.width, rect.height, x, y);
511                 }
512                 pm = monitor_from_client(c);
513         }
514
515         if (pm == NULL || pm == loc->monitor) {
516                 return true;
517         }
518
519         bool focused = (n == mon->desk->focus);
520         transfer_node(loc->monitor, loc->desktop, n, pm, pm->desk, pm->desk->focus);
521         loc->monitor = pm;
522         loc->desktop = pm->desk;
523         if (focused) {
524                 focus_node(pm, pm->desk, n);
525         }
526
527         return true;
528 }
529
530 bool resize_client(coordinates_t *loc, resize_handle_t rh, int dx, int dy)
531 {
532         node_t *n = loc->node;
533         if (n == NULL || n->client == NULL || n->client->state == STATE_FULLSCREEN) {
534                 return false;
535         }
536         node_t *horizontal_fence = NULL, *vertical_fence = NULL;
537         xcb_rectangle_t rect = get_rectangle(NULL, NULL, n);
538         uint16_t width = rect.width, height = rect.height;
539         int16_t x = rect.x, y = rect.y;
540         if (n->client->state == STATE_TILED) {
541                 if (rh & HANDLE_LEFT) {
542                         vertical_fence = find_fence(n, DIR_WEST);
543                 } else if (rh & HANDLE_RIGHT) {
544                         vertical_fence = find_fence(n, DIR_EAST);
545                 }
546                 if (rh & HANDLE_TOP) {
547                         horizontal_fence = find_fence(n, DIR_NORTH);
548                 } else if (rh & HANDLE_BOTTOM) {
549                         horizontal_fence = find_fence(n, DIR_SOUTH);
550                 }
551                 if (vertical_fence == NULL && horizontal_fence == NULL) {
552                         return false;
553                 }
554                 if (vertical_fence != NULL) {
555                         double sr = vertical_fence->split_ratio + (double) dx / vertical_fence->rectangle.width;
556                         sr = MAX(0, sr);
557                         sr = MIN(1, sr);
558                         vertical_fence->split_ratio = sr;
559                 }
560                 if (horizontal_fence != NULL) {
561                         double sr = horizontal_fence->split_ratio + (double) dy / horizontal_fence->rectangle.height;
562                         sr = MAX(0, sr);
563                         sr = MIN(1, sr);
564                         horizontal_fence->split_ratio = sr;
565                 }
566                 arrange(loc->monitor, loc->desktop);
567         } else {
568                 int w = width + dx * (rh & HANDLE_LEFT ? -1 : (rh & HANDLE_RIGHT ? 1 : 0));
569                 int h = height + dy * (rh & HANDLE_TOP ? -1 : (rh & HANDLE_BOTTOM ? 1 : 0));
570                 width = MAX(1, w);
571                 height = MAX(1, h);
572                 apply_size_hints(n->client, &width, &height);
573                 if (rh & HANDLE_LEFT) {
574                         x += rect.width - width;
575                 }
576                 if (rh & HANDLE_TOP) {
577                         y += rect.height - height;
578                 }
579                 n->client->floating_rectangle = (xcb_rectangle_t) {x, y, width, height};
580                 if (n->client->state == STATE_FLOATING) {
581                         window_move_resize(n->id, x, y, width, height);
582
583                         if (!grabbing) {
584                                 put_status(SBSC_MASK_NODE_GEOMETRY, "node_geometry 0x%08X 0x%08X 0x%08X %ux%u+%i+%i\n", loc->monitor->id, loc->desktop->id, loc->node->id, width, height, x, y);
585                         }
586                 } else {
587                         arrange(loc->monitor, loc->desktop);
588                 }
589         }
590         return true;
591 }
592
593 /* taken from awesomeWM */
594 void apply_size_hints(client_t *c, uint16_t *width, uint16_t *height)
595 {
596         if (!honor_size_hints) {
597                 return;
598         }
599
600         int32_t minw = 0, minh = 0;
601         int32_t basew = 0, baseh = 0, real_basew = 0, real_baseh = 0;
602
603         if (c->state == STATE_FULLSCREEN) {
604                 return;
605         }
606
607         if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
608                 basew = c->size_hints.base_width;
609                 baseh = c->size_hints.base_height;
610                 real_basew = basew;
611                 real_baseh = baseh;
612         } else if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
613                 /* base size is substituted with min size if not specified */
614                 basew = c->size_hints.min_width;
615                 baseh = c->size_hints.min_height;
616         }
617
618         if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
619                 minw = c->size_hints.min_width;
620                 minh = c->size_hints.min_height;
621         } else if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
622                 /* min size is substituted with base size if not specified */
623                 minw = c->size_hints.base_width;
624                 minh = c->size_hints.base_height;
625         }
626
627         /* Handle the size aspect ratio */
628         if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT &&
629             c->size_hints.min_aspect_den > 0 &&
630             c->size_hints.max_aspect_den > 0 &&
631             *height > real_baseh &&
632             *width > real_basew) {
633                 /* ICCCM mandates:
634                  * If a base size is provided along with the aspect ratio fields, the base size should be subtracted from the
635                  * window size prior to checking that the aspect ratio falls in range. If a base size is not provided, nothing
636                  * should be subtracted from the window size. (The minimum size is not to be used in place of the base size for
637                  * this purpose.)
638                  */
639                 double dx = *width - real_basew;
640                 double dy = *height - real_baseh;
641                 double ratio = dx / dy;
642                 double min = c->size_hints.min_aspect_num / (double) c->size_hints.min_aspect_den;
643                 double max = c->size_hints.max_aspect_num / (double) c->size_hints.max_aspect_den;
644
645                 if (max > 0 && min > 0 && ratio > 0) {
646                         if (ratio < min) {
647                                 /* dx is lower than allowed, make dy lower to compensate this (+ 0.5 to force proper rounding). */
648                                 dy = dx / min + 0.5;
649                                 *width  = dx + real_basew;
650                                 *height = dy + real_baseh;
651                         } else if (ratio > max) {
652                                 /* dx is too high, lower it (+0.5 for proper rounding) */
653                                 dx = dy * max + 0.5;
654                                 *width  = dx + real_basew;
655                                 *height = dy + real_baseh;
656                         }
657                 }
658         }
659
660         /* Handle the minimum size */
661         *width = MAX(*width, minw);
662         *height = MAX(*height, minh);
663
664         /* Handle the maximum size */
665         if (c->size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)
666         {
667                 if (c->size_hints.max_width > 0) {
668                         *width = MIN(*width, c->size_hints.max_width);
669                 }
670                 if (c->size_hints.max_height > 0) {
671                         *height = MIN(*height, c->size_hints.max_height);
672                 }
673         }
674
675         /* Handle the size increment */
676         if (c->size_hints.flags & (XCB_ICCCM_SIZE_HINT_P_RESIZE_INC | XCB_ICCCM_SIZE_HINT_BASE_SIZE) &&
677             c->size_hints.width_inc > 0 && c->size_hints.height_inc > 0) {
678                 uint16_t t1 = *width, t2 = *height;
679                 unsigned_subtract(t1, basew);
680                 unsigned_subtract(t2, baseh);
681                 *width -= t1 % c->size_hints.width_inc;
682                 *height -= t2 % c->size_hints.height_inc;
683         }
684 }
685
686 void query_pointer(xcb_window_t *win, xcb_point_t *pt)
687 {
688         if (motion_recorder.enabled) {
689                 window_hide(motion_recorder.id);
690         }
691
692         xcb_query_pointer_reply_t *qpr = xcb_query_pointer_reply(dpy, xcb_query_pointer(dpy, root), NULL);
693
694         if (qpr != NULL) {
695                 if (win != NULL) {
696                         *win = qpr->child;
697                         xcb_point_t pt = {qpr->root_x, qpr->root_y};
698                         for (stacking_list_t *s = stack_tail; s != NULL; s = s->prev) {
699                                 if (!s->node->client->shown || s->node->hidden) {
700                                         continue;
701                                 }
702                                 xcb_rectangle_t rect = get_rectangle(NULL, NULL, s->node);
703                                 if (is_inside(pt, rect)) {
704                                         if (s->node->id == qpr->child || is_presel_window(qpr->child)) {
705                                                 *win = s->node->id;
706                                         }
707                                         break;
708                                 }
709                         }
710                 }
711                 if (pt != NULL) {
712                         *pt = (xcb_point_t) {qpr->root_x, qpr->root_y};
713                 }
714         }
715
716         free(qpr);
717
718         if (motion_recorder.enabled) {
719                 window_show(motion_recorder.id);
720         }
721 }
722
723 void update_motion_recorder(void)
724 {
725         xcb_point_t pt;
726         xcb_window_t win = XCB_NONE;
727         query_pointer(&win, &pt);
728         if (win == XCB_NONE) {
729                 return;
730         }
731         monitor_t *m = monitor_from_point(pt);
732         if (m == NULL) {
733                 return;
734         }
735         desktop_t *d = m->desk;
736         node_t *n = NULL;
737         for (n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) {
738                 if (n->id == win || (n->presel != NULL && n->presel->feedback == win)) {
739                         break;
740                 }
741         }
742         if ((n != NULL && n != mon->desk->focus) || (n == NULL && m != mon)) {
743                 enable_motion_recorder(win);
744         } else {
745                 disable_motion_recorder();
746         }
747 }
748
749 void enable_motion_recorder(xcb_window_t win)
750 {
751         xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL);
752         if (geo != NULL) {
753                 uint16_t width = geo->width + 2 * geo->border_width;
754                 uint16_t height = geo->height + 2 * geo->border_width;
755                 window_move_resize(motion_recorder.id, geo->x, geo->y, width, height);
756                 window_above(motion_recorder.id, win);
757                 window_show(motion_recorder.id);
758                 motion_recorder.enabled = true;
759         }
760         free(geo);
761 }
762
763 void disable_motion_recorder(void)
764 {
765         if (!motion_recorder.enabled) {
766                 return;
767         }
768         window_hide(motion_recorder.id);
769         motion_recorder.enabled = false;
770 }
771
772 void window_border_width(xcb_window_t win, uint32_t bw)
773 {
774         uint32_t values[] = {bw};
775         xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_BORDER_WIDTH, values);
776 }
777
778 void window_move(xcb_window_t win, int16_t x, int16_t y)
779 {
780         uint32_t values[] = {x, y};
781         xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y, values);
782 }
783
784 void window_resize(xcb_window_t win, uint16_t w, uint16_t h)
785 {
786         uint32_t values[] = {w, h};
787         xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_WIDTH_HEIGHT, values);
788 }
789
790 void window_move_resize(xcb_window_t win, int16_t x, int16_t y, uint16_t w, uint16_t h)
791 {
792         uint32_t values[] = {x, y, w, h};
793         xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y_WIDTH_HEIGHT, values);
794 }
795
796 void window_center(monitor_t *m, client_t *c)
797 {
798         xcb_rectangle_t *r = &c->floating_rectangle;
799         xcb_rectangle_t a = m->rectangle;
800         if (r->width >= a.width) {
801                 r->x = a.x;
802         } else {
803                 r->x = a.x + (a.width - r->width) / 2;
804         }
805         if (r->height >= a.height) {
806                 r->y = a.y;
807         } else {
808                 r->y = a.y + (a.height - r->height) / 2;
809         }
810         r->x -= c->border_width;
811         r->y -= c->border_width;
812 }
813
814 void window_stack(xcb_window_t w1, xcb_window_t w2, uint32_t mode)
815 {
816         if (w2 == XCB_NONE) {
817                 return;
818         }
819         uint16_t mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
820         uint32_t values[] = {w2, mode};
821         xcb_configure_window(dpy, w1, mask, values);
822 }
823
824 /* Stack w1 above w2 */
825 void window_above(xcb_window_t w1, xcb_window_t w2)
826 {
827         window_stack(w1, w2, XCB_STACK_MODE_ABOVE);
828 }
829
830 /* Stack w1 below w2 */
831 void window_below(xcb_window_t w1, xcb_window_t w2)
832 {
833         window_stack(w1, w2, XCB_STACK_MODE_BELOW);
834 }
835
836 void window_lower(xcb_window_t win)
837 {
838         uint32_t values[] = {XCB_STACK_MODE_BELOW};
839         xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
840 }
841
842 void window_set_visibility(xcb_window_t win, bool visible)
843 {
844         uint32_t values_off[] = {ROOT_EVENT_MASK & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY};
845         uint32_t values_on[] = {ROOT_EVENT_MASK};
846         xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_off);
847         if (visible) {
848                 xcb_map_window(dpy, win);
849         } else {
850                 xcb_unmap_window(dpy, win);
851         }
852         xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_on);
853 }
854
855 void window_hide(xcb_window_t win)
856 {
857         window_set_visibility(win, false);
858 }
859
860 void window_show(xcb_window_t win)
861 {
862         window_set_visibility(win, true);
863 }
864
865 void update_input_focus(void)
866 {
867         set_input_focus(mon->desk->focus);
868 }
869
870 void set_input_focus(node_t *n)
871 {
872         if (n == NULL || n->client == NULL) {
873                 clear_input_focus();
874         } else {
875                 if (n->client->icccm_props.input_hint) {
876                         xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_PARENT, n->id, XCB_CURRENT_TIME);
877                 } else if (n->client->icccm_props.take_focus) {
878                         send_client_message(n->id, ewmh->WM_PROTOCOLS, WM_TAKE_FOCUS);
879                 }
880         }
881 }
882
883 void clear_input_focus(void)
884 {
885         xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
886 }
887
888 void center_pointer(xcb_rectangle_t r)
889 {
890         if (grabbing) {
891                 return;
892         }
893         int16_t cx = r.x + r.width / 2;
894         int16_t cy = r.y + r.height / 2;
895         xcb_warp_pointer(dpy, XCB_NONE, root, 0, 0, 0, 0, cx, cy);
896 }
897
898 void get_atom(char *name, xcb_atom_t *atom)
899 {
900         xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(dpy, xcb_intern_atom(dpy, 0, strlen(name), name), NULL);
901         if (reply != NULL) {
902                 *atom = reply->atom;
903         } else {
904                 *atom = XCB_NONE;
905         }
906         free(reply);
907 }
908
909 void set_atom(xcb_window_t win, xcb_atom_t atom, uint32_t value)
910 {
911         xcb_change_property(dpy, XCB_PROP_MODE_REPLACE, win, atom, XCB_ATOM_CARDINAL, 32, 1, &value);
912 }
913
914 void send_client_message(xcb_window_t win, xcb_atom_t property, xcb_atom_t value)
915 {
916         xcb_client_message_event_t *e = calloc(32, 1);
917
918         e->response_type = XCB_CLIENT_MESSAGE;
919         e->window = win;
920         e->type = property;
921         e->format = 32;
922         e->data.data32[0] = value;
923         e->data.data32[1] = XCB_CURRENT_TIME;
924
925         xcb_send_event(dpy, false, win, XCB_EVENT_MASK_NO_EVENT, (char *) e);
926         xcb_flush(dpy);
927         free(e);
928 }