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