6 #include <xcb/xcb_event.h>
7 #include <xcb/xcb_icccm.h>
17 void center(xcb_rectangle_t a, xcb_rectangle_t *b)
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;
25 bool contains(xcb_rectangle_t a, xcb_rectangle_t b)
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));
31 bool is_inside(monitor_t *m, xcb_point_t pt)
33 xcb_rectangle_t r = m->rectangle;
34 return (r.x <= pt.x && pt.x < (r.x + r.width)
35 && r.y <= pt.y && pt.y < (r.y + r.height));
38 xcb_rectangle_t get_rectangle(client_t *c)
41 return c->tiled_rectangle;
43 return c->floating_rectangle;
46 void get_side_handle(client_t *c, direction_t dir, xcb_point_t *pt)
48 xcb_rectangle_t rect = get_rectangle(c);
51 pt->x = rect.x + rect.width;
52 pt->y = rect.y + (rect.height / 2);
55 pt->x = rect.x + (rect.width / 2);
56 pt->y = rect.y + rect.height;
60 pt->y = rect.y + (rect.height / 2);
63 pt->x = rect.x + (rect.width / 2);
69 monitor_t *monitor_from_point(xcb_point_t pt)
71 for (monitor_t *m = mon_head; m != NULL; m = m->next)
77 monitor_t *underlying_monitor(client_t *c)
79 xcb_point_t pt = (xcb_point_t) {c->floating_rectangle.x, c->floating_rectangle.y};
80 return monitor_from_point(pt);
83 void manage_window(monitor_t *m, desktop_t *d, xcb_window_t win)
86 xcb_get_window_attributes_reply_t *wa = xcb_get_window_attributes_reply(dpy, xcb_get_window_attributes(dpy, win), NULL);
87 uint8_t override_redirect = 0;
90 override_redirect = wa->override_redirect;
94 if (override_redirect || locate_window(win, &loc))
97 bool floating = false, fullscreen = false, locked = false, follow = false, transient = false, takes_focus = true, manage = true;
98 handle_rules(win, &m, &d, &floating, &fullscreen, &locked, &follow, &transient, &takes_focus, &manage);
101 disable_floating_atom(win);
106 client_t *c = make_client(win);
107 update_floating_rectangle(c);
109 xcb_icccm_get_wm_class_reply_t reply;
110 if (xcb_icccm_get_wm_class_reply(dpy, xcb_icccm_get_wm_class(dpy, win), &reply, NULL) == 1) {
111 strncpy(c->class_name, reply.class_name, sizeof(c->class_name));
112 xcb_icccm_get_wm_class_reply_wipe(&reply);
118 node_t *n = make_node();
121 insert_node(m, d, n, d->focus);
123 set_state(win, XCB_ICCCM_WM_STATE_NORMAL);
124 disable_floating_atom(c->window);
125 set_floating(d, n, floating);
126 set_locked(m, d, n, locked);
128 if (d->focus != NULL && d->focus->client->fullscreen)
129 set_fullscreen(d, d->focus, false);
131 set_fullscreen(d, n, fullscreen);
133 c->transient = transient;
135 bool give_focus = (takes_focus && (d == mon->desk || follow));
138 } else if (takes_focus) {
141 node_t *f = d->focus;
147 xcb_rectangle_t *frect = &n->client->floating_rectangle;
148 if (frect->x == 0 && frect->y == 0)
149 center(m->rectangle, frect);
151 fit_monitor(m, n->client);
155 if (d == m->desk && visible)
156 window_show(c->window);
158 /* the same function is already called in `focus_node` but has no effects on unmapped windows */
160 xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, win, XCB_CURRENT_TIME);
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);
166 ewmh_set_wm_desktop(n, d);
167 ewmh_update_client_list();
170 void adopt_orphans(void)
172 xcb_query_tree_reply_t *qtr = xcb_query_tree_reply(dpy, xcb_query_tree(dpy, root), NULL);
176 PUTS("adopt orphans");
178 int len = xcb_query_tree_children_length(qtr);
179 xcb_window_t *wins = xcb_query_tree_children(qtr);
180 for (int i = 0; i < len; i++) {
182 xcb_window_t win = wins[i];
184 if (xcb_ewmh_get_wm_desktop_reply(ewmh, xcb_ewmh_get_wm_desktop(ewmh, win), &idx, NULL) == 1) {
186 if (ewmh_locate_desktop(idx, &loc))
187 manage_window(loc.monitor, loc.desktop, win);
189 manage_window(mon, mon->desk, win);
195 void window_draw_border(node_t *n, bool focused_window, bool focused_monitor)
197 if (n == NULL || border_width < 1 || n->client->border_width < 1)
200 xcb_window_t win = n->client->window;
201 uint32_t border_color_pxl = get_border_color(n->client, focused_window, focused_monitor);
203 if (n->split_mode == MODE_AUTOMATIC) {
204 xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXEL, &border_color_pxl);
206 uint32_t presel_border_color_pxl;
207 get_color(presel_border_color, win, &presel_border_color_pxl);
209 xcb_rectangle_t actual_rectangle = get_rectangle(n->client);
211 uint16_t width = actual_rectangle.width;
212 uint16_t height = actual_rectangle.height;
214 uint16_t full_width = width + 2 * border_width;
215 uint16_t full_height = height + 2 * border_width;
217 xcb_rectangle_t border_rectangles[] =
219 { width, 0, 2 * border_width, height + 2 * border_width },
220 { 0, height, width + 2 * border_width, 2 * border_width }
223 xcb_rectangle_t *presel_rectangles;
225 uint8_t win_depth = root_depth;
226 xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, win), NULL);
228 win_depth = geo->depth;
231 xcb_pixmap_t pixmap = xcb_generate_id(dpy);
232 xcb_create_pixmap(dpy, win_depth, pixmap, win, full_width, full_height);
234 xcb_gcontext_t gc = xcb_generate_id(dpy);
235 xcb_create_gc(dpy, gc, pixmap, 0, NULL);
237 xcb_change_gc(dpy, gc, XCB_GC_FOREGROUND, &border_color_pxl);
238 xcb_poly_fill_rectangle(dpy, pixmap, gc, LENGTH(border_rectangles), border_rectangles);
240 uint16_t fence = (int16_t) (n->split_ratio * ((n->split_dir == DIR_UP || n->split_dir == DIR_DOWN) ? height : width));
241 presel_rectangles = malloc(2 * sizeof(xcb_rectangle_t));
242 switch (n->split_dir) {
244 presel_rectangles[0] = (xcb_rectangle_t) {width, 0, 2 * border_width, fence};
245 presel_rectangles[1] = (xcb_rectangle_t) {0, height + border_width, full_width, border_width};
248 presel_rectangles[0] = (xcb_rectangle_t) {width, fence + 1, 2 * border_width, height + border_width - (fence + 1)};
249 presel_rectangles[1] = (xcb_rectangle_t) {0, height, full_width, border_width};
252 presel_rectangles[0] = (xcb_rectangle_t) {0, height, fence, 2 * border_width};
253 presel_rectangles[1] = (xcb_rectangle_t) {width + border_width, 0, border_width, full_height};
256 presel_rectangles[0] = (xcb_rectangle_t) {fence + 1, height, width + border_width - (fence + 1), 2 * border_width};
257 presel_rectangles[1] = (xcb_rectangle_t) {width, 0, border_width, full_height};
260 xcb_change_gc(dpy, gc, XCB_GC_FOREGROUND, &presel_border_color_pxl);
261 xcb_poly_fill_rectangle(dpy, pixmap, gc, 2, presel_rectangles);
262 xcb_change_window_attributes(dpy, win, XCB_CW_BORDER_PIXMAP, &pixmap);
263 free(presel_rectangles);
264 xcb_free_gc(dpy, gc);
265 xcb_free_pixmap(dpy, pixmap);
269 void window_close(node_t *n)
271 if (n == NULL || n->client->locked)
274 PRINTF("close window %X\n", n->client->window);
276 send_client_message(n->client->window, ewmh->WM_PROTOCOLS, WM_DELETE_WINDOW);
279 void window_kill(desktop_t *d, node_t *n)
284 xcb_window_t win = n->client->window;
285 PRINTF("kill window %X\n", win);
287 xcb_kill_client(dpy, win);
291 void set_fullscreen(desktop_t *d, node_t *n, bool value)
293 if (n == NULL || n->client->fullscreen == value)
296 client_t *c = n->client;
298 PRINTF("fullscreen %X: %s\n", c->window, BOOLSTR(value));
301 c->fullscreen = true;
302 xcb_atom_t values[] = {ewmh->_NET_WM_STATE_FULLSCREEN};
303 xcb_ewmh_set_wm_state(ewmh, c->window, LENGTH(values), values);
305 c->fullscreen = false;
306 xcb_atom_t values[] = {XCB_NONE};
307 xcb_ewmh_set_wm_state(ewmh, c->window, LENGTH(values), values);
313 void set_floating(desktop_t *d, node_t *n, bool value)
315 if (n == NULL || n->client->transient || n->client->fullscreen || n->client->floating == value)
318 PRINTF("floating %X: %s\n", n->client->window, BOOLSTR(value));
320 n->split_mode = MODE_AUTOMATIC;
321 client_t *c = n->client;
322 c->floating = n->vacant = value;
323 update_vacant_state(n->parent);
326 enable_floating_atom(c->window);
329 disable_floating_atom(c->window);
336 void set_locked(monitor_t *m, desktop_t *d, node_t *n, bool value)
338 if (n == NULL || n->client->locked == value)
341 client_t *c = n->client;
343 PRINTF("set locked %X: %s\n", c->window, BOOLSTR(value));
346 window_draw_border(n, d->focus == n, m == mon);
349 void set_urgency(monitor_t *m, desktop_t *d, node_t *n, bool value)
351 if (value && mon->desk->focus == n)
353 n->client->urgent = value;
354 window_draw_border(n, d->focus == n, m == mon);
358 void set_floating_atom(xcb_window_t win, uint32_t value)
360 if (!apply_floating_atom)
362 set_atom(win, _BSPWM_FLOATING_WINDOW, value);
365 void enable_floating_atom(xcb_window_t win)
367 set_floating_atom(win, 1);
370 void disable_floating_atom(xcb_window_t win)
372 set_floating_atom(win, 0);
375 uint32_t get_border_color(client_t *c, bool focused_window, bool focused_monitor)
382 if (focused_monitor && focused_window) {
384 get_color(focused_locked_border_color, c->window, &pxl);
386 get_color(focused_border_color, c->window, &pxl);
387 } else if (focused_window) {
389 get_color(urgent_border_color, c->window, &pxl);
391 get_color(active_locked_border_color, c->window, &pxl);
393 get_color(active_border_color, c->window, &pxl);
396 get_color(urgent_border_color, c->window, &pxl);
398 get_color(normal_locked_border_color, c->window, &pxl);
400 get_color(normal_border_color, c->window, &pxl);
406 void update_floating_rectangle(client_t *c)
408 xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, c->window), NULL);
411 c->floating_rectangle = (xcb_rectangle_t) {geo->x, geo->y, geo->width, geo->height};
413 c->floating_rectangle = (xcb_rectangle_t) {0, 0, 32, 24};
419 void query_pointer(xcb_window_t *win, xcb_point_t *pt)
421 window_lower(motion_recorder);
423 xcb_query_pointer_reply_t *qpr = xcb_query_pointer_reply(dpy, xcb_query_pointer(dpy, root), NULL);
429 *pt = (xcb_point_t) {qpr->root_x, qpr->root_y};
433 window_raise(motion_recorder);
436 void window_focus(xcb_window_t win)
439 if (locate_window(win, &loc)) {
440 if (loc.node == mon->desk->focus)
442 focus_node(loc.monitor, loc.desktop, loc.node);
446 void window_border_width(xcb_window_t win, uint32_t bw)
448 uint32_t values[] = {bw};
449 xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_BORDER_WIDTH, values);
452 void window_move(xcb_window_t win, int16_t x, int16_t y)
454 uint32_t values[] = {x, y};
455 xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y, values);
458 void window_resize(xcb_window_t win, uint16_t w, uint16_t h)
460 uint32_t values[] = {w, h};
461 xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_WIDTH_HEIGHT, values);
464 void window_move_resize(xcb_window_t win, int16_t x, int16_t y, uint16_t w, uint16_t h)
466 uint32_t values[] = {x, y, w, h};
467 xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_X_Y_WIDTH_HEIGHT, values);
470 void window_raise(xcb_window_t win)
472 uint32_t values[] = {XCB_STACK_MODE_ABOVE};
473 xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
476 void window_stack(xcb_window_t w1, xcb_window_t w2, uint32_t mode)
480 uint16_t mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
481 uint32_t values[] = {w2, mode};
482 xcb_configure_window(dpy, w1, mask, values);
485 void window_above(xcb_window_t w1, xcb_window_t w2)
487 window_stack(w1, w2, XCB_STACK_MODE_ABOVE);
490 void window_below(xcb_window_t w1, xcb_window_t w2)
492 window_stack(w1, w2, XCB_STACK_MODE_BELOW);
495 void stack(desktop_t *d, node_t *n)
497 if (is_leaf(d->root))
499 if (n->client->fullscreen) {
500 window_raise(n->client->window);
502 if (n->client->floating && !auto_raise)
504 xcb_window_t latest_tiled = XCB_NONE;
505 xcb_window_t oldest_floating = XCB_NONE;
506 for (node_list_t *a = d->history->head; a != NULL; a = a->next) {
507 if (a->latest && a->node != n) {
508 if (a->node->client->floating == n->client->floating) {
509 window_above(n->client->window, a->node->client->window);
511 } else if (latest_tiled == XCB_NONE && !a->node->client->floating) {
512 latest_tiled = a->node->client->window;
513 } else if (a->node->client->floating) {
514 oldest_floating = a->node->client->window;
518 if (n->client->floating)
519 window_above(n->client->window, latest_tiled);
521 window_below(n->client->window, oldest_floating);
525 void window_lower(xcb_window_t win)
527 uint32_t values[] = {XCB_STACK_MODE_BELOW};
528 xcb_configure_window(dpy, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
531 void window_set_visibility(xcb_window_t win, bool visible)
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);
537 xcb_map_window(dpy, win);
538 set_state(win, XCB_ICCCM_WM_STATE_NORMAL);
540 xcb_unmap_window(dpy, win);
541 set_state(win, XCB_ICCCM_WM_STATE_WITHDRAWN);
543 xcb_change_window_attributes(dpy, root, XCB_CW_EVENT_MASK, values_on);
546 void window_hide(xcb_window_t win)
548 window_set_visibility(win, false);
551 void window_show(xcb_window_t win)
553 window_set_visibility(win, true);
556 void toggle_visibility(void)
561 for (monitor_t *m = mon_head; m != NULL; m = m->next)
562 for (node_t *n = first_extrema(m->desk->root); n != NULL; n = next_leaf(n, m->desk->root))
563 window_set_visibility(n->client->window, visible);
565 update_input_focus();
568 void desktop_show(desktop_t *d)
572 for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root))
573 window_show(n->client->window);
576 void desktop_hide(desktop_t *d)
580 for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root))
581 window_hide(n->client->window);
584 void enable_motion_recorder(void)
586 PUTS("enable motion recorder");
587 window_raise(motion_recorder);
588 window_show(motion_recorder);
591 void disable_motion_recorder(void)
593 PUTS("disable motion recorder");
594 window_hide(motion_recorder);
597 void update_motion_recorder(void)
599 xcb_get_geometry_reply_t *geo = xcb_get_geometry_reply(dpy, xcb_get_geometry(dpy, root), NULL);
602 window_resize(motion_recorder, geo->width, geo->height);
603 PRINTF("update motion recorder size: %ux%u\n", geo->width, geo->height);
609 void update_input_focus(void)
611 set_input_focus(mon->desk->focus);
614 void set_input_focus(node_t *n)
619 if (n->client->icccm_focus)
620 send_client_message(n->client->window, ewmh->WM_PROTOCOLS, WM_TAKE_FOCUS);
621 xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, n->client->window, XCB_CURRENT_TIME);
625 void clear_input_focus(void)
627 xcb_set_input_focus(dpy, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
630 void center_pointer(monitor_t *m)
632 int16_t cx = m->rectangle.x + m->rectangle.width / 2;
633 int16_t cy = m->rectangle.y + m->rectangle.height / 2;
634 window_lower(motion_recorder);
635 xcb_warp_pointer(dpy, XCB_NONE, root, 0, 0, 0, 0, cx, cy);
636 window_raise(motion_recorder);
639 void get_atom(char *name, xcb_atom_t *atom)
641 xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(dpy, xcb_intern_atom(dpy, 0, strlen(name), name), NULL);
649 void set_atom(xcb_window_t win, xcb_atom_t atom, uint32_t value)
651 xcb_change_property(dpy, XCB_PROP_MODE_REPLACE, win, atom, XCB_ATOM_CARDINAL, 32, 1, &value);
654 void set_state(xcb_window_t win, uint32_t state)
656 uint32_t values[] = {state, XCB_NONE};
657 xcb_change_property(dpy, XCB_PROP_MODE_REPLACE, win, WM_STATE, WM_STATE, 32, 2, values);
660 bool has_proto(xcb_atom_t atom, xcb_icccm_get_wm_protocols_reply_t *protocols)
662 for (uint32_t i = 0; i < protocols->atoms_len; i++)
663 if (protocols->atoms[i] == atom)
668 void send_client_message(xcb_window_t win, xcb_atom_t property, xcb_atom_t value)
670 xcb_client_message_event_t e;
672 e.response_type = XCB_CLIENT_MESSAGE;
677 e.data.data32[0] = value;
678 e.data.data32[1] = XCB_CURRENT_TIME;
680 xcb_send_event(dpy, false, win, XCB_EVENT_MASK_NO_EVENT, (char *) &e);