]> git.sur5r.net Git - i3/i3/blob - src/xinerama.c
Implement an internal bar which displays the workspaces
[i3/i3] / src / xinerama.c
1 /*
2  * vim:ts=8:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  *
6  * © 2009 Michael Stapelberg and contributors
7  *
8  * See file LICENSE for license information.
9  *
10  */
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <stdbool.h>
15
16 #include <xcb/xcb.h>
17 #include <xcb/xinerama.h>
18
19 #include "queue.h"
20 #include "i3.h"
21 #include "data.h"
22 #include "table.h"
23 #include "util.h"
24 #include "xinerama.h"
25 #include "layout.h"
26 #include "xcb.h"
27 #include "font.h"
28 #include "config.h"
29
30 /* This TAILQ of i3Screens stores the virtual screens, used for handling overlapping screens
31  * (xrandr --same-as) */
32 struct screens_head *virtual_screens;
33
34 static bool xinerama_enabled = true;
35
36 /*
37  * Looks in virtual_screens for the i3Screen whose start coordinates are x, y
38  *
39  */
40 i3Screen *get_screen_at(int x, int y, struct screens_head *screenlist) {
41         i3Screen *screen;
42         TAILQ_FOREACH(screen, screenlist, screens)
43                 if (screen->rect.x == x && screen->rect.y == y)
44                         return screen;
45
46         return NULL;
47 }
48
49 /*
50  * Looks in virtual_screens for the i3Screen which contains coordinates x, y
51  *
52  */
53 i3Screen *get_screen_containing(int x, int y) {
54         i3Screen *screen;
55         TAILQ_FOREACH(screen, virtual_screens, screens) {
56                 printf("comparing x=%d y=%d with x=%d and y=%d width %d height %d\n",
57                                 x, y, screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
58                 if (x >= screen->rect.x && x < (screen->rect.x + screen->rect.width) &&
59                     y >= screen->rect.y && y < (screen->rect.y + screen->rect.height))
60                         return screen;
61         }
62
63         return NULL;
64 }
65
66 /*
67  * Fills virtual_screens with exactly one screen with width/height of the whole X server.
68  *
69  */
70 static void disable_xinerama(xcb_connection_t *connection) {
71         xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
72
73         i3Screen *s = calloc(sizeof(i3Screen), 1);
74
75         s->rect.x = 0;
76         s->rect.y = 0;
77         s->rect.width = root_screen->width_in_pixels;
78         s->rect.height = root_screen->height_in_pixels;
79
80         TAILQ_INSERT_TAIL(virtual_screens, s, screens);
81
82         xinerama_enabled = false;
83 }
84
85 /*
86  * Gets the Xinerama screens and converts them to virtual i3Screens (only one screen for two
87  * Xinerama screen which are configured in clone mode) in the given screenlist
88  *
89  */
90 static void query_screens(xcb_connection_t *connection, struct screens_head *screenlist) {
91         xcb_xinerama_query_screens_reply_t *reply;
92         xcb_xinerama_screen_info_t *screen_info;
93
94         reply = xcb_xinerama_query_screens_reply(connection, xcb_xinerama_query_screens_unchecked(connection), NULL);
95         if (!reply) {
96                 printf("Couldn't get Xinerama screens\n");
97                 return;
98         }
99         screen_info = xcb_xinerama_query_screens_screen_info(reply);
100         int screens = xcb_xinerama_query_screens_screen_info_length(reply);
101         num_screens = 0;
102
103         for (int screen = 0; screen < screens; screen++) {
104                 i3Screen *s = get_screen_at(screen_info[screen].x_org, screen_info[screen].y_org, screenlist);
105                 if (s!= NULL) {
106                         /* This screen already exists. We use the littlest screen so that the user
107                            can always see the complete workspace */
108                         s->rect.width = min(s->rect.width, screen_info[screen].width);
109                         s->rect.height = min(s->rect.height, screen_info[screen].height);
110                 } else {
111                         s = calloc(sizeof(i3Screen), 1);
112                         s->rect.x = screen_info[screen].x_org;
113                         s->rect.y = screen_info[screen].y_org;
114                         s->rect.width = screen_info[screen].width;
115                         s->rect.height = screen_info[screen].height;
116                         /* We always treat the screen at 0x0 as the primary screen */
117                         if (s->rect.x == 0 && s->rect.y == 0)
118                                 TAILQ_INSERT_HEAD(screenlist, s, screens);
119                         else TAILQ_INSERT_TAIL(screenlist, s, screens);
120                         num_screens++;
121                 }
122
123                 printf("found Xinerama screen: %d x %d at %d x %d\n",
124                                 screen_info[screen].width, screen_info[screen].height,
125                                 screen_info[screen].x_org, screen_info[screen].y_org);
126         }
127
128         free(reply);
129 }
130
131 static void initialize_screen(xcb_connection_t *connection, i3Screen *screen, Workspace *workspace) {
132         i3Font *font = load_font(connection, config.font);
133
134         workspace->screen = screen;
135         screen->current_workspace = workspace->num;
136
137         /* Create a bar for each screen */
138         Rect bar_rect = {screen->rect.x,
139                          screen->rect.height - (font->height + 6),
140                          screen->rect.x + screen->rect.width,
141                          font->height + 6};
142         uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
143         uint32_t values[] = {1, XCB_EVENT_MASK_EXPOSURE};
144         screen->bar = create_window(connection, bar_rect, XCB_WINDOW_CLASS_INPUT_OUTPUT, mask, values);
145         screen->bargc = xcb_generate_id(connection);
146         xcb_create_gc(connection, screen->bargc, screen->bar, 0, 0);
147
148         /* Copy dimensions */
149         memcpy(&(workspace->rect), &(screen->rect), sizeof(Rect));
150         printf("that is virtual screen at %d x %d with %d x %d\n",
151                         screen->rect.x, screen->rect.y, screen->rect.width, screen->rect.height);
152 }
153
154 /*
155  * We have just established a connection to the X server and need the initial Xinerama
156  * information to setup workspaces for each screen.
157  *
158  */
159 void initialize_xinerama(xcb_connection_t *connection) {
160         virtual_screens = scalloc(sizeof(struct screens_head));
161         TAILQ_INIT(virtual_screens);
162
163         if (!xcb_get_extension_data(connection, &xcb_xinerama_id)->present) {
164                 printf("Xinerama extension not found, disabling.\n");
165                 disable_xinerama(connection);
166                 return;
167         }
168
169         if (!xcb_xinerama_is_active_reply(connection, xcb_xinerama_is_active(connection), NULL)->state) {
170                 printf("Xinerama is not active (in your X-Server), disabling.\n");
171                 disable_xinerama(connection);
172                 return;
173         }
174
175         query_screens(connection, virtual_screens);
176
177         i3Screen *s;
178         num_screens = 0;
179         /* Just go through each workspace and associate as many screens as we can. */
180         TAILQ_FOREACH(s, virtual_screens, screens) {
181                 s->num = num_screens;
182                 initialize_screen(connection, s, &(workspaces[num_screens]));
183                 num_screens++;
184         }
185 }
186
187 /*
188  * This is called when the rootwindow receives a configure_notify event and therefore the
189  * number/position of the Xinerama screens could have changed.
190  *
191  */
192 void xinerama_requery_screens(xcb_connection_t *connection) {
193         /* POSSIBLE PROBLEM: Is the order of the Xinerama screens always constant? That is, can
194            it change when I move the --right-of video projector to --left-of? */
195
196         if (!xinerama_enabled) {
197                 printf("Xinerama is disabled\n");
198                 return;
199         }
200
201         /* We use a separate copy to diff with the previous set of screens */
202         struct screens_head *new_screens = scalloc(sizeof(struct screens_head));
203         TAILQ_INIT(new_screens);
204
205         query_screens(connection, new_screens);
206
207         i3Screen *first = TAILQ_FIRST(new_screens),
208                  *screen;
209         int screen_count = 0;
210         TAILQ_FOREACH(screen, new_screens, screens) {
211                 screen->num = screen_count;
212                 screen->current_workspace = -1;
213                 for (int c = 0; c < 10; c++)
214                         if ((workspaces[c].screen != NULL) &&
215                             (workspaces[c].screen->num == screen_count)) {
216                                 printf("Found a matching screen\n");
217                                 /* Try to use the same workspace, if it’s available */
218                                 if (workspaces[c].screen->current_workspace)
219                                         screen->current_workspace = workspaces[c].screen->current_workspace;
220
221                                 if (screen->current_workspace == -1)
222                                         screen->current_workspace = c;
223
224                                 /* Re-use the old bar window */
225                                 screen->bar = workspaces[c].screen->bar;
226                                 screen->bargc = workspaces[c].screen->bargc;
227
228                                 /* Update the dimensions */
229                                 memcpy(&(workspaces[c].rect), &(screen->rect), sizeof(Rect));
230                                 workspaces[c].screen = screen;
231                         }
232                 if (screen->current_workspace == -1) {
233                         /* Create a new workspace for this screen, it’s new */
234                         for (int c = 0; c < 10; c++)
235                                 if (workspaces[c].screen == NULL) {
236                                         printf("fix: initializing new workspace, setting num to %d\n", c);
237                                         initialize_screen(connection, screen, &(workspaces[c]));
238                                         break;
239                                 }
240                 }
241                 screen_count++;
242         }
243
244         /* Check for workspaces which are out of bounds */
245         for (int c = 0; c < 10; c++)
246                 if ((workspaces[c].screen != NULL) &&
247                     (workspaces[c].screen->num >= num_screens)) {
248                         printf("Closing bar window\n");
249                         xcb_destroy_window(connection, workspaces[c].screen->bar);
250
251                         printf("Workspace %d's screen out of bounds, assigning to first screen\n", c+1);
252                         workspaces[c].screen = first;
253                         memcpy(&(workspaces[c].rect), &(first->rect), sizeof(Rect));
254                 }
255
256         /* Free the old list */
257         TAILQ_FOREACH(screen, virtual_screens, screens) {
258                 TAILQ_REMOVE(virtual_screens, screen, screens);
259                 free(screen);
260         }
261         free(virtual_screens);
262
263         virtual_screens = new_screens;
264
265         printf("Current workspace is now: %d\n", first->current_workspace);
266
267         render_layout(connection);
268 }