]> git.lizzy.rs Git - bspwm.git/blob - window.c
New setting: `honor_ewmh_focus`
[bspwm.git] / window.c
1 #include <stdlib.h>
2 #include <string.h>
3 #include "types.h"
4 #include "monitor.h"
5 #include "tree.h"
6 #include "bspwm.h"
7 #include "settings.h"
8 #include "ewmh.h"
9 #include "query.h"
10 #include "rule.h"
11 #include "window.h"
12
13 pointer_state_t *make_pointer_state(void)
14 {
15     pointer_state_t *p = malloc(sizeof(pointer_state_t));
16     p->monitor = NULL;
17     p->desktop = NULL;
18     p->node = p->vertical_fence = p->horizontal_fence = NULL;
19     p->client = NULL;
20     p->window = XCB_NONE;
21     return p;
22 }
23
24 void center(xcb_rectangle_t a, xcb_rectangle_t *b)
25 {
26     if (b->width < a.width)
27         b->x = a.x + (a.width - b->width) / 2;
28     if (b->height < a.height)
29         b->y = a.y + (a.height - b->height) / 2;
30 }
31
32 bool contains(xcb_rectangle_t a, xcb_rectangle_t b)
33 {
34     return (a.x <= b.x && (a.x + a.width) >= (b.x + b.width)
35             && a.y <= b.y && (a.y + a.height) >= (b.y + b.height));
36 }
37
38 bool is_inside(monitor_t *m, xcb_point_t pt)
39 {
40     xcb_rectangle_t r = m->rectangle;
41     return (r.x <= pt.x && pt.x < (r.x + r.width)
42             && r.y <= pt.y && pt.y < (r.y + r.height));
43 }
44
45 xcb_rectangle_t get_rectangle(client_t *c)
46 {
47     if (is_tiled(c))
48         return c->tiled_rectangle;
49     else
50         return c->floating_rectangle;
51 }
52
53 void get_side_handle(client_t *c, direction_t dir, xcb_point_t *pt)
54 {
55     xcb_rectangle_t rect = get_rectangle(c);
56     switch (dir) {
57         case DIR_RIGHT:
58             pt->x = rect.x + rect.width;
59             pt->y = rect.y + (rect.height / 2);
60             break;
61         case DIR_DOWN:
62             pt->x = rect.x + (rect.width / 2);
63             pt->y = rect.y + rect.height;
64             break;
65         case DIR_LEFT:
66             pt->x = rect.x;
67             pt->y = rect.y + (rect.height / 2);
68             break;
69         case DIR_UP:
70             pt->x = rect.x + (rect.width / 2);
71             pt->y = rect.y;
72             break;
73     }
74 }
75
76 monitor_t *monitor_from_point(xcb_point_t pt)
77 {
78     for (monitor_t *m = mon_head; m != NULL; m = m->next)
79         if (is_inside(m, pt))
80             return m;
81     return NULL;
82 }
83
84 monitor_t *underlying_monitor(client_t *c)
85 {
86     xcb_point_t pt = (xcb_point_t) {c->floating_rectangle.x, c->floating_rectangle.y};
87     return monitor_from_point(pt);
88 }
89
90 void manage_window(monitor_t *m, desktop_t *d, xcb_window_t win)
91 {
92     coordinates_t loc;
93     xcb_get_window_attributes_reply_t *wa = xcb_get_window_attributes_reply(dpy, xcb_get_window_attributes(dpy, win), NULL);
94     uint8_t override_redirect = 0;
95
96     if (wa != NULL) {
97         override_redirect = wa->override_redirect;
98         free(wa);
99     }
100
101     if (override_redirect || locate_window(win, &loc))
102         return;
103
104     bool floating = false, fullscreen = false, locked = false, follow = false, transient = false, takes_focus = true, manage = true;
105     handle_rules(win, &m, &d, &floating, &fullscreen, &locked, &follow, &transient, &takes_focus, &manage);
106
107     if (!manage) {
108         disable_floating_atom(win);
109         window_show(win);
110         return;
111     }
112
113     PRINTF("manage %X\n", win);
114
115     client_t *c = make_client(win);
116     update_floating_rectangle(c);
117
118     xcb_icccm_get_wm_class_reply_t reply;
119     if (xcb_icccm_get_wm_class_reply(dpy, xcb_icccm_get_wm_class(dpy, win), &reply, NULL) == 1) {
120         snprintf(c->class_name, sizeof(c->class_name), "%s", reply.class_name);
121         xcb_icccm_get_wm_class_reply_wipe(&reply);
122     }
123
124     if (c->transient)
125         floating = true;
126
127     node_t *n = make_node();
128     n->client = c;
129
130     insert_node(m, d, n, d->focus);
131
132     disable_floating_atom(c->window);
133     set_floating(d, n, floating);
134     set_locked(m, d, n, locked);
135
136     if (d->focus != NULL && d->focus->client->fullscreen)
137         set_fullscreen(d, d->focus, false);
138
139     set_fullscreen(d, n, fullscreen);
140
141     c->transient = transient;
142
143     bool give_focus = (takes_focus && (d == mon->desk || follow));
144     if (give_focus) {
145         focus_node(m, d, n);
146     } else if (takes_focus) {
147         pseudo_focus(d, n);
148     } else {
149         node_t *f = d->focus;
150         pseudo_focus(d, n);
151         if (f != NULL)
152             pseudo_focus(d, f);
153     }
154
155     xcb_rectangle_t *frect = &n->client->floating_rectangle;
156     if (frect->x == 0 && frect->y == 0)
157         center(m->rectangle, frect);
158
159     fit_monitor(m, n->client);
160
161     arrange(m, d);
162
163     if (d == m->desk && visible)
164         window_show(c->window);
165     else
166         window_hide(c->window);
167
168     /* the same function is already called in `focus_node` but has no effects on unmapped windows */
169     if (give_focus)
170         xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
171
172     uint32_t values[] = {(focus_follows_pointer ? CLIENT_EVENT_MASK_FFP : CLIENT_EVENT_MASK)};
173     xcb_change_window_attributes(dpy, c->window, XCB_CW_EVENT_MASK, values);
174
175     num_clients++;
176     ewmh_set_wm_desktop(n, d);
177     ewmh_update_client_list();
178 }
179
180 void adopt_orphans(void)
181 {
182     xcb_query_tree_reply_t *qtr = xcb_query_tree_reply(dpy, xcb_query_tree(dpy, root), NULL);
183     if (qtr == NULL)
184         return;
185
186     PUTS("adopt orphans");
187
188     int len = xcb_query_tree_children_length(qtr);
189     xcb_window_t *wins = xcb_query_tree_children(qtr);
190     for (int i = 0; i < len; i++) {
191         uint32_t idx;
192         xcb_window_t win = wins[i];
193         if (xcb_ewmh_get_wm_desktop_reply(ewmh, xcb_ewmh_get_wm_desktop(ewmh, win), &idx, NULL) == 1) {
194             coordinates_t loc;
195             if (ewmh_locate_desktop(idx, &loc))
196                 manage_window(loc.monitor, loc.desktop, win);
197             else
198                 manage_window(mon, mon->desk, win);
199         }
200     }
201     free(qtr);
202 }
203
204 void window_draw_border(node_t *n, bool focused_window, bool focused_monitor)
205 {
206     if (n == NULL || border_width < 1 || n->client->border_width < 1)
207         return;
208
209     xcb_window_t win = n->client->window;
210     uint32_t border_color_pxl = get_border_color(n->client, focused_window, focused_monitor);
211
212     if (n->split_mode == MODE_AUTOMATIC) {
213         xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXEL, &border_color_pxl);
214     } else {
215         uint32_t presel_border_color_pxl;
216         get_color(presel_border_color, win, &presel_border_color_pxl);
217
218         xcb_rectangle_t actual_rectangle = get_rectangle(n->client);
219
220         uint16_t width = actual_rectangle.width;
221         uint16_t height = actual_rectangle.height;
222
223         uint16_t full_width = width + 2 * border_width;
224         uint16_t full_height = height + 2 * border_width;
225
226         xcb_rectangle_t border_rectangles[] =
227         {
228             { width, 0, 2 * border_width, height + 2 * border_width },
229             { 0, height, width + 2 * border_width, 2 * border_width }
230         };
231
232         xcb_rectangle_t *presel_rectangles;
233
234         uint8_t win_depth = root_depth;
235         xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL);
236         if (geo != NULL)
237             win_depth = geo->depth;
238         free(geo);
239
240         xcb_pixmap_t pixmap = xcb_generate_id(dpy);
241         xcb_create_pixmap(dpy, win_depth, pixmap, win, full_width, full_height);
242
243         xcb_gcontext_t gc = xcb_generate_id(dpy);
244         xcb_create_gc(dpy, gc, pixmap, 0, NULL);
245
246         xcb_change_gc(dpy, gc, XCB_GC_FOREGROUND, &border_color_pxl);
247         xcb_poly_fill_rectangle(dpy, pixmap, gc, LENGTH(border_rectangles), border_rectangles);
248
249         uint16_t fence = (int16_t) (n->split_ratio * ((n->split_dir == DIR_UP || n->split_dir == DIR_DOWN) ? height : width));
250         presel_rectangles = malloc(2 * sizeof(xcb_rectangle_t));
251         switch (n->split_dir) {
252             case DIR_UP:
253                 presel_rectangles[0] = (xcb_rectangle_t) {width, 0, 2 * border_width, fence};
254                 presel_rectangles[1] = (xcb_rectangle_t) {0, height + border_width, full_width, border_width};
255                 break;
256             case DIR_DOWN:
257                 presel_rectangles[0] = (xcb_rectangle_t) {width, fence + 1, 2 * border_width, height + border_width - (fence + 1)};
258                 presel_rectangles[1] = (xcb_rectangle_t) {0, height, full_width, border_width};
259                 break;
260             case DIR_LEFT:
261                 presel_rectangles[0] = (xcb_rectangle_t) {0, height, fence, 2 * border_width};
262                 presel_rectangles[1] = (xcb_rectangle_t) {width + border_width, 0, border_width, full_height};
263                 break;
264             case DIR_RIGHT:
265                 presel_rectangles[0] = (xcb_rectangle_t) {fence + 1, height, width + border_width - (fence + 1), 2 * border_width};
266                 presel_rectangles[1] = (xcb_rectangle_t) {width, 0, border_width, full_height};
267                 break;
268         }
269         xcb_change_gc(dpy, gc, XCB_GC_FOREGROUND, &presel_border_color_pxl);
270         xcb_poly_fill_rectangle(dpy, pixmap, gc, 2, presel_rectangles);
271         xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXMAP, &pixmap);
272         free(presel_rectangles);
273         xcb_free_gc(dpy, gc);
274         xcb_free_pixmap(dpy, pixmap);
275     }
276 }
277
278 void window_close(node_t *n)
279 {
280     if (n == NULL || n->client->locked)
281         return;
282
283     PRINTF("close window %X\n", n->client->window);
284
285     send_client_message(n->client->window, ewmh->WM_PROTOCOLS, WM_DELETE_WINDOW);
286 }
287
288 void window_kill(desktop_t *d, node_t *n)
289 {
290     if (n == NULL)
291         return;
292
293     xcb_window_t win = n->client->window;
294     PRINTF("kill window %X\n", win);
295
296     xcb_kill_client(dpy, win);
297     remove_node(d, n);
298 }
299
300 void set_fullscreen(desktop_t *d, node_t *n, bool value)
301 {
302     if (n == NULL || n->client->fullscreen == value)
303         return;
304
305     client_t *c = n->client;
306
307     PRINTF("fullscreen %X: %s\n", c->window, BOOLSTR(value));
308
309     if (value) {
310         c->fullscreen = true;
311         xcb_atom_t values[] = {ewmh->_NET_WM_STATE_FULLSCREEN};
312         xcb_ewmh_set_wm_state(ewmh, c->window, LENGTH(values), values);
313     } else {
314         c->fullscreen = false;
315         xcb_atom_t values[] = {XCB_NONE};
316         xcb_ewmh_set_wm_state(ewmh, c->window, LENGTH(values), values);
317     }
318
319     stack(d, n);
320 }
321
322 void set_floating(desktop_t *d, node_t *n, bool value)
323 {
324     if (n == NULL || n->client->transient || n->client->fullscreen || n->client->floating == value)
325         return;
326
327     PRINTF("floating %X: %s\n", n->client->window, BOOLSTR(value));
328
329     n->split_mode = MODE_AUTOMATIC;
330     client_t *c = n->client;
331     c->floating = n->vacant = value;
332     update_vacant_state(n->parent);
333
334     if (value) {
335         enable_floating_atom(c->window);
336         unrotate_brother(n);
337     } else {
338         disable_floating_atom(c->window);
339         rotate_brother(n);
340     }
341
342     stack(d, n);
343 }
344
345 void set_locked(monitor_t *m, desktop_t *d, node_t *n, bool value)
346 {
347     if (n == NULL || n->client->locked == value)
348         return;
349
350     client_t *c = n->client;
351
352     PRINTF("set locked %X: %s\n", c->window, BOOLSTR(value));
353
354     c->locked = value;
355     window_draw_border(n, d->focus == n, m == mon);
356 }
357
358 void set_urgency(monitor_t *m, desktop_t *d, node_t *n, bool value)
359 {
360     if (value && mon->desk->focus == n)
361         return;
362     n->client->urgent = value;
363     window_draw_border(n, d->focus == n, m == mon);
364     put_status();
365 }
366
367 void set_floating_atom(xcb_window_t win, uint32_t value)
368 {
369     if (!apply_floating_atom)
370         return;
371     set_atom(win, _BSPWM_FLOATING_WINDOW, value);
372 }
373
374 void enable_floating_atom(xcb_window_t win)
375 {
376     set_floating_atom(win, 1);
377 }
378
379 void disable_floating_atom(xcb_window_t win)
380 {
381     set_floating_atom(win, 0);
382 }
383
384 uint32_t get_border_color(client_t *c, bool focused_window, bool focused_monitor)
385 {
386     if (c == NULL)
387         return 0;
388
389     uint32_t pxl = 0;
390
391     if (focused_monitor && focused_window) {
392         if (c->locked)
393             get_color(focused_locked_border_color, c->window, &pxl);
394         else
395             get_color(focused_border_color, c->window, &pxl);
396     } else if (focused_window) {
397         if (c->urgent)
398             get_color(urgent_border_color, c->window, &pxl);
399         else if (c->locked)
400             get_color(active_locked_border_color, c->window, &pxl);
401         else
402             get_color(active_border_color, c->window, &pxl);
403     } else {
404         if (c->urgent)
405             get_color(urgent_border_color, c->window, &pxl);
406         else if (c->locked)
407             get_color(normal_locked_border_color, c->window, &pxl);
408         else
409             get_color(normal_border_color, c->window, &pxl);
410     }
411
412     return pxl;
413 }
414
415 void update_floating_rectangle(client_t *c)
416 {
417     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, c->window), NULL);
418
419     if (geo != NULL)
420         c->floating_rectangle = (xcb_rectangle_t) {geo->x, geo->y, geo->width, geo->height};
421     else
422         c->floating_rectangle = (xcb_rectangle_t) {0, 0, 32, 24};
423
424     free(geo);
425 }
426
427
428 void query_pointer(xcb_window_t *win, xcb_point_t *pt)
429 {
430     window_lower(motion_recorder);
431
432     xcb_query_pointer_reply_t *qpr = xcb_query_pointer_reply(dpy, xcb_query_pointer(dpy, root), NULL);
433
434     if (qpr != NULL) {
435         if (win != NULL)
436             *win = qpr->child;
437         if (pt != NULL)
438             *pt = (xcb_point_t) {qpr->root_x, qpr->root_y};
439         free(qpr);
440     }
441
442     window_raise(motion_recorder);
443 }
444
445 bool window_focus(xcb_window_t win)
446 {
447     coordinates_t loc;
448     if (locate_window(win, &loc)) {
449         if (loc.node != mon->desk->focus)
450             focus_node(loc.monitor, loc.desktop, loc.node);
451         return true;
452     }
453     return false;
454 }
455
456 void window_border_width(xcb_window_t win, uint32_t bw)
457 {
458     uint32_t values[] = {bw};
459     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_BORDER_WIDTH, values);
460 }
461
462 void window_move(xcb_window_t win, int16_t x, int16_t y)
463 {
464     uint32_t values[] = {x, y};
465     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y, values);
466 }
467
468 void window_resize(xcb_window_t win, uint16_t w, uint16_t h)
469 {
470     uint32_t values[] = {w, h};
471     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_WIDTH_HEIGHT, values);
472 }
473
474 void window_move_resize(xcb_window_t win, int16_t x, int16_t y, uint16_t w, uint16_t h)
475 {
476     uint32_t values[] = {x, y, w, h};
477     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y_WIDTH_HEIGHT, values);
478 }
479
480 void window_raise(xcb_window_t win)
481 {
482     uint32_t values[] = {XCB_STACK_MODE_ABOVE};
483     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
484 }
485
486 void window_stack(xcb_window_t w1, xcb_window_t w2, uint32_t mode)
487 {
488     if (w2 == XCB_NONE)
489         return;
490     uint16_t mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
491     uint32_t values[] = {w2, mode};
492     xcb_configure_window(dpy, w1, mask, values);
493 }
494
495 void window_above(xcb_window_t w1, xcb_window_t w2)
496 {
497     window_stack(w1, w2, XCB_STACK_MODE_ABOVE);
498 }
499
500 void window_below(xcb_window_t w1, xcb_window_t w2)
501 {
502     window_stack(w1, w2, XCB_STACK_MODE_BELOW);
503 }
504
505 void stack(desktop_t *d, node_t *n)
506 {
507     if (is_leaf(d->root))
508         return;
509     if (n->client->fullscreen) {
510         window_raise(n->client->window);
511     } else {
512         if (n->client->floating && !auto_raise)
513             return;
514         xcb_window_t latest_tiled = XCB_NONE;
515         xcb_window_t oldest_floating = XCB_NONE;
516         for (node_list_t *a = d->history->head; a != NULL; a = a->next) {
517             if (a->latest && a->node != n) {
518                 if (a->node->client->floating == n->client->floating) {
519                     window_above(n->client->window, a->node->client->window);
520                     return;
521                 } else if (latest_tiled == XCB_NONE && !a->node->client->floating) {
522                     latest_tiled = a->node->client->window;
523                 } else if (a->node->client->floating) {
524                     oldest_floating = a->node->client->window;
525                 }
526             }
527         }
528         if (n->client->floating)
529             window_above(n->client->window, latest_tiled);
530         else
531             window_below(n->client->window, oldest_floating);
532     }
533 }
534
535 void window_lower(xcb_window_t win)
536 {
537     uint32_t values[] = {XCB_STACK_MODE_BELOW};
538     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
539 }
540
541 void window_set_visibility(xcb_window_t win, bool visible)
542 {
543     uint32_t values_off[] = {ROOT_EVENT_MASK & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY};
544     uint32_t values_on[] = {ROOT_EVENT_MASK};
545     xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_off);
546     if (visible)
547         xcb_map_window(dpy, win);
548     else
549         xcb_unmap_window(dpy, win);
550     xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_on);
551 }
552
553 void window_hide(xcb_window_t win)
554 {
555     window_set_visibility(win, false);
556 }
557
558 void window_show(xcb_window_t win)
559 {
560     window_set_visibility(win, true);
561 }
562
563 void toggle_visibility(void)
564 {
565     visible = !visible;
566     if (!visible)
567         clear_input_focus();
568     for (monitor_t *m = mon_head; m != NULL; m = m->next)
569         for (node_t *n = first_extrema(m->desk->root); n != NULL; n = next_leaf(n, m->desk->root))
570             window_set_visibility(n->client->window, visible);
571     if (visible)
572         update_input_focus();
573 }
574
575 void enable_motion_recorder(void)
576 {
577     PUTS("enable motion recorder");
578     window_raise(motion_recorder);
579     window_show(motion_recorder);
580 }
581
582 void disable_motion_recorder(void)
583 {
584     PUTS("disable motion recorder");
585     window_hide(motion_recorder);
586 }
587
588 void update_motion_recorder(void)
589 {
590     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, root), NULL);
591
592     if (geo != NULL) {
593         window_resize(motion_recorder, geo->width, geo->height);
594         PRINTF("update motion recorder size: %ux%u\n", geo->width, geo->height);
595     }
596
597     free(geo);
598 }
599
600 void update_input_focus(void)
601 {
602     set_input_focus(mon->desk->focus);
603 }
604
605 void set_input_focus(node_t *n)
606 {
607     if (n == NULL) {
608         clear_input_focus();
609     } else {
610         if (n->client->icccm_focus)
611             send_client_message(n->client->window, ewmh->WM_PROTOCOLS, WM_TAKE_FOCUS);
612         xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, n->client->window, XCB_CURRENT_TIME);
613     }
614 }
615
616 void clear_input_focus(void)
617 {
618     xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
619 }
620
621 void center_pointer(monitor_t *m)
622 {
623     int16_t cx = m->rectangle.x + m->rectangle.width / 2;
624     int16_t cy = m->rectangle.y + m->rectangle.height / 2;
625     window_lower(motion_recorder);
626     xcb_warp_pointer(dpy, XCB_NONE, root, 0, 0, 0, 0, cx, cy);
627     window_raise(motion_recorder);
628 }
629
630 void get_atom(char *name, xcb_atom_t *atom)
631 {
632     xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(dpy, xcb_intern_atom(dpy, 0, strlen(name), name), NULL);
633     if (reply != NULL)
634         *atom = reply->atom;
635     else
636         *atom = XCB_NONE;
637     free(reply);
638 }
639
640 void set_atom(xcb_window_t win, xcb_atom_t atom, uint32_t value)
641 {
642     xcb_change_property(dpy, XCB_PROP_MODE_REPLACE, win, atom, XCB_ATOM_CARDINAL, 32, 1, &value);
643 }
644
645 bool has_proto(xcb_atom_t atom, xcb_icccm_get_wm_protocols_reply_t *protocols)
646 {
647     for (uint32_t i = 0; i < protocols->atoms_len; i++)
648         if (protocols->atoms[i] == atom)
649             return true;
650     return false;
651 }
652
653 void send_client_message(xcb_window_t win, xcb_atom_t property, xcb_atom_t value)
654 {
655     xcb_client_message_event_t e;
656
657     e.response_type = XCB_CLIENT_MESSAGE;
658     e.window = win;
659     e.format = 32;
660     e.sequence = 0;
661     e.type = property;
662     e.data.data32[0] = value;
663     e.data.data32[1] = XCB_CURRENT_TIME;
664
665     xcb_send_event(dpy, false, win, XCB_EVENT_MASK_NO_EVENT, (char *) &e);
666 }