]> git.lizzy.rs Git - bspwm.git/blob - monitor.c
85c01824e1403e53974a695524839ee6ee1a1145
[bspwm.git] / 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 <stdlib.h>
27 #include <string.h>
28 #include "bspwm.h"
29 #include "desktop.h"
30 #include "ewmh.h"
31 #include "history.h"
32 #include "query.h"
33 #include "settings.h"
34 #include "tree.h"
35 #include "subscribe.h"
36 #include "window.h"
37 #include "monitor.h"
38
39 monitor_t *make_monitor(xcb_rectangle_t rect)
40 {
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;
45         m->rectangle = rect;
46         m->top_padding = m->right_padding = m->bottom_padding = m->left_padding = 0;
47         m->wired = true;
48         m->num_sticky = 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) {
55                 window_show(m->root);
56         }
57         return m;
58 }
59
60 monitor_t *find_monitor(char *name)
61 {
62         for (monitor_t *m = mon_head; m != NULL; m = m->next)
63                 if (streq(m->name, name))
64                         return m;
65         return NULL;
66 }
67
68 monitor_t *get_monitor_by_id(xcb_randr_output_t id)
69 {
70         for (monitor_t *m = mon_head; m != NULL; m = m->next)
71                 if (m->id == id)
72                         return m;
73         return NULL;
74 }
75
76 void embrace_client(monitor_t *m, client_t *c)
77 {
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;
86 }
87
88 void translate_client(monitor_t *ms, monitor_t *md, client_t *c)
89 {
90         if (frozen_pointer->action != ACTION_NONE || ms == md)
91                 return;
92
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);
104
105         int dx_s = c->floating_rectangle.x - ms->rectangle.x;
106         int dy_s = c->floating_rectangle.y - ms->rectangle.y;
107
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);
110
111         int deno_x = ms->rectangle.width - c->floating_rectangle.width;
112         int deno_y = ms->rectangle.height - c->floating_rectangle.height;
113
114         int dx_d = (deno_x == 0 ? 0 : nume_x / deno_x);
115         int dy_d = (deno_y == 0 ? 0 : nume_y / deno_y);
116
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;
122 }
123
124 void update_root(monitor_t *m)
125 {
126         xcb_rectangle_t rect = m->rectangle;
127         window_move_resize(m->root, rect.x, rect.y, rect.width, rect.height);
128 }
129
130 void focus_monitor(monitor_t *m)
131 {
132         if (mon == m)
133                 return;
134
135         PRINTF("focus monitor %s\n", m->name);
136         put_status(SBSC_MASK_MONITOR_FOCUS, "monitor_focus %s\n", m->name);
137
138         mon = m;
139
140         if (pointer_follows_monitor)
141                 center_pointer(m->rectangle);
142
143         ewmh_update_current_desktop();
144         put_status(SBSC_MASK_REPORT);
145 }
146
147 monitor_t *add_monitor(xcb_rectangle_t rect)
148 {
149         monitor_t *m = make_monitor(rect);
150         put_status(SBSC_MASK_MONITOR_ADD, "monitor_add %s\n", m->name);
151
152         if (mon == NULL) {
153                 mon = m;
154                 mon_head = m;
155                 mon_tail = m;
156         } else {
157                 mon_tail->next = m;
158                 m->prev = mon_tail;
159                 mon_tail = m;
160         }
161
162         num_monitors++;
163         return m;
164 }
165
166 void remove_monitor(monitor_t *m)
167 {
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);
170
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);
176         if (prev != NULL)
177                 prev->next = next;
178         if (next != NULL)
179                 next->prev = prev;
180         if (mon_head == m)
181                 mon_head = next;
182         if (mon_tail == m)
183                 mon_tail = prev;
184         if (pri_mon == m)
185                 pri_mon = NULL;
186         if (mon == m) {
187                 mon = (last_mon == NULL ? (prev == NULL ? next : prev) : last_mon);
188                 if (mon != NULL && mon->desk != NULL)
189                         update_current();
190         }
191         xcb_destroy_window(dpy, m->root);
192         free(m);
193         num_monitors--;
194         put_status(SBSC_MASK_REPORT);
195 }
196
197 void merge_monitors(monitor_t *ms, monitor_t *md)
198 {
199         if (ms == NULL || md == NULL || ms == md) {
200                 return;
201         }
202
203         PRINTF("merge %s into %s\n", ms->name, md->name);
204
205         desktop_t *d = ms->desk_head;
206         while (d != NULL) {
207                 desktop_t *next = d->next;
208                 if (d->root != NULL || strstr(d->name, DEFAULT_DESK_NAME) == NULL)
209                         transfer_desktop(ms, md, d);
210                 d = next;
211         }
212 }
213
214 void swap_monitors(monitor_t *m1, monitor_t *m2)
215 {
216         if (m1 == NULL || m2 == NULL || m1 == m2)
217                 return;
218
219         if (mon_head == m1)
220                 mon_head = m2;
221         else if (mon_head == m2)
222                 mon_head = m1;
223         if (mon_tail == m1)
224                 mon_tail = m2;
225         else if (mon_tail == m2)
226                 mon_tail = m1;
227
228         monitor_t *p1 = m1->prev;
229         monitor_t *n1 = m1->next;
230         monitor_t *p2 = m2->prev;
231         monitor_t *n2 = m2->next;
232
233         if (p1 != NULL && p1 != m2)
234                 p1->next = m2;
235         if (n1 != NULL && n1 != m2)
236                 n1->prev = m2;
237         if (p2 != NULL && p2 != m1)
238                 p2->next = m1;
239         if (n2 != NULL && n2 != m1)
240                 n2->prev = m1;
241
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;
246
247         ewmh_update_wm_desktops();
248         ewmh_update_desktop_names();
249         ewmh_update_current_desktop();
250         put_status(SBSC_MASK_REPORT);
251 }
252
253 monitor_t *closest_monitor(monitor_t *m, cycle_dir_t dir, desktop_select_t sel)
254 {
255         monitor_t *f = (dir == CYCLE_PREV ? m->prev : m->next);
256         if (f == NULL)
257                 f = (dir == CYCLE_PREV ? mon_tail : mon_head);
258
259         while (f != m) {
260                 coordinates_t loc = {m, m->desk, NULL};
261                 if (desktop_matches(&loc, &loc, sel))
262                         return f;
263                 f = (dir == CYCLE_PREV ? m->prev : m->next);
264                 if (f == NULL)
265                         f = (dir == CYCLE_PREV ? mon_tail : mon_head);
266         }
267
268         return NULL;
269 }
270
271 bool is_inside_monitor(monitor_t *m, xcb_point_t pt)
272 {
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));
276 }
277
278 monitor_t *monitor_from_point(xcb_point_t pt)
279 {
280         for (monitor_t *m = mon_head; m != NULL; m = m->next)
281                 if (is_inside_monitor(m, pt))
282                         return m;
283         return NULL;
284 }
285
286 monitor_t *monitor_from_client(client_t *c)
287 {
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;
293                 int dmin = INT_MAX;
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);
297                         if (d < dmin) {
298                                 dmin = d;
299                                 nearest = m;
300                         }
301                 }
302         }
303         return nearest;
304 }
305
306 monitor_t *nearest_monitor(monitor_t *m, direction_t dir, desktop_select_t sel)
307 {
308         int dmin = INT_MAX;
309         monitor_t *nearest = NULL;
310         xcb_rectangle_t rect = m->rectangle;
311         for (monitor_t *f = mon_head; f != NULL; f = f->next) {
312                 if (f == m)
313                         continue;
314                 coordinates_t loc = {f, f->desk, NULL};
315                 if (!desktop_matches(&loc, &loc, sel))
316                         continue;
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));
324                         if (d < dmin) {
325                                 dmin = d;
326                                 nearest = f;
327                         }
328                 }
329         }
330         return nearest;
331 }
332
333 bool update_monitors(void)
334 {
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);
337         if (sres == NULL)
338                 return false;
339
340         monitor_t *m, *mm = NULL;
341
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);
344
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);
348
349         for (m = mon_head; m != NULL; m = m->next)
350                 m->wired = false;
351
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);
354                 if (info != 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);
357                                 if (cir != 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]);
360                                         if (mm != NULL) {
361                                                 mm->rectangle = rect;
362                                                 update_root(mm);
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);
367                                                 mm->wired = true;
368                                                 PRINTF("update monitor %s (0x%X)\n", mm->name, mm->id);
369                                         } else {
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);
374                                                 mm->id = outputs[i];
375                                                 PRINTF("add monitor %s (0x%X)\n", mm->name, mm->id);
376                                         }
377                                 }
378                                 free(cir);
379                         } else if (!remove_disabled_monitors && info->connection != XCB_RANDR_CONNECTION_DISCONNECTED) {
380                                 m = get_monitor_by_id(outputs[i]);
381                                 if (m != NULL)
382                                         m->wired = true;
383                         }
384                 }
385                 free(info);
386         }
387
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);
390         if (gpo != NULL) {
391                 pri_mon = get_monitor_by_id(gpo->output);
392                 if (!running && pri_mon != NULL) {
393                         if (mon != pri_mon)
394                                 mon = pri_mon;
395                         add_desktop(pri_mon, make_desktop(NULL));
396                         ewmh_update_current_desktop();
397                 }
398         }
399         free(gpo);
400
401         /* handle overlapping monitors */
402         if (merge_overlapping_monitors) {
403                 m = mon_head;
404                 while (m != NULL) {
405                         monitor_t *next = m->next;
406                         if (m->wired) {
407                                 monitor_t *mb = mon_head;
408                                 while (mb != NULL) {
409                                         monitor_t *mb_next = mb->next;
410                                         if (m != mb && mb->wired && contains(m->rectangle, mb->rectangle)) {
411                                                 if (mm == mb) {
412                                                         mm = m;
413                                                 }
414                                                 if (next == mb) {
415                                                         next = mb_next;
416                                                 }
417                                                 merge_monitors(mb, m);
418                                                 remove_monitor(mb);
419                                         }
420                                         mb = mb_next;
421                                 }
422                         }
423                         m = next;
424                 }
425         }
426
427         /* merge and remove disconnected monitors */
428         if (remove_unplugged_monitors) {
429                 m = mon_head;
430                 while (m != NULL) {
431                         monitor_t *next = m->next;
432                         if (!m->wired) {
433                                 merge_monitors(m, mm);
434                                 remove_monitor(m);
435                         }
436                         m = next;
437                 }
438         }
439
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));
444
445         if (!running && pri_mon != NULL && mon_head != pri_mon)
446                 swap_monitors(mon_head, pri_mon);
447
448         free(sres);
449         update_motion_recorder();
450         return (num_monitors > 0);
451 }