1 /* Copyright (c) 2012, Bastien Dejean
2  * All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
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.
12  *
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.
23  */
24 
25 #include <limits.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <stdbool.h>
30 #include "bspwm.h"
31 #include "desktop.h"
32 #include "ewmh.h"
33 #include "query.h"
34 #include "pointer.h"
35 #include "settings.h"
36 #include "geometry.h"
37 #include "tree.h"
38 #include "subscribe.h"
39 #include "window.h"
40 #include "monitor.h"
41 
make_monitor(const char * name,xcb_rectangle_t * rect,uint32_t id)42 monitor_t *make_monitor(const char *name, xcb_rectangle_t *rect, uint32_t id)
43 {
44 	monitor_t *m = calloc(1, sizeof(monitor_t));
45 	if (id == XCB_NONE) {
46 		m->id = xcb_generate_id(dpy);
47 	}
48 	m->randr_id = XCB_NONE;
49 	snprintf(m->name, sizeof(m->name), "%s", name == NULL ? DEFAULT_MON_NAME : name);
50 	m->padding = padding;
51 	m->border_width = border_width;
52 	m->window_gap = window_gap;
53 	m->root = XCB_NONE;
54 	m->prev = m->next = NULL;
55 	m->desk = m->desk_head = m->desk_tail = NULL;
56 	m->wired = true;
57 	m->sticky_count = 0;
58 	if (rect != NULL) {
59 		update_root(m, rect);
60 	} else {
61 		m->rectangle = (xcb_rectangle_t) {0, 0, screen_width, screen_height};
62 	}
63 	return m;
64 }
65 
update_root(monitor_t * m,xcb_rectangle_t * rect)66 void update_root(monitor_t *m, xcb_rectangle_t *rect)
67 {
68 	xcb_rectangle_t last_rect = m->rectangle;
69 	m->rectangle = *rect;
70 	if (m->root == XCB_NONE) {
71 		uint32_t values[] = {XCB_EVENT_MASK_ENTER_WINDOW};
72 		m->root = xcb_generate_id(dpy);
73 		xcb_create_window(dpy, XCB_COPY_FROM_PARENT, m->root, root,
74 		                  rect->x, rect->y, rect->width, rect->height, 0,
75 		                  XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values);
76 		xcb_icccm_set_wm_class(dpy, m->root, sizeof(ROOT_WINDOW_IC), ROOT_WINDOW_IC);
77 		xcb_icccm_set_wm_name(dpy, m->root, XCB_ATOM_STRING, 8, strlen(m->name), m->name);
78 		window_lower(m->root);
79 		if (focus_follows_pointer) {
80 			window_show(m->root);
81 		}
82 	} else {
83 		window_move_resize(m->root, rect->x, rect->y, rect->width, rect->height);
84 		put_status(SBSC_MASK_MONITOR_GEOMETRY, "monitor_geometry 0x%08X %ux%u+%i+%i\n",
85 		           m->id, rect->width, rect->height, rect->x, rect->y);
86 	}
87 	for (desktop_t *d = m->desk_head; d != NULL; d = d->next) {
88 		for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root)) {
89 			if (n->client == NULL) {
90 				continue;
91 			}
92 			adapt_geometry(&last_rect, rect, n);
93 		}
94 		arrange(m, d);
95 	}
96 	reorder_monitor(m);
97 }
98 
reorder_monitor(monitor_t * m)99 void reorder_monitor(monitor_t *m)
100 {
101 	if (m == NULL) {
102 		return;
103 	}
104 	monitor_t *prev = m->prev;
105 	while (prev != NULL && rect_cmp(m->rectangle, prev->rectangle) < 0) {
106 		swap_monitors(m, prev);
107 		prev = m->prev;
108 	}
109 	monitor_t *next = m->next;
110 	while (next != NULL && rect_cmp(m->rectangle, next->rectangle) > 0) {
111 		swap_monitors(m, next);
112 		next = m->next;
113 	}
114 }
115 
rename_monitor(monitor_t * m,const char * name)116 void rename_monitor(monitor_t *m, const char *name)
117 {
118 	put_status(SBSC_MASK_MONITOR_RENAME, "monitor_rename 0x%08X %s %s\n", m->id, m->name, name);
119 
120 	snprintf(m->name, sizeof(m->name), "%s", name);
121 	xcb_icccm_set_wm_name(dpy, m->root, XCB_ATOM_STRING, 8, strlen(m->name), m->name);
122 
123 	put_status(SBSC_MASK_REPORT);
124 }
125 
find_monitor(uint32_t id)126 monitor_t *find_monitor(uint32_t id)
127 {
128 	for (monitor_t *m = mon_head; m != NULL; m = m->next) {
129 		if (m->id == id) {
130 			return m;
131 		}
132 	}
133 	return NULL;
134 }
135 
get_monitor_by_randr_id(xcb_randr_output_t id)136 monitor_t *get_monitor_by_randr_id(xcb_randr_output_t id)
137 {
138 	for (monitor_t *m = mon_head; m != NULL; m = m->next) {
139 		if (m->randr_id == id) {
140 			return m;
141 		}
142 	}
143 	return NULL;
144 }
145 
embrace_client(monitor_t * m,client_t * c)146 void embrace_client(monitor_t *m, client_t *c)
147 {
148 	if ((c->floating_rectangle.x + c->floating_rectangle.width) <= m->rectangle.x) {
149 		c->floating_rectangle.x = m->rectangle.x;
150 	} else if (c->floating_rectangle.x >= (m->rectangle.x + m->rectangle.width)) {
151 		c->floating_rectangle.x = (m->rectangle.x + m->rectangle.width) - c->floating_rectangle.width;
152 	}
153 	if ((c->floating_rectangle.y + c->floating_rectangle.height) <= m->rectangle.y) {
154 		c->floating_rectangle.y = m->rectangle.y;
155 	} else if (c->floating_rectangle.y >= (m->rectangle.y + m->rectangle.height)) {
156 		c->floating_rectangle.y = (m->rectangle.y + m->rectangle.height) - c->floating_rectangle.height;
157 	}
158 }
159 
adapt_geometry(xcb_rectangle_t * rs,xcb_rectangle_t * rd,node_t * n)160 void adapt_geometry(xcb_rectangle_t *rs, xcb_rectangle_t *rd, node_t *n)
161 {
162 	for (node_t *f = first_extrema(n); f != NULL; f = next_leaf(f, n)) {
163 		if (f->client == NULL) {
164 			continue;
165 		}
166 		client_t *c = f->client;
167 		/* Clip the rectangle to fit into the monitor.	Without this, the fitting
168 		 * algorithm doesn't work as expected. This also conserves the
169 		 * out-of-bounds regions */
170 		int left_adjust = MAX((rs->x - c->floating_rectangle.x), 0);
171 		int top_adjust = MAX((rs->y - c->floating_rectangle.y), 0);
172 		int right_adjust = MAX((c->floating_rectangle.x + c->floating_rectangle.width) - (rs->x + rs->width), 0);
173 		int bottom_adjust = MAX((c->floating_rectangle.y + c->floating_rectangle.height) - (rs->y + rs->height), 0);
174 		c->floating_rectangle.x += left_adjust;
175 		c->floating_rectangle.y += top_adjust;
176 		c->floating_rectangle.width -= (left_adjust + right_adjust);
177 		c->floating_rectangle.height -= (top_adjust + bottom_adjust);
178 
179 		int dx_s = c->floating_rectangle.x - rs->x;
180 		int dy_s = c->floating_rectangle.y - rs->y;
181 
182 		int nume_x = dx_s * (rd->width - c->floating_rectangle.width);
183 		int nume_y = dy_s * (rd->height - c->floating_rectangle.height);
184 
185 		int deno_x = rs->width - c->floating_rectangle.width;
186 		int deno_y = rs->height - c->floating_rectangle.height;
187 
188 		int dx_d = (deno_x == 0 ? 0 : nume_x / deno_x);
189 		int dy_d = (deno_y == 0 ? 0 : nume_y / deno_y);
190 
191 		/* Translate and undo clipping */
192 		c->floating_rectangle.width += left_adjust + right_adjust;
193 		c->floating_rectangle.height += top_adjust + bottom_adjust;
194 		c->floating_rectangle.x = rd->x + dx_d - left_adjust;
195 		c->floating_rectangle.y = rd->y + dy_d - top_adjust;
196 	}
197 }
198 
focus_monitor(monitor_t * m)199 void focus_monitor(monitor_t *m)
200 {
201 	if (mon == m) {
202 		return;
203 	}
204 
205 	mon = m;
206 
207 	if (pointer_follows_monitor) {
208 		center_pointer(m->rectangle);
209 	}
210 
211 	put_status(SBSC_MASK_MONITOR_FOCUS, "monitor_focus 0x%08X\n", m->id);
212 }
213 
add_monitor(monitor_t * m)214 void add_monitor(monitor_t *m)
215 {
216 	xcb_rectangle_t r = m->rectangle;
217 
218 	if (mon == NULL) {
219 		mon = m;
220 		mon_head = m;
221 		mon_tail = m;
222 	} else {
223 		monitor_t *a = mon_head;
224 		while (a != NULL && rect_cmp(m->rectangle, a->rectangle) > 0) {
225 			a = a->next;
226 		}
227 		if (a != NULL) {
228 			monitor_t *b = a->prev;
229 			if (b != NULL) {
230 				b->next = m;
231 			} else {
232 				mon_head = m;
233 			}
234 			m->prev = b;
235 			m->next = a;
236 			a->prev = m;
237 		} else {
238 			mon_tail->next = m;
239 			m->prev = mon_tail;
240 			mon_tail = m;
241 		}
242 	}
243 
244 	put_status(SBSC_MASK_MONITOR_ADD, "monitor_add 0x%08X %s %ux%u+%i+%i\n", m->id, m->name, r.width, r.height, r.x, r.y);
245 
246 	put_status(SBSC_MASK_REPORT);
247 }
248 
unlink_monitor(monitor_t * m)249 void unlink_monitor(monitor_t *m)
250 {
251 	monitor_t *prev = m->prev;
252 	monitor_t *next = m->next;
253 
254 	if (prev != NULL) {
255 		prev->next = next;
256 	}
257 
258 	if (next != NULL) {
259 		next->prev = prev;
260 	}
261 
262 	if (mon_head == m) {
263 		mon_head = next;
264 	}
265 
266 	if (mon_tail == m) {
267 		mon_tail = prev;
268 	}
269 
270 	if (pri_mon == m) {
271 		pri_mon = NULL;
272 	}
273 
274 	if (mon == m) {
275 		mon = NULL;
276 	}
277 }
278 
remove_monitor(monitor_t * m)279 void remove_monitor(monitor_t *m)
280 {
281 	put_status(SBSC_MASK_MONITOR_REMOVE, "monitor_remove 0x%08X\n", m->id);
282 
283 	while (m->desk_head != NULL) {
284 		remove_desktop(m, m->desk_head);
285 	}
286 
287 	monitor_t *last_mon = mon;
288 
289 	unlink_monitor(m);
290 	xcb_destroy_window(dpy, m->root);
291 	free(m);
292 
293 	if (mon != last_mon) {
294 		focus_node(NULL, NULL, NULL);
295 	}
296 
297 	put_status(SBSC_MASK_REPORT);
298 }
299 
merge_monitors(monitor_t * ms,monitor_t * md)300 void merge_monitors(monitor_t *ms, monitor_t *md)
301 {
302 	if (ms == NULL || md == NULL || ms == md) {
303 		return;
304 	}
305 
306 	desktop_t *d = ms->desk_head;
307 	while (d != NULL) {
308 		desktop_t *next = d->next;
309 		transfer_desktop(ms, md, d, false);
310 		d = next;
311 	}
312 }
313 
swap_monitors(monitor_t * m1,monitor_t * m2)314 bool swap_monitors(monitor_t *m1, monitor_t *m2)
315 {
316 	if (m1 == NULL || m2 == NULL || m1 == m2) {
317 		return false;
318 	}
319 
320 	put_status(SBSC_MASK_MONITOR_SWAP, "monitor_swap 0x%08X 0x%08X\n", m1->id, m2->id);
321 
322 	if (mon_head == m1) {
323 		mon_head = m2;
324 	} else if (mon_head == m2) {
325 		mon_head = m1;
326 	}
327 	if (mon_tail == m1) {
328 		mon_tail = m2;
329 	} else if (mon_tail == m2) {
330 		mon_tail = m1;
331 	}
332 
333 	monitor_t *p1 = m1->prev;
334 	monitor_t *n1 = m1->next;
335 	monitor_t *p2 = m2->prev;
336 	monitor_t *n2 = m2->next;
337 
338 	if (p1 != NULL && p1 != m2) {
339 		p1->next = m2;
340 	}
341 	if (n1 != NULL && n1 != m2) {
342 		n1->prev = m2;
343 	}
344 	if (p2 != NULL && p2 != m1) {
345 		p2->next = m1;
346 	}
347 	if (n2 != NULL && n2 != m1) {
348 		n2->prev = m1;
349 	}
350 
351 	m1->prev = p2 == m1 ? m2 : p2;
352 	m1->next = n2 == m1 ? m2 : n2;
353 	m2->prev = p1 == m2 ? m1 : p1;
354 	m2->next = n1 == m2 ? m1 : n1;
355 
356 	ewmh_update_wm_desktops();
357 	ewmh_update_desktop_names();
358 	ewmh_update_desktop_viewport();
359 	ewmh_update_current_desktop();
360 
361 	put_status(SBSC_MASK_REPORT);
362 	return true;
363 }
364 
closest_monitor(monitor_t * m,cycle_dir_t dir,monitor_select_t * sel)365 monitor_t *closest_monitor(monitor_t *m, cycle_dir_t dir, monitor_select_t *sel)
366 {
367 	monitor_t *f = (dir == CYCLE_PREV ? m->prev : m->next);
368 
369 	if (f == NULL) {
370 		f = (dir == CYCLE_PREV ? mon_tail : mon_head);
371 	}
372 
373 	while (f != m) {
374 		coordinates_t loc = {f, NULL, NULL};
375 		if (monitor_matches(&loc, &loc, sel)) {
376 			return f;
377 		}
378 		f = (dir == CYCLE_PREV ? f->prev : f->next);
379 		if (f == NULL) {
380 			f = (dir == CYCLE_PREV ? mon_tail : mon_head);
381 		}
382 	}
383 
384 	return NULL;
385 }
386 
is_inside_monitor(monitor_t * m,xcb_point_t pt)387 bool is_inside_monitor(monitor_t *m, xcb_point_t pt)
388 {
389 	return is_inside(pt, m->rectangle);
390 }
391 
monitor_from_point(xcb_point_t pt)392 monitor_t *monitor_from_point(xcb_point_t pt)
393 {
394 	for (monitor_t *m = mon_head; m != NULL; m = m->next) {
395 		if (is_inside_monitor(m, pt)) {
396 			return m;
397 		}
398 	}
399 	return NULL;
400 }
401 
monitor_from_client(client_t * c)402 monitor_t *monitor_from_client(client_t *c)
403 {
404 	int16_t xc = c->floating_rectangle.x + c->floating_rectangle.width/2;
405 	int16_t yc = c->floating_rectangle.y + c->floating_rectangle.height/2;
406 	xcb_point_t pt = {xc, yc};
407 	monitor_t *nearest = monitor_from_point(pt);
408 	if (nearest == NULL) {
409 		int dmin = INT_MAX;
410 		for (monitor_t *m = mon_head; m != NULL; m = m->next) {
411 			xcb_rectangle_t r = m->rectangle;
412 			int d = abs((r.x + r.width / 2) - xc) + abs((r.y + r.height / 2) - yc);
413 			if (d < dmin) {
414 				dmin = d;
415 				nearest = m;
416 			}
417 		}
418 	}
419 	return nearest;
420 }
421 
nearest_monitor(monitor_t * m,direction_t dir,monitor_select_t * sel)422 monitor_t *nearest_monitor(monitor_t *m, direction_t dir, monitor_select_t *sel)
423 {
424 	uint32_t dmin = UINT32_MAX;
425 	monitor_t *nearest = NULL;
426 	xcb_rectangle_t rect = m->rectangle;
427 	for (monitor_t *f = mon_head; f != NULL; f = f->next) {
428 		coordinates_t loc = {f, NULL, NULL};
429 		xcb_rectangle_t r = f->rectangle;
430 		if (f == m ||
431 		    !monitor_matches(&loc, &loc, sel) ||
432 		    !on_dir_side(rect, r, dir)) {
433 			continue;
434 		}
435 		uint32_t d = boundary_distance(rect, r, dir);
436 		if (d < dmin) {
437 			dmin = d;
438 			nearest = f;
439 		}
440 	}
441 	return nearest;
442 }
443 
find_any_monitor(coordinates_t * ref,coordinates_t * dst,monitor_select_t * sel)444 bool find_any_monitor(coordinates_t *ref, coordinates_t *dst, monitor_select_t *sel)
445 {
446 	for (monitor_t *m = mon_head; m != NULL; m = m->next) {
447 		coordinates_t loc = {m, NULL, NULL};
448 		if (monitor_matches(&loc, ref, sel)) {
449 			*dst = loc;
450 			return true;
451 		}
452 	}
453 	return false;
454 }
455 
update_monitors(void)456 bool update_monitors(void)
457 {
458 	xcb_randr_get_screen_resources_reply_t *sres = xcb_randr_get_screen_resources_reply(dpy, xcb_randr_get_screen_resources(dpy, root), NULL);
459 	if (sres == NULL) {
460 		return false;
461 	}
462 
463 	monitor_t *last_wired = NULL;
464 
465 	int len = xcb_randr_get_screen_resources_outputs_length(sres);
466 	xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(sres);
467 
468 	xcb_randr_get_output_info_cookie_t cookies[len];
469 	for (int i = 0; i < len; i++) {
470 		cookies[i] = xcb_randr_get_output_info(dpy, outputs[i], XCB_CURRENT_TIME);
471 	}
472 
473 	for (monitor_t *m = mon_head; m != NULL; m = m->next) {
474 		m->wired = false;
475 	}
476 
477 	for (int i = 0; i < len; i++) {
478 		xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(dpy, cookies[i], NULL);
479 		if (info != NULL) {
480 			if (info->crtc != XCB_NONE) {
481 				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);
482 				if (cir != NULL) {
483 					xcb_rectangle_t rect = (xcb_rectangle_t) {cir->x, cir->y, cir->width, cir->height};
484 					last_wired = get_monitor_by_randr_id(outputs[i]);
485 					if (last_wired != NULL) {
486 						update_root(last_wired, &rect);
487 						last_wired->wired = true;
488 					} else {
489 						char *name = (char *) xcb_randr_get_output_info_name(info);
490 						size_t len = (size_t) xcb_randr_get_output_info_name_length(info);
491 						char *name_copy = copy_string(name, len);
492 						last_wired = make_monitor(name_copy, &rect, XCB_NONE);
493 						free(name_copy);
494 						last_wired->randr_id = outputs[i];
495 						add_monitor(last_wired);
496 					}
497 				}
498 				free(cir);
499 			} else if (!remove_disabled_monitors && info->connection != XCB_RANDR_CONNECTION_DISCONNECTED) {
500 				monitor_t *m = get_monitor_by_randr_id(outputs[i]);
501 				if (m != NULL) {
502 					m->wired = true;
503 				}
504 			}
505 		}
506 		free(info);
507 	}
508 
509 	xcb_randr_get_output_primary_reply_t *gpo = xcb_randr_get_output_primary_reply(dpy, xcb_randr_get_output_primary(dpy, root), NULL);
510 	if (gpo != NULL) {
511 		pri_mon = get_monitor_by_randr_id(gpo->output);
512 	}
513 	free(gpo);
514 
515 	/* handle overlapping monitors */
516 	if (merge_overlapping_monitors) {
517 		monitor_t *m = mon_head;
518 		while (m != NULL) {
519 			monitor_t *next = m->next;
520 			if (m->wired) {
521 				monitor_t *mb = mon_head;
522 				while (mb != NULL) {
523 					monitor_t *mb_next = mb->next;
524 					if (m != mb && mb->wired && contains(m->rectangle, mb->rectangle)) {
525 						if (last_wired == mb) {
526 							last_wired = m;
527 						}
528 						if (next == mb) {
529 							next = mb_next;
530 						}
531 						merge_monitors(mb, m);
532 						remove_monitor(mb);
533 					}
534 					mb = mb_next;
535 				}
536 			}
537 			m = next;
538 		}
539 	}
540 
541 	/* merge and remove disconnected monitors */
542 	if (remove_unplugged_monitors) {
543 		monitor_t *m = mon_head;
544 		while (m != NULL) {
545 			monitor_t *next = m->next;
546 			if (!m->wired) {
547 				merge_monitors(m, last_wired);
548 				remove_monitor(m);
549 			}
550 			m = next;
551 		}
552 	}
553 
554 	/* add one desktop to each new monitor */
555 	for (monitor_t *m = mon_head; m != NULL; m = m->next) {
556 		if (m->desk == NULL) {
557 			add_desktop(m, make_desktop(NULL, XCB_NONE));
558 		}
559 	}
560 
561 	if (!running && mon != NULL) {
562 		if (pri_mon != NULL) {
563 			mon = pri_mon;
564 		}
565 		center_pointer(mon->rectangle);
566 		ewmh_update_current_desktop();
567 	}
568 
569 	free(sres);
570 
571 	return (mon != NULL);
572 }
573