]> git.lizzy.rs Git - bspwm.git/blob - monitor.c
Merge overlapping monitors
[bspwm.git] / monitor.c
1 #include <stdlib.h>
2 #include <string.h>
3 #include <limits.h>
4 #include "settings.h"
5 #include "bspwm.h"
6 #include "tree.h"
7 #include "desktop.h"
8 #include "window.h"
9 #include "query.h"
10 #include "ewmh.h"
11 #include "monitor.h"
12
13 monitor_t *make_monitor(xcb_rectangle_t rect)
14 {
15     monitor_t *m = malloc(sizeof(monitor_t));
16     snprintf(m->name, sizeof(m->name), "%s%02d", DEFAULT_MON_NAME, ++monitor_uid);
17     m->prev = m->next = NULL;
18     m->desk = m->last_desk = NULL;
19     m->rectangle = rect;
20     m->top_padding = m->right_padding = m->bottom_padding = m->left_padding = 0;
21     m->wired = true;
22     return m;
23 }
24
25 monitor_t *find_monitor(char *name)
26 {
27     for (monitor_t *m = mon_head; m != NULL; m = m->next)
28         if (streq(m->name, name))
29             return m;
30     return NULL;
31 }
32
33 monitor_t *get_monitor_by_id(xcb_randr_output_t id)
34 {
35     for (monitor_t *m = mon_head; m != NULL; m = m->next)
36         if (m->id == id)
37             return m;
38     return NULL;
39 }
40
41 void fit_monitor(monitor_t *m, client_t *c)
42 {
43     xcb_rectangle_t crect = c->floating_rectangle;
44     xcb_rectangle_t mrect = m->rectangle;
45     while (crect.x < mrect.x)
46         crect.x += mrect.width;
47     while (crect.x > (mrect.x + mrect.width - 1))
48         crect.x -= mrect.width;
49     while (crect.y < mrect.y)
50         crect.y += mrect.height;
51     while (crect.y > (mrect.y + mrect.height - 1))
52         crect.y -= mrect.height;
53     c->floating_rectangle = crect;
54 }
55
56 void select_monitor(monitor_t *m)
57 {
58     if (mon == m)
59         return;
60
61     PRINTF("select monitor %s\n", m->name);
62
63     last_mon = mon;
64     mon = m;
65
66     if (pointer_follows_monitor)
67         center_pointer(m);
68
69     ewmh_update_current_desktop();
70     put_status();
71 }
72
73 monitor_t *add_monitor(xcb_rectangle_t rect)
74 {
75     monitor_t *m = make_monitor(rect);
76     if (mon == NULL) {
77         mon = m;
78         mon_head = m;
79         mon_tail = m;
80     } else {
81         mon_tail->next = m;
82         m->prev = mon_tail;
83         mon_tail = m;
84     }
85     num_monitors++;
86     return m;
87 }
88
89 void remove_monitor(monitor_t *m)
90 {
91     PRINTF("remove monitor %s (0x%X)\n", m->name, m->id);
92
93     while (m->desk_head != NULL)
94         remove_desktop(m, m->desk_head);
95     monitor_t *prev = m->prev;
96     monitor_t *next = m->next;
97     if (prev != NULL)
98         prev->next = next;
99     if (next != NULL)
100         next->prev = prev;
101     if (mon_head == m)
102         mon_head = next;
103     if (mon_tail == m)
104         mon_tail = prev;
105     if (last_mon == m)
106         last_mon = NULL;
107     if (pri_mon == m)
108         pri_mon = NULL;
109     if (mon == m) {
110         monitor_t *mm = (last_mon == NULL ? (prev == NULL ? next : prev) : last_mon);
111         if (mm != NULL) {
112             focus_node(mm, mm->desk, mm->desk->focus);
113             last_mon = NULL;
114         } else {
115             mon = NULL;
116         }
117     }
118     free(m);
119     num_monitors--;
120     put_status();
121 }
122
123 void merge_monitors(monitor_t *ms, monitor_t *md)
124 {
125     PRINTF("merge %s into %s\n", ms->name, md->name);
126
127     desktop_t *d = ms->desk_head;
128     while (d != NULL) {
129         desktop_t *next = d->next;
130         transfer_desktop(ms, md, d);
131         d = next;
132     }
133 }
134
135 void swap_monitors(monitor_t *m1, monitor_t *m2)
136 {
137     if (m1 == NULL || m2 == NULL || m1 == m2)
138         return;
139
140     if (mon_head == m1)
141         mon_head = m2;
142     else if (mon_head == m2)
143         mon_head = m1;
144     if (mon_tail == m1)
145         mon_tail = m2;
146     else if (mon_tail == m2)
147         mon_tail = m1;
148
149     monitor_t *p1 = m1->prev;
150     monitor_t *n1 = m1->next;
151     monitor_t *p2 = m2->prev;
152     monitor_t *n2 = m2->next;
153
154     if (p1 != NULL && p1 != m2)
155         p1->next = m2;
156     if (n1 != NULL && n1 != m2)
157         n1->prev = m2;
158     if (p2 != NULL && p2 != m1)
159         p2->next = m1;
160     if (n2 != NULL && n2 != m1)
161         n2->prev = m1;
162
163     m1->prev = p2 == m1 ? m2 : p2;
164     m1->next = n2 == m1 ? m2 : n2;
165     m2->prev = p1 == m2 ? m1 : p1;
166     m2->next = n1 == m2 ? m1 : n1;
167
168     ewmh_update_wm_desktops();
169     ewmh_update_desktop_names();
170     ewmh_update_current_desktop();
171     put_status();
172 }
173
174 monitor_t *closest_monitor(monitor_t *m, cycle_dir_t dir, desktop_select_t sel)
175 {
176     monitor_t *f = (dir == CYCLE_PREV ? m->prev : m->next);
177     if (f == NULL)
178         f = (dir == CYCLE_PREV ? mon_tail : mon_head);
179
180     while (f != m) {
181         if (desktop_matches(f->desk, sel))
182             return f;
183         f = (dir == CYCLE_PREV ? m->prev : m->next);
184         if (f == NULL)
185             f = (dir == CYCLE_PREV ? mon_tail : mon_head);
186     }
187
188     return NULL;
189 }
190
191 monitor_t *nearest_monitor(monitor_t *m, direction_t dir, desktop_select_t sel)
192 {
193     int dmin = INT_MAX;
194     monitor_t *nearest = NULL;
195     xcb_rectangle_t rect = m->rectangle;
196     for (monitor_t *f = mon_head; f != NULL; f = f->next) {
197         if (f == m)
198             continue;
199         if (!desktop_matches(f->desk, sel))
200             continue;
201         xcb_rectangle_t r = f->rectangle;
202         if ((dir == DIR_LEFT && r.x < rect.x) ||
203                 (dir == DIR_RIGHT && r.x >= (rect.x + rect.width)) ||
204                 (dir == DIR_UP && r.y < rect.y) ||
205                 (dir == DIR_DOWN && r.y >= (rect.y + rect.height))) {
206             int d = abs((r.x + r.width / 2) - (rect.x + rect.width / 2)) +
207                 abs((r.y + r.height / 2) - (rect.y + rect.height / 2));
208             if (d < dmin) {
209                 dmin = d;
210                 nearest = f;
211             }
212         }
213     }
214     return nearest;
215 }
216
217 bool import_monitors(void)
218 {
219     PUTS("import monitors");
220     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);
221     if (sres == NULL)
222         return false;
223
224     monitor_t *m, *mm = NULL;
225     unsigned int num = 0;
226
227     int len = xcb_randr_get_screen_resources_current_outputs_length(sres);
228     xcb_randr_output_t *outputs = xcb_randr_get_screen_resources_current_outputs(sres);
229
230     xcb_randr_get_output_info_cookie_t cookies[len];
231     for (int i = 0; i < len; i++)
232         cookies[i] = xcb_randr_get_output_info(dpy, outputs[i], XCB_CURRENT_TIME);
233
234     for (m = mon_head; m != NULL; m = m->next)
235         m->wired = false;
236
237     for (int i = 0; i < len; i++) {
238         xcb_randr_get_output_info_reply_t *info = xcb_randr_get_output_info_reply(dpy, cookies[i], NULL);
239         if (info != NULL && info->crtc != XCB_NONE) {
240
241             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);
242             if (cir != NULL) {
243                 xcb_rectangle_t rect = (xcb_rectangle_t) {cir->x, cir->y, cir->width, cir->height};
244                 mm = get_monitor_by_id(outputs[i]);
245                 if (mm != NULL) {
246                     mm->rectangle = rect;
247                     for (desktop_t *d = mm->desk_head; d != NULL; d = d->next)
248                         for (node_t *n = first_extrema(d->root); n != NULL; n = next_leaf(n, d->root))
249                             fit_monitor(mm, n->client);
250                     arrange(mm, mm->desk);
251                     mm->wired = true;
252                     PRINTF("update monitor %s (0x%X)\n", mm->name, mm->id);
253                 } else {
254                     mm = add_monitor(rect);
255                     char *name = (char *)xcb_randr_get_output_info_name(info);
256                     size_t name_len = MIN(sizeof(mm->name), (size_t)xcb_randr_get_output_info_name_length(info));
257                     snprintf(mm->name, name_len, "%s", name);
258                     mm->id = outputs[i];
259                     PRINTF("add monitor %s (0x%X)\n", mm->name, mm->id);
260                 }
261                 num++;
262             }
263             free(cir);
264         }
265         free(info);
266     }
267
268     /* initially focus the primary monitor and add the first desktop to it */
269     xcb_randr_get_output_primary_reply_t *gpo = xcb_randr_get_output_primary_reply(dpy, xcb_randr_get_output_primary(dpy, root), NULL);
270     if (gpo != NULL) {
271         pri_mon = get_monitor_by_id(gpo->output);
272         if (!running && pri_mon != NULL) {
273             if (mon != pri_mon)
274                 mon = pri_mon;
275             add_desktop(pri_mon, make_desktop(NULL));
276             ewmh_update_current_desktop();
277         }
278     }
279     free(gpo);
280
281     /* handle overlapping monitors */
282     m = mon_head;
283     while (m != NULL) {
284         monitor_t *next = m->next;
285         if (m->wired) {
286             for (monitor_t *mb = mon_head; mb != NULL; mb = mb->next)
287                 if (mb != m && mb->wired && contains(mb->rectangle, m->rectangle)) {
288                     if (mm == m)
289                         mm = mb;
290                     merge_monitors(m, mb);
291                     remove_monitor(m);
292                     break;
293                 }
294         }
295         m = next;
296     }
297
298     /* add one desktop to each new monitor */
299     for (m = mon_head; m != NULL; m = m->next)
300         if (m->desk == NULL && (running || pri_mon == NULL || m != pri_mon))
301             add_desktop(m, make_desktop(NULL));
302
303     /* merge and remove disconnected monitors */
304     m = mon_head;
305     while (m != NULL) {
306         monitor_t *next = m->next;
307         if (!m->wired) {
308             merge_monitors(m, mm);
309             remove_monitor(m);
310         }
311         m = next;
312     }
313
314     free(sres);
315     update_motion_recorder();
316     return (num_monitors > 0);
317 }