]> git.lizzy.rs Git - bspwm.git/blob - src/monitor.c
Set the input focus before unmapping windows
[bspwm.git] / src / monitor.c
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
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
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
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
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
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
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
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
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
199 void add_monitor(monitor_t *m)
200 {
201         xcb_rectangle_t r = m->rectangle;
202
203         if (mon == NULL) {
204                 mon = m;
205                 mon_head = m;
206                 mon_tail = m;
207         } else {
208                 monitor_t *a = mon_head;
209                 while (a != NULL && rect_cmp(m->rectangle, a->rectangle) > 0) {
210                         a = a->next;
211                 }
212                 if (a != NULL) {
213                         monitor_t *b = a->prev;
214                         if (b != NULL) {
215                                 b->next = m;
216                         } else {
217                                 mon_head = m;
218                         }
219                         m->prev = b;
220                         m->next = a;
221                         a->prev = m;
222                 } else {
223                         mon_tail->next = m;
224                         m->prev = mon_tail;
225                         mon_tail = m;
226                 }
227         }
228
229         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);
230
231         put_status(SBSC_MASK_REPORT);
232 }
233
234 void unlink_monitor(monitor_t *m)
235 {
236         monitor_t *prev = m->prev;
237         monitor_t *next = m->next;
238
239         if (prev != NULL) {
240                 prev->next = next;
241         }
242
243         if (next != NULL) {
244                 next->prev = prev;
245         }
246
247         if (mon_head == m) {
248                 mon_head = next;
249         }
250
251         if (mon_tail == m) {
252                 mon_tail = prev;
253         }
254
255         if (pri_mon == m) {
256                 pri_mon = NULL;
257         }
258
259         if (mon == m) {
260                 mon = NULL;
261         }
262 }
263
264 void remove_monitor(monitor_t *m)
265 {
266         put_status(SBSC_MASK_MONITOR_REMOVE, "monitor_remove 0x%08X\n", m->id);
267
268         while (m->desk_head != NULL) {
269                 remove_desktop(m, m->desk_head);
270         }
271
272         monitor_t *last_mon = mon;
273
274         unlink_monitor(m);
275         xcb_destroy_window(dpy, m->root);
276         free(m);
277
278         if (mon != last_mon) {
279                 focus_node(NULL, NULL, NULL);
280         }
281
282         put_status(SBSC_MASK_REPORT);
283 }
284
285 void merge_monitors(monitor_t *ms, monitor_t *md)
286 {
287         if (ms == NULL || md == NULL || ms == md) {
288                 return;
289         }
290
291         desktop_t *d = ms->desk_head;
292         while (d != NULL) {
293                 desktop_t *next = d->next;
294                 transfer_desktop(ms, md, d, false);
295                 d = next;
296         }
297 }
298
299 bool swap_monitors(monitor_t *m1, monitor_t *m2)
300 {
301         if (m1 == NULL || m2 == NULL || m1 == m2) {
302                 return false;
303         }
304
305         put_status(SBSC_MASK_MONITOR_SWAP, "monitor_swap 0x%08X 0x%08X\n", m1->id, m2->id);
306
307         if (mon_head == m1) {
308                 mon_head = m2;
309         } else if (mon_head == m2) {
310                 mon_head = m1;
311         }
312         if (mon_tail == m1) {
313                 mon_tail = m2;
314         } else if (mon_tail == m2) {
315                 mon_tail = m1;
316         }
317
318         monitor_t *p1 = m1->prev;
319         monitor_t *n1 = m1->next;
320         monitor_t *p2 = m2->prev;
321         monitor_t *n2 = m2->next;
322
323         if (p1 != NULL && p1 != m2) {
324                 p1->next = m2;
325         }
326         if (n1 != NULL && n1 != m2) {
327                 n1->prev = m2;
328         }
329         if (p2 != NULL && p2 != m1) {
330                 p2->next = m1;
331         }
332         if (n2 != NULL && n2 != m1) {
333                 n2->prev = m1;
334         }
335
336         m1->prev = p2 == m1 ? m2 : p2;
337         m1->next = n2 == m1 ? m2 : n2;
338         m2->prev = p1 == m2 ? m1 : p1;
339         m2->next = n1 == m2 ? m1 : n1;
340
341         ewmh_update_wm_desktops();
342         ewmh_update_desktop_names();
343         ewmh_update_desktop_viewport();
344         ewmh_update_current_desktop();
345
346         put_status(SBSC_MASK_REPORT);
347         return true;
348 }
349
350 monitor_t *closest_monitor(monitor_t *m, cycle_dir_t dir, monitor_select_t *sel)
351 {
352         monitor_t *f = (dir == CYCLE_PREV ? m->prev : m->next);
353
354         if (f == NULL) {
355                 f = (dir == CYCLE_PREV ? mon_tail : mon_head);
356         }
357
358         while (f != m) {
359                 coordinates_t loc = {f, NULL, NULL};
360                 if (monitor_matches(&loc, &loc, sel)) {
361                         return f;
362                 }
363                 f = (dir == CYCLE_PREV ? f->prev : f->next);
364                 if (f == NULL) {
365                         f = (dir == CYCLE_PREV ? mon_tail : mon_head);
366                 }
367         }
368
369         return NULL;
370 }
371
372 bool is_inside_monitor(monitor_t *m, xcb_point_t pt)
373 {
374         return is_inside(pt, m->rectangle);
375 }
376
377 monitor_t *monitor_from_point(xcb_point_t pt)
378 {
379         for (monitor_t *m = mon_head; m != NULL; m = m->next) {
380                 if (is_inside_monitor(m, pt)) {
381                         return m;
382                 }
383         }
384         return NULL;
385 }
386
387 monitor_t *monitor_from_client(client_t *c)
388 {
389         int16_t xc = c->floating_rectangle.x + c->floating_rectangle.width/2;
390         int16_t yc = c->floating_rectangle.y + c->floating_rectangle.height/2;
391         xcb_point_t pt = {xc, yc};
392         monitor_t *nearest = monitor_from_point(pt);
393         if (nearest == NULL) {
394                 int dmin = INT_MAX;
395                 for (monitor_t *m = mon_head; m != NULL; m = m->next) {
396                         xcb_rectangle_t r = m->rectangle;
397                         int d = abs((r.x + r.width / 2) - xc) + abs((r.y + r.height / 2) - yc);
398                         if (d < dmin) {
399                                 dmin = d;
400                                 nearest = m;
401                         }
402                 }
403         }
404         return nearest;
405 }
406
407 monitor_t *nearest_monitor(monitor_t *m, direction_t dir, monitor_select_t *sel)
408 {
409         uint32_t dmin = UINT32_MAX;
410         monitor_t *nearest = NULL;
411         xcb_rectangle_t rect = m->rectangle;
412         for (monitor_t *f = mon_head; f != NULL; f = f->next) {
413                 coordinates_t loc = {f, NULL, NULL};
414                 xcb_rectangle_t r = f->rectangle;
415                 if (f == m ||
416                     !monitor_matches(&loc, &loc, sel) ||
417                     !on_dir_side(rect, r, dir)) {
418                         continue;
419                 }
420                 uint32_t d = boundary_distance(rect, r, dir);
421                 if (d < dmin) {
422                         dmin = d;
423                         nearest = f;
424                 }
425         }
426         return nearest;
427 }
428
429 bool find_any_monitor(coordinates_t *ref, coordinates_t *dst, monitor_select_t *sel)
430 {
431         for (monitor_t *m = mon_head; m != NULL; m = m->next) {
432                 coordinates_t loc = {m, NULL, NULL};
433                 if (monitor_matches(&loc, ref, sel)) {
434                         *dst = loc;
435                         return true;
436                 }
437         }
438         return false;
439 }
440
441 bool update_monitors(void)
442 {
443         xcb_randr_get_screen_resources_reply_t *sres = xcb_randr_get_screen_resources_reply(dpy, xcb_randr_get_screen_resources(dpy, root), NULL);
444         if (sres == NULL) {
445                 return false;
446         }
447
448         monitor_t *last_wired = NULL;
449
450         int len = xcb_randr_get_screen_resources_outputs_length(sres);
451         xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_outputs(sres);
452
453         xcb_randr_get_output_info_cookie_t cookies[len];
454         for (int i = 0; i < len; i++) {
455                 cookies[i] = xcb_randr_get_output_info(dpy, outputs[i], XCB_CURRENT_TIME);
456         }
457
458         for (monitor_t *m = mon_head; m != NULL; m = m->next) {
459                 m->wired = false;
460         }
461
462         for (int i = 0; i < len; i++) {
463                 xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(dpy, cookies[i], NULL);
464                 if (info != NULL) {
465                         if (info->crtc != XCB_NONE) {
466                                 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);
467                                 if (cir != NULL) {
468                                         xcb_rectangle_t rect = (xcb_rectangle_t) {cir->x, cir->y, cir->width, cir->height};
469                                         last_wired = get_monitor_by_randr_id(outputs[i]);
470                                         if (last_wired != NULL) {
471                                                 update_root(last_wired, &rect);
472                                                 last_wired->wired = true;
473                                         } else {
474                                                 char *name = (char *) xcb_randr_get_output_info_name(info);
475                                                 size_t len = (size_t) xcb_randr_get_output_info_name_length(info);
476                                                 char *name_copy = copy_string(name, len);
477                                                 last_wired = make_monitor(name_copy, &rect, XCB_NONE);
478                                                 free(name_copy);
479                                                 last_wired->randr_id = outputs[i];
480                                                 add_monitor(last_wired);
481                                         }
482                                 }
483                                 free(cir);
484                         } else if (!remove_disabled_monitors && info->connection != XCB_RANDR_CONNECTION_DISCONNECTED) {
485                                 monitor_t *m = get_monitor_by_randr_id(outputs[i]);
486                                 if (m != NULL) {
487                                         m->wired = true;
488                                 }
489                         }
490                 }
491                 free(info);
492         }
493
494         xcb_randr_get_output_primary_reply_t *gpo = xcb_randr_get_output_primary_reply(dpy, xcb_randr_get_output_primary(dpy, root), NULL);
495         if (gpo != NULL) {
496                 pri_mon = get_monitor_by_randr_id(gpo->output);
497         }
498         free(gpo);
499
500         /* handle overlapping monitors */
501         if (merge_overlapping_monitors) {
502                 monitor_t *m = mon_head;
503                 while (m != NULL) {
504                         monitor_t *next = m->next;
505                         if (m->wired) {
506                                 monitor_t *mb = mon_head;
507                                 while (mb != NULL) {
508                                         monitor_t *mb_next = mb->next;
509                                         if (m != mb && mb->wired && contains(m->rectangle, mb->rectangle)) {
510                                                 if (last_wired == mb) {
511                                                         last_wired = m;
512                                                 }
513                                                 if (next == mb) {
514                                                         next = mb_next;
515                                                 }
516                                                 merge_monitors(mb, m);
517                                                 remove_monitor(mb);
518                                         }
519                                         mb = mb_next;
520                                 }
521                         }
522                         m = next;
523                 }
524         }
525
526         /* merge and remove disconnected monitors */
527         if (remove_unplugged_monitors) {
528                 monitor_t *m = mon_head;
529                 while (m != NULL) {
530                         monitor_t *next = m->next;
531                         if (!m->wired) {
532                                 merge_monitors(m, last_wired);
533                                 remove_monitor(m);
534                         }
535                         m = next;
536                 }
537         }
538
539         /* add one desktop to each new monitor */
540         for (monitor_t *m = mon_head; m != NULL; m = m->next) {
541                 if (m->desk == NULL) {
542                         add_desktop(m, make_desktop(NULL, XCB_NONE));
543                 }
544         }
545
546         if (!running && mon != NULL) {
547                 if (pri_mon != NULL) {
548                         mon = pri_mon;
549                 }
550                 center_pointer(mon->rectangle);
551                 ewmh_update_current_desktop();
552         }
553
554         free(sres);
555
556         return (mon != NULL);
557 }