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