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