]> git.lizzy.rs Git - bspwm.git/blob - window.c
New setting: `pointer_follows_monitor`
[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     else if (takes_focus)
170         pseudo_focus(d, birth);
171
172     xcb_rectangle_t *frect = &birth->client->floating_rectangle;
173     if (frect->x == 0 && frect->y == 0)
174         center(m->rectangle, frect);
175
176     fit_monitor(m, birth->client);
177
178     arrange(m, d);
179
180     if (d == m->desk && visible)
181         window_show(c->window);
182
183     /* the same function is already called in `focus_node` but has no effects on unmapped windows */
184     if (give_focus)
185         xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
186
187     uint32_t values[] = {(focus_follows_pointer ? CLIENT_EVENT_MASK_FFP : CLIENT_EVENT_MASK)};
188     xcb_change_window_attributes(dpy, c->window, XCB_CW_EVENT_MASK, values);
189
190     num_clients++;
191     ewmh_set_wm_desktop(birth, d);
192     ewmh_update_client_list();
193 }
194
195 void adopt_orphans(void)
196 {
197     xcb_query_tree_reply_t *qtr = xcb_query_tree_reply(dpy, xcb_query_tree(dpy, root), NULL);
198     if (qtr == NULL)
199         return;
200
201     PUTS("adopt orphans");
202
203     int len = xcb_query_tree_children_length(qtr);
204     xcb_window_t *wins = xcb_query_tree_children(qtr);
205     for (int i = 0; i < len; i++) {
206         uint32_t idx;
207         xcb_window_t win = wins[i];
208         window_hide(win);
209         if (xcb_ewmh_get_wm_desktop_reply(ewmh, xcb_ewmh_get_wm_desktop(ewmh, win), &idx, NULL) == 1) {
210             desktop_location_t loc;
211             if (ewmh_locate_desktop(idx, &loc))
212                 manage_window(loc.monitor, loc.desktop, win);
213             else
214                 manage_window(mon, mon->desk, win);
215         }
216     }
217     free(qtr);
218 }
219
220 void window_draw_border(node_t *n, bool focused_window, bool focused_monitor)
221 {
222     if (n == NULL || border_width < 1 || n->client->border_width < 1)
223         return;
224
225     xcb_window_t win = n->client->window;
226     uint32_t border_color_pxl = get_border_color(n->client, focused_window, focused_monitor);
227
228     if (split_mode == MODE_AUTOMATIC || !focused_monitor || !focused_window) {
229         xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXEL, &border_color_pxl);
230     } else {
231         xcb_rectangle_t actual_rectangle = (is_tiled(n->client) ? n->client->tiled_rectangle : n->client->floating_rectangle);
232
233         uint16_t width = actual_rectangle.width;
234         uint16_t height = actual_rectangle.height;
235
236         uint16_t full_width = width + 2 * border_width;
237         uint16_t full_height = height + 2 * border_width;
238
239         xcb_rectangle_t border_rectangles[] =
240         {
241             { width, 0, 2 * border_width, height + 2 * border_width },
242             { 0, height, width + 2 * border_width, 2 * border_width }
243         };
244
245         xcb_rectangle_t *presel_rectangles;
246
247         uint8_t win_depth = root_depth;
248         xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL);
249         if (geo != NULL)
250             win_depth = geo->depth;
251         free(geo);
252
253         xcb_pixmap_t pix = xcb_generate_id(dpy);
254         xcb_create_pixmap(dpy, win_depth, pix, win, full_width, full_height);
255
256         xcb_gcontext_t gc = xcb_generate_id(dpy);
257         xcb_create_gc(dpy, gc, pix, 0, NULL);
258
259         xcb_change_gc(dpy, gc, XCB_GC_FOREGROUND, &border_color_pxl);
260         xcb_poly_fill_rectangle(dpy, pix, gc, LENGTH(border_rectangles), border_rectangles);
261
262         uint16_t fence = (int16_t) (n->split_ratio * ((split_dir == DIR_UP || split_dir == DIR_DOWN) ? height : width));
263         presel_rectangles = malloc(2 * sizeof(xcb_rectangle_t));
264         switch (split_dir) {
265             case DIR_UP:
266                 presel_rectangles[0] = (xcb_rectangle_t) {width, 0, 2 * border_width, fence};
267                 presel_rectangles[1] = (xcb_rectangle_t) {0, height + border_width, full_width, border_width};
268                 break;
269             case DIR_DOWN:
270                 presel_rectangles[0] = (xcb_rectangle_t) {width, fence + 1, 2 * border_width, height + border_width - (fence + 1)};
271                 presel_rectangles[1] = (xcb_rectangle_t) {0, height, full_width, border_width};
272                 break;
273             case DIR_LEFT:
274                 presel_rectangles[0] = (xcb_rectangle_t) {0, height, fence, 2 * border_width};
275                 presel_rectangles[1] = (xcb_rectangle_t) {width + border_width, 0, border_width, full_height};
276                 break;
277             case DIR_RIGHT:
278                 presel_rectangles[0] = (xcb_rectangle_t) {fence + 1, height, width + border_width - (fence + 1), 2 * border_width};
279                 presel_rectangles[1] = (xcb_rectangle_t) {width, 0, border_width, full_height};
280                 break;
281         }
282
283         xcb_change_gc(dpy, gc, XCB_GC_FOREGROUND, &presel_border_color_pxl);
284         xcb_poly_fill_rectangle(dpy, pix, gc, 2, presel_rectangles);
285         xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXMAP, &pix);
286         free(presel_rectangles);
287         xcb_free_gc(dpy, gc);
288         xcb_free_pixmap(dpy, pix);
289     }
290 }
291
292 void window_close(node_t *n)
293 {
294     if (n == NULL || n->client->locked)
295         return;
296
297     PRINTF("close window %X\n", n->client->window);
298
299     xcb_atom_t WM_DELETE_WINDOW;
300     xcb_window_t win = n->client->window;
301     xcb_client_message_event_t e;
302
303     xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(dpy, xcb_intern_atom(dpy, 0, strlen("WM_DELETE_WINDOW"), "WM_DELETE_WINDOW"), NULL);
304     if (reply) {
305         WM_DELETE_WINDOW = reply->atom;
306         free(reply);
307     } else {
308         warn("close_window %X: could not acquire WM_DELETE_WINDOW atom\n", win);
309         return;
310     }
311
312     e.response_type = XCB_CLIENT_MESSAGE;
313     e.window = win;
314     e.format = 32;
315     e.sequence = 0;
316     e.type = ewmh->WM_PROTOCOLS;
317     e.data.data32[0] = WM_DELETE_WINDOW;
318     e.data.data32[1] = XCB_CURRENT_TIME;
319
320     xcb_send_event(dpy, false, win, XCB_EVENT_MASK_NO_EVENT, (char *) &e);
321 }
322
323 void window_kill(desktop_t *d, node_t *n)
324 {
325     if (n == NULL)
326         return;
327
328     PRINTF("kill window %X\n", n->client->window);
329
330     xcb_kill_client(dpy, n->client->window);
331     remove_node(d, n);
332 }
333
334 void toggle_fullscreen(monitor_t *m, client_t *c)
335 {
336     PRINTF("toggle fullscreen %X\n", c->window);
337
338     if (c->fullscreen) {
339         c->fullscreen = false;
340         xcb_atom_t values[] = {XCB_NONE};
341         xcb_ewmh_set_wm_state(ewmh, c->window, LENGTH(values), values);
342         if (is_tiled(c))
343             window_lower(c->window);
344     } else {
345         c->fullscreen = true;
346         xcb_atom_t values[] = {ewmh->_NET_WM_STATE_FULLSCREEN};
347         xcb_ewmh_set_wm_state(ewmh, c->window, LENGTH(values), values);
348         window_raise(c->window);
349         window_border_width(c->window, 0);
350         xcb_rectangle_t r = m->rectangle;
351         window_move_resize(c->window, r.x, r.y, r.width, r.height);
352     }
353     update_current();
354 }
355
356 void toggle_floating(node_t *n)
357 {
358     if (n == NULL || n->client->transient || n->client->fullscreen)
359         return;
360
361     PRINTF("toggle floating %X\n", n->client->window);
362
363     client_t *c = n->client;
364     c->floating = !c->floating;
365     n->vacant = !n->vacant;
366     update_vacant_state(n->parent);
367     if (c->floating) {
368         window_raise(c->window);
369         enable_shadow(c->window);
370         unrotate_brother(n);
371     } else {
372         window_lower(c->window);
373         disable_shadow(c->window);
374         rotate_brother(n);
375     }
376     update_current();
377 }
378
379 void toggle_locked(client_t *c)
380 {
381     PRINTF("toggle locked %X\n", c->window);
382
383     c->locked = !c->locked;
384 }
385
386 void set_urgency(monitor_t *m, desktop_t *d, node_t *n, bool value)
387 {
388     if (value && mon->desk->focus == n)
389         return;
390     n->client->urgent = value;
391     put_status();
392     if (m->desk == d)
393         arrange(m, d);
394 }
395
396 void set_shadow(xcb_window_t win, uint32_t value)
397 {
398     if (!apply_shadow_property)
399         return;
400     xcb_change_property(dpy, XCB_PROP_MODE_REPLACE, win, compton_shadow, XCB_ATOM_CARDINAL, 32, 1, &value);
401 }
402
403 void enable_shadow(xcb_window_t win)
404 {
405     set_shadow(win, 1);
406 }
407
408 void disable_shadow(xcb_window_t win)
409 {
410     set_shadow(win, 0);
411 }
412
413 void list_windows(char *rsp)
414 {
415     char line[MAXLEN];
416
417     for (monitor_t *m = mon_head; m != NULL; m = m->next)
418         for (desktop_t *d = m->desk_head; d != NULL; d = d->next)
419             for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) {
420                 snprintf(line, sizeof(line), "0x%X\n", n->client->window);
421                 strncat(rsp, line, REMLEN(rsp));
422             }
423 }
424
425 uint32_t get_border_color(client_t *c, bool focused_window, bool focused_monitor)
426 {
427     if (c == NULL)
428         return 0;
429
430     if (focused_monitor && focused_window) {
431         if (c->locked)
432             return focused_locked_border_color_pxl;
433         else
434             return focused_border_color_pxl;
435     } else if (focused_window) {
436         if (c->urgent)
437             return urgent_border_color_pxl;
438         else if (c->locked)
439             return active_locked_border_color_pxl;
440         else
441             return active_border_color_pxl;
442     } else {
443         if (c->urgent)
444             return urgent_border_color_pxl;
445         else if (c->locked)
446             return normal_locked_border_color_pxl;
447         else
448             return normal_border_color_pxl;
449     }
450 }
451
452 void update_floating_rectangle(client_t *c)
453 {
454     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, c->window), NULL);
455
456     if (geo != NULL)
457         c->floating_rectangle = (xcb_rectangle_t) {geo->x, geo->y, geo->width, geo->height};
458     else
459         c->floating_rectangle = (xcb_rectangle_t) {0, 0, 32, 24};
460
461     free(geo);
462 }
463
464
465 void query_pointer(xcb_window_t *win, xcb_point_t *pt)
466 {
467     window_lower(motion_recorder);
468     xcb_query_pointer_reply_t *qpr = xcb_query_pointer_reply(dpy, xcb_query_pointer(dpy, root), NULL);
469     if (qpr != NULL) {
470         if (win != NULL)
471             *win = qpr->child;
472         if (pt != NULL)
473             *pt = (xcb_point_t) {qpr->root_x, qpr->root_y};
474         free(qpr);
475     }
476     window_raise(motion_recorder);
477 }
478
479 void window_focus(xcb_window_t win)
480 {
481     window_location_t loc;
482     if (locate_window(win, &loc)) {
483         if (loc.node == mon->desk->focus)
484             return;
485         focus_node(loc.monitor, loc.desktop, loc.node);
486     }
487 }
488
489 void window_border_width(xcb_window_t win, uint32_t bw)
490 {
491     uint32_t values[] = {bw};
492     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_BORDER_WIDTH, values);
493 }
494
495 void window_move(xcb_window_t win, int16_t x, int16_t y)
496 {
497     uint32_t values[] = {x, y};
498     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y, values);
499 }
500
501 void window_resize(xcb_window_t win, uint16_t w, uint16_t h)
502 {
503     uint32_t values[] = {w, h};
504     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_WIDTH_HEIGHT, values);
505 }
506
507 void window_move_resize(xcb_window_t win, int16_t x, int16_t y, uint16_t w, uint16_t h)
508 {
509     uint32_t values[] = {x, y, w, h};
510     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y_WIDTH_HEIGHT, values);
511 }
512
513 void window_raise(xcb_window_t win)
514 {
515     uint32_t values[] = {XCB_STACK_MODE_ABOVE};
516     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
517 }
518
519 void stack_tiled(desktop_t *d)
520 {
521     for (node_list_t *a = d->history->head; a != NULL; a = a->next)
522         if (a->latest && is_tiled(a->node->client))
523             window_lower(a->node->client->window);
524 }
525
526 void stack(desktop_t *d, node_t *n)
527 {
528     if (!is_tiled(n->client)) {
529         if (!adaptative_raise || !might_cover(d, n))
530             window_raise(n->client->window);
531     } else {
532         stack_tiled(d);
533     }
534 }
535
536 void window_lower(xcb_window_t win)
537 {
538     uint32_t values[] = {XCB_STACK_MODE_BELOW};
539     xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
540 }
541
542 void window_set_visibility(xcb_window_t win, bool visible)
543 {
544     uint32_t values_off[] = {ROOT_EVENT_MASK & ~XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY};
545     uint32_t values_on[] = {ROOT_EVENT_MASK};
546     xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_off);
547     if (visible)
548         xcb_map_window(dpy, win);
549     else
550         xcb_unmap_window(dpy, win);
551     xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_on);
552 }
553
554 void window_hide(xcb_window_t win)
555 {
556     window_set_visibility(win, false);
557 }
558
559 void window_show(xcb_window_t win)
560 {
561     window_set_visibility(win, true);
562 }
563
564 void toggle_visibility(void)
565 {
566     if (visible)
567         clear_input_focus();
568     visible = !visible;
569     for (monitor_t *m = mon_head; m != NULL; m = m->next)
570         for (node_t *n = first_extrema(m->desk->root); n != NULL; n = next_leaf(n, m->desk->root))
571             window_set_visibility(n->client->window, visible);
572     if (visible)
573         update_current();
574 }
575
576 void desktop_show(desktop_t *d)
577 {
578     for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root))
579         window_show(n->client->window);
580 }
581
582 void desktop_hide(desktop_t *d)
583 {
584     for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root))
585         window_hide(n->client->window);
586 }
587
588 void enable_motion_recorder(void)
589 {
590     PUTS("enable motion recorder");
591     window_raise(motion_recorder);
592     window_show(motion_recorder);
593 }
594
595 void disable_motion_recorder(void)
596 {
597     PUTS("disable motion recorder");
598     window_hide(motion_recorder);
599 }
600
601 void update_motion_recorder(void)
602 {
603     xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, root), NULL);
604
605     if (geo != NULL) {
606         window_resize(motion_recorder, geo->width, geo->height);
607         PRINTF("update motion recorder size: %ux%u\n", geo->width, geo->height);
608     }
609
610     free(geo);
611 }
612
613 void clear_input_focus(void)
614 {
615     xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
616 }
617
618 void center_pointer(monitor_t *m)
619 {
620     int16_t cx = m->rectangle.x + m->rectangle.width / 2;
621     int16_t cy = m->rectangle.y + m->rectangle.height / 2;
622     xcb_warp_pointer(dpy, XCB_NONE, root, 0, 0, 0, 0, cx, cy);
623 }