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