1 /* Copyright (c) 2012, Bastien Dejean
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright notice, this
8 * list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright notice,
10 * this list of conditions and the following disclaimer in the documentation
11 * and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 #include "subscribe.h"
39 monitor_t *make_monitor(xcb_rectangle_t rect)
41 monitor_t *m = malloc(sizeof(monitor_t));
42 snprintf(m->name, sizeof(m->name), "%s%02d", DEFAULT_MON_NAME, ++monitor_uid);
43 m->prev = m->next = NULL;
44 m->desk = m->desk_head = m->desk_tail = NULL;
46 m->top_padding = m->right_padding = m->bottom_padding = m->left_padding = 0;
49 uint32_t values[] = {XCB_EVENT_MASK_ENTER_WINDOW};
50 m->root = xcb_generate_id(dpy);
51 xcb_create_window(dpy, XCB_COPY_FROM_PARENT, m->root, root, rect.x, rect.y, rect.width, rect.height, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values);
52 xcb_icccm_set_wm_class(dpy, m->root, sizeof(ROOT_WINDOW_IC), ROOT_WINDOW_IC);
53 window_lower(m->root);
54 if (focus_follows_pointer) {
60 monitor_t *find_monitor(char *name)
62 for (monitor_t *m = mon_head; m != NULL; m = m->next)
63 if (streq(m->name, name))
68 monitor_t *get_monitor_by_id(xcb_randr_output_t id)
70 for (monitor_t *m = mon_head; m != NULL; m = m->next)
76 void embrace_client(monitor_t *m, client_t *c)
78 if ((c->floating_rectangle.x + c->floating_rectangle.width) <= m->rectangle.x)
79 c->floating_rectangle.x = m->rectangle.x;
80 else if (c->floating_rectangle.x >= (m->rectangle.x + m->rectangle.width))
81 c->floating_rectangle.x = (m->rectangle.x + m->rectangle.width) - c->floating_rectangle.width;
82 if ((c->floating_rectangle.y + c->floating_rectangle.height) <= m->rectangle.y)
83 c->floating_rectangle.y = m->rectangle.y;
84 else if (c->floating_rectangle.y >= (m->rectangle.y + m->rectangle.height))
85 c->floating_rectangle.y = (m->rectangle.y + m->rectangle.height) - c->floating_rectangle.height;
88 void translate_client(monitor_t *ms, monitor_t *md, client_t *c)
90 if (frozen_pointer->action != ACTION_NONE || ms == md)
93 /* Clip the rectangle to fit into the monitor. Without this, the fitting
94 * algorithm doesn't work as expected. This also conserves the
95 * out-of-bounds regions */
96 int left_adjust = MAX((ms->rectangle.x - c->floating_rectangle.x), 0);
97 int top_adjust = MAX((ms->rectangle.y - c->floating_rectangle.y), 0);
98 int right_adjust = MAX((c->floating_rectangle.x + c->floating_rectangle.width) - (ms->rectangle.x + ms->rectangle.width), 0);
99 int bottom_adjust = MAX((c->floating_rectangle.y + c->floating_rectangle.height) - (ms->rectangle.y + ms->rectangle.height), 0);
100 c->floating_rectangle.x += left_adjust;
101 c->floating_rectangle.y += top_adjust;
102 c->floating_rectangle.width -= (left_adjust + right_adjust);
103 c->floating_rectangle.height -= (top_adjust + bottom_adjust);
105 int dx_s = c->floating_rectangle.x - ms->rectangle.x;
106 int dy_s = c->floating_rectangle.y - ms->rectangle.y;
108 int nume_x = dx_s * (md->rectangle.width - c->floating_rectangle.width);
109 int nume_y = dy_s * (md->rectangle.height - c->floating_rectangle.height);
111 int deno_x = ms->rectangle.width - c->floating_rectangle.width;
112 int deno_y = ms->rectangle.height - c->floating_rectangle.height;
114 int dx_d = (deno_x == 0 ? 0 : nume_x / deno_x);
115 int dy_d = (deno_y == 0 ? 0 : nume_y / deno_y);
117 /* Translate and undo clipping */
118 c->floating_rectangle.width += left_adjust + right_adjust;
119 c->floating_rectangle.height += top_adjust + bottom_adjust;
120 c->floating_rectangle.x = md->rectangle.x + dx_d - left_adjust;
121 c->floating_rectangle.y = md->rectangle.y + dy_d - top_adjust;
124 void update_root(monitor_t *m)
126 xcb_rectangle_t rect = m->rectangle;
127 window_move_resize(m->root, rect.x, rect.y, rect.width, rect.height);
130 void focus_monitor(monitor_t *m)
135 PRINTF("focus monitor %s\n", m->name);
136 put_status(SBSC_MASK_MONITOR_FOCUS, "monitor_focus %s\n", m->name);
140 if (pointer_follows_monitor)
141 center_pointer(m->rectangle);
143 ewmh_update_current_desktop();
144 put_status(SBSC_MASK_REPORT);
147 monitor_t *add_monitor(xcb_rectangle_t rect)
149 monitor_t *m = make_monitor(rect);
150 put_status(SBSC_MASK_MONITOR_ADD, "monitor_add %s\n", m->name);
166 void remove_monitor(monitor_t *m)
168 PRINTF("remove monitor %s (0x%X)\n", m->name, m->id);
169 put_status(SBSC_MASK_MONITOR_REMOVE, "monitor_remove %s\n", m->name);
171 while (m->desk_head != NULL)
172 remove_desktop(m, m->desk_head);
173 monitor_t *prev = m->prev;
174 monitor_t *next = m->next;
175 monitor_t *last_mon = history_get_monitor(m);
187 mon = (last_mon == NULL ? (prev == NULL ? next : prev) : last_mon);
188 if (mon != NULL && mon->desk != NULL)
191 xcb_destroy_window(dpy, m->root);
194 put_status(SBSC_MASK_REPORT);
197 void merge_monitors(monitor_t *ms, monitor_t *md)
199 if (ms == NULL || md == NULL || ms == md) {
203 PRINTF("merge %s into %s\n", ms->name, md->name);
205 desktop_t *d = ms->desk_head;
207 desktop_t *next = d->next;
208 if (d->root != NULL || strstr(d->name, DEFAULT_DESK_NAME) == NULL)
209 transfer_desktop(ms, md, d);
214 void swap_monitors(monitor_t *m1, monitor_t *m2)
216 if (m1 == NULL || m2 == NULL || m1 == m2)
221 else if (mon_head == m2)
225 else if (mon_tail == m2)
228 monitor_t *p1 = m1->prev;
229 monitor_t *n1 = m1->next;
230 monitor_t *p2 = m2->prev;
231 monitor_t *n2 = m2->next;
233 if (p1 != NULL && p1 != m2)
235 if (n1 != NULL && n1 != m2)
237 if (p2 != NULL && p2 != m1)
239 if (n2 != NULL && n2 != m1)
242 m1->prev = p2 == m1 ? m2 : p2;
243 m1->next = n2 == m1 ? m2 : n2;
244 m2->prev = p1 == m2 ? m1 : p1;
245 m2->next = n1 == m2 ? m1 : n1;
247 ewmh_update_wm_desktops();
248 ewmh_update_desktop_names();
249 ewmh_update_current_desktop();
250 put_status(SBSC_MASK_REPORT);
253 monitor_t *closest_monitor(monitor_t *m, cycle_dir_t dir, desktop_select_t sel)
255 monitor_t *f = (dir == CYCLE_PREV ? m->prev : m->next);
257 f = (dir == CYCLE_PREV ? mon_tail : mon_head);
260 coordinates_t loc = {m, m->desk, NULL};
261 if (desktop_matches(&loc, &loc, sel))
263 f = (dir == CYCLE_PREV ? m->prev : m->next);
265 f = (dir == CYCLE_PREV ? mon_tail : mon_head);
271 bool is_inside_monitor(monitor_t *m, xcb_point_t pt)
273 xcb_rectangle_t r = m->rectangle;
274 return (r.x <= pt.x && pt.x < (r.x + r.width)
275 && r.y <= pt.y && pt.y < (r.y + r.height));
278 monitor_t *monitor_from_point(xcb_point_t pt)
280 for (monitor_t *m = mon_head; m != NULL; m = m->next)
281 if (is_inside_monitor(m, pt))
286 monitor_t *monitor_from_client(client_t *c)
288 xcb_point_t pt = {c->floating_rectangle.x, c->floating_rectangle.y};
289 monitor_t *nearest = monitor_from_point(pt);
290 if (nearest == NULL) {
291 int x = (c->floating_rectangle.x + c->floating_rectangle.width) / 2;
292 int y = (c->floating_rectangle.y + c->floating_rectangle.height) / 2;
294 for (monitor_t *m = mon_head; m != NULL; m = m->next) {
295 xcb_rectangle_t r = m->rectangle;
296 int d = abs((r.x + r.width / 2) - x) + abs((r.y + r.height / 2) - y);
306 monitor_t *nearest_monitor(monitor_t *m, direction_t dir, desktop_select_t sel)
309 monitor_t *nearest = NULL;
310 xcb_rectangle_t rect = m->rectangle;
311 for (monitor_t *f = mon_head; f != NULL; f = f->next) {
314 coordinates_t loc = {f, f->desk, NULL};
315 if (!desktop_matches(&loc, &loc, sel))
317 xcb_rectangle_t r = f->rectangle;
318 if ((dir == DIR_LEFT && r.x < rect.x) ||
319 (dir == DIR_RIGHT && r.x >= (rect.x + rect.width)) ||
320 (dir == DIR_UP && r.y < rect.y) ||
321 (dir == DIR_DOWN && r.y >= (rect.y + rect.height))) {
322 int d = abs((r.x + r.width / 2) - (rect.x + rect.width / 2)) +
323 abs((r.y + r.height / 2) - (rect.y + rect.height / 2));
333 bool update_monitors(void)
335 PUTS("update monitors");
336 xcb_randr_get_screen_resources_current_reply_t *sres = xcb_randr_get_screen_resources_current_reply(dpy, xcb_randr_get_screen_resources_current(dpy, root), NULL);
340 monitor_t *m, *mm = NULL;
342 int len = xcb_randr_get_screen_resources_current_outputs_length(sres);
343 xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_current_outputs(sres);
345 xcb_randr_get_output_info_cookie_t cookies[len];
346 for (int i = 0; i < len; i++)
347 cookies[i] = xcb_randr_get_output_info(dpy, outputs[i], XCB_CURRENT_TIME);
349 for (m = mon_head; m != NULL; m = m->next)
352 for (int i = 0; i < len; i++) {
353 xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(dpy, cookies[i], NULL);
355 if (info->crtc != XCB_NONE) {
356 xcb_randr_get_crtc_info_reply_t *cir = xcb_randr_get_crtc_info_reply(dpy, xcb_randr_get_crtc_info(dpy, info->crtc, XCB_CURRENT_TIME), NULL);
358 xcb_rectangle_t rect = (xcb_rectangle_t) {cir->x, cir->y, cir->width, cir->height};
359 mm = get_monitor_by_id(outputs[i]);
361 mm->rectangle = rect;
363 for (desktop_t *d = mm->desk_head; d != NULL; d = d->next)
364 for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root))
365 translate_client(mm, mm, n->client);
366 arrange(mm, mm->desk);
368 PRINTF("update monitor %s (0x%X)\n", mm->name, mm->id);
370 mm = add_monitor(rect);
371 char *name = (char *)xcb_randr_get_output_info_name(info);
372 size_t name_len = MIN(sizeof(mm->name), (size_t)xcb_randr_get_output_info_name_length(info) + 1);
373 snprintf(mm->name, name_len, "%s", name);
375 PRINTF("add monitor %s (0x%X)\n", mm->name, mm->id);
379 } else if (!remove_disabled_monitors && info->connection != XCB_RANDR_CONNECTION_DISCONNECTED) {
380 m = get_monitor_by_id(outputs[i]);
388 /* initially focus the primary monitor and add the first desktop to it */
389 xcb_randr_get_output_primary_reply_t *gpo = xcb_randr_get_output_primary_reply(dpy, xcb_randr_get_output_primary(dpy, root), NULL);
391 pri_mon = get_monitor_by_id(gpo->output);
392 if (!running && pri_mon != NULL) {
395 add_desktop(pri_mon, make_desktop(NULL));
396 ewmh_update_current_desktop();
401 /* handle overlapping monitors */
402 if (merge_overlapping_monitors) {
405 monitor_t *next = m->next;
407 monitor_t *mb = mon_head;
409 monitor_t *mb_next = mb->next;
410 if (m != mb && mb->wired && contains(m->rectangle, mb->rectangle)) {
417 merge_monitors(mb, m);
427 /* merge and remove disconnected monitors */
428 if (remove_unplugged_monitors) {
431 monitor_t *next = m->next;
433 merge_monitors(m, mm);
440 /* add one desktop to each new monitor */
441 for (m = mon_head; m != NULL; m = m->next)
442 if (m->desk == NULL && (running || pri_mon == NULL || m != pri_mon))
443 add_desktop(m, make_desktop(NULL));
445 if (!running && pri_mon != NULL && mon_head != pri_mon)
446 swap_monitors(mon_head, pri_mon);
449 update_motion_recorder();
450 return (num_monitors > 0);