]> git.sur5r.net Git - i3/i3/blob - src/layout.c
Use default cursor (XC_left_ptr) for all windows
[i3/i3] / src / layout.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  * layout.c: Functions handling layout/drawing of window decorations
11  *
12  */
13 #include <stdio.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <xcb/xcb.h>
17 #include <assert.h>
18
19 #include "config.h"
20 #include "i3.h"
21 #include "xcb.h"
22 #include "table.h"
23 #include "util.h"
24 #include "xinerama.h"
25 #include "layout.h"
26
27 /* This macro copies the old value of the given variable, changes the variable to contain
28    the new one and returns true if it changed.
29    Note that when combining multiple HAS_CHANGED statements, you need to use different variables.
30    If someone by chance knows why this is necessary (order of expressions in gcc?) and/or can
31    come up with a fix, please mail me. */
32 #define HAS_CHANGED(temp, value, new) (temp = value, temp != (value = new))
33
34 static int old_value_1;
35 static int old_value_2;
36
37 /*
38  * Gets the unoccupied space (= space which is available for windows which were resized by the user)
39  * for the given row. This is necessary to render both, customly resized windows and never touched
40  * windows correctly, meaning that the aspect ratio will be maintained when opening new windows.
41  *
42  */
43 int get_unoccupied_x(Workspace *workspace, int row) {
44         int unoccupied = workspace->rect.width;
45         float default_factor = ((float)workspace->rect.width / workspace->cols) / workspace->rect.width;
46
47         printf("get_unoccupied_x(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
48
49         for (int cols = 0; cols < workspace->cols;) {
50                 Container *con = workspace->table[cols][row];
51                 printf("width_factor[%d][%d] = %f, colspan = %d\n", cols, row, con->width_factor, con->colspan);
52                 if (con->width_factor == 0)
53                         unoccupied -= workspace->rect.width * default_factor * con->colspan;
54                 cols += con->colspan;
55         }
56
57         assert(unoccupied != 0);
58
59         printf("unoccupied space: %d\n", unoccupied);
60         return unoccupied;
61 }
62
63 /* See get_unoccupied_x() */
64 int get_unoccupied_y(Workspace *workspace, int col) {
65         int unoccupied = workspace->rect.height;
66         float default_factor = ((float)workspace->rect.height / workspace->rows) / workspace->rect.height;
67
68         printf("get_unoccupied_y(), starting with %d, default_factor = %f\n", unoccupied, default_factor);
69
70         for (int rows = 0; rows < workspace->rows;) {
71                 Container *con = workspace->table[col][rows];
72                 printf("height_factor[%d][%d] = %f, rowspan %d\n", col, rows, con->height_factor, con->rowspan);
73                 if (con->height_factor == 0)
74                         unoccupied -= workspace->rect.height * default_factor * con->rowspan;
75                 rows += con->rowspan;
76         }
77
78         assert(unoccupied != 0);
79
80         printf("unoccupied space: %d\n", unoccupied);
81         return unoccupied;
82 }
83
84 /*
85  * Redecorates the given client correctly by checking if it’s in a stacking container and
86  * re-rendering the stack window or just calling decorate_window if it’s not in a stacking
87  * container.
88  *
89  */
90 void redecorate_window(xcb_connection_t *conn, Client *client) {
91         if (client->container->mode == MODE_STACK) {
92                 render_container(conn, client->container);
93                 xcb_flush(conn);
94         } else decorate_window(conn, client, client->frame, client->titlegc, 0);
95 }
96
97 /*
98  * (Re-)draws window decorations for a given Client onto the given drawable/graphic context.
99  * When in stacking mode, the window decorations are drawn onto an own window.
100  *
101  */
102 void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t drawable, xcb_gcontext_t gc, int offset) {
103         i3Font *font = load_font(conn, config.font);
104         uint32_t background_color,
105                  text_color,
106                  border_color;
107
108         /* Clients without a container (docks) won’t get decorated */
109         if (client->container == NULL)
110                 return;
111
112         if (client->container->currently_focused == client) {
113                 /* Distinguish if the window is currently focused… */
114                 if (CUR_CELL->currently_focused == client)
115                         background_color = get_colorpixel(conn, client, client->frame, "#285577");
116                 /* …or if it is the focused window in a not focused container */
117                 else background_color = get_colorpixel(conn, client, client->frame, "#555555");
118
119                 text_color = get_colorpixel(conn, client, client->frame, "#ffffff");
120                 border_color = get_colorpixel(conn, client, client->frame, "#4c7899");
121         } else {
122                 background_color = get_colorpixel(conn, client, client->frame, "#222222");
123                 text_color = get_colorpixel(conn, client, client->frame, "#888888");
124                 border_color = get_colorpixel(conn, client, client->frame, "#333333");
125         }
126
127         /* Our plan is the following:
128            - Draw a rect around the whole client in background_color
129            - Draw two lines in a lighter color
130            - Draw the window’s title
131          */
132
133         /* Draw a rectangle in background color around the window */
134         xcb_change_gc_single(conn, gc, XCB_GC_FOREGROUND, background_color);
135
136         xcb_rectangle_t rect = {0, offset, client->rect.width, offset + client->rect.height};
137         xcb_poly_fill_rectangle(conn, drawable, gc, 1, &rect);
138
139         /* Draw the lines */
140         xcb_draw_line(conn, drawable, gc, border_color, 2, offset, client->rect.width, offset);
141         xcb_draw_line(conn, drawable, gc, border_color, 2, offset + font->height + 3,
142                       2 + client->rect.width, offset + font->height + 3);
143
144         /* Draw the font */
145         uint32_t mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
146         uint32_t values[] = { text_color, background_color, font->id };
147         xcb_change_gc(conn, gc, mask, values);
148
149         /* TODO: utf8? */
150         char *label;
151         asprintf(&label, "%.*s", client->name_len, client->name);
152         xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), drawable,
153                                         gc, 3 /* X */, offset + font->height /* Y = baseline of font */, label);
154         check_error(conn, text_cookie, "Could not draw client's title");
155         free(label);
156 }
157
158 /*
159  * Pushes the client’s x and y coordinates to X11
160  *
161  */
162 static void reposition_client(xcb_connection_t *connection, Client *client) {
163         printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
164         /* Note: We can use a pointer to client->x like an array of uint32_ts
165            because it is followed by client->y by definition */
166         xcb_configure_window(connection, client->frame,
167                         XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
168 }
169
170 /*
171  * Pushes the client’s width/height to X11 and resizes the child window
172  *
173  */
174 static void resize_client(xcb_connection_t *connection, Client *client) {
175         i3Font *font = load_font(connection, config.font);
176
177         printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
178         xcb_configure_window(connection, client->frame,
179                         XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
180                         &(client->rect.width));
181
182         /* Adjust the position of the child inside its frame.
183          * The coordinates of the child are relative to its frame, we
184          * add a border of 2 pixel to each value */
185         uint32_t mask = XCB_CONFIG_WINDOW_X |
186                         XCB_CONFIG_WINDOW_Y |
187                         XCB_CONFIG_WINDOW_WIDTH |
188                         XCB_CONFIG_WINDOW_HEIGHT;
189         Rect *rect = &(client->child_rect);
190         switch (client->container->mode) {
191                 case MODE_STACK:
192                         rect->x = 2;
193                         rect->y = 0;
194                         rect->width = client->rect.width - (2 + 2);
195                         rect->height = client->rect.height - 2;
196                         break;
197                 default:
198                         if (client->titlebar_position == TITLEBAR_OFF) {
199                                 rect->x = 0;
200                                 rect->y = 0;
201                                 rect->width = client->rect.width;
202                                 rect->height = client->rect.height;
203                         } else {
204                                 rect->x = 2;
205                                 rect->y = font->height + 2 + 2;
206                                 rect->width = client->rect.width - (2 + 2);
207                                 rect->height = client->rect.height - ((font->height + 2 + 2) + 2);
208                         }
209                         break;
210         }
211
212         printf("child will be at %dx%d with size %dx%d\n", rect->x, rect->y, rect->width, rect->height);
213
214         xcb_configure_window(connection, client->child, mask, &(rect->x));
215
216         /* After configuring a child window we need to fake a configure_notify_event according
217            to ICCCM 4.2.3. This seems rather broken, especially since X sends exactly the same
218            configure_notify_event automatically according to xtrace. Anyone knows details? */
219         xcb_configure_notify_event_t event;
220
221         event.event = client->child;
222         event.window = client->child;
223         event.response_type = XCB_CONFIGURE_NOTIFY;
224
225         event.x = rect->x;
226         event.y = rect->y;
227         event.width = rect->width;
228         event.height = rect->height;
229
230         event.border_width = 0;
231         event.above_sibling = XCB_NONE;
232         event.override_redirect = false;
233
234         xcb_send_event(connection, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&event);
235 }
236
237 /*
238  * Renders the given container. Is called by render_layout() or individually (for example
239  * when focus changes in a stacking container)
240  *
241  */
242 void render_container(xcb_connection_t *connection, Container *container) {
243         Client *client;
244         int num_clients = 0, current_client = 0;
245
246         if (container->currently_focused == NULL)
247                 return;
248
249         CIRCLEQ_FOREACH(client, &(container->clients), clients)
250                 num_clients++;
251
252         if (container->mode == MODE_DEFAULT) {
253                 printf("got %d clients in this default container.\n", num_clients);
254                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
255                         /* Check if we changed client->x or client->y by updating it.
256                          * Note the bitwise OR instead of logical OR to force evaluation of both statements */
257                         if (client->force_reconfigure |
258                             HAS_CHANGED(old_value_1, client->rect.x, container->x) |
259                             HAS_CHANGED(old_value_2, client->rect.y, container->y +
260                                         (container->height / num_clients) * current_client))
261                                 reposition_client(connection, client);
262
263                         /* TODO: vertical default layout */
264                         if (client->force_reconfigure |
265                             HAS_CHANGED(old_value_1, client->rect.width, container->width) |
266                             HAS_CHANGED(old_value_2, client->rect.height, container->height / num_clients))
267                                 resize_client(connection, client);
268
269                         client->force_reconfigure = false;
270
271                         current_client++;
272                 }
273         } else {
274                 i3Font *font = load_font(connection, config.font);
275                 int decoration_height = (font->height + 2 + 2);
276                 struct Stack_Window *stack_win = &(container->stack_win);
277
278                 /* Check if we need to remap our stack title window, it gets unmapped when the container
279                    is empty in src/handlers.c:unmap_notify() */
280                 if (stack_win->height == 0)
281                         xcb_map_window(connection, stack_win->window);
282
283                 /* Check if we need to reconfigure our stack title window */
284                 if (HAS_CHANGED(old_value_1, stack_win->width, container->width) |
285                     HAS_CHANGED(old_value_2, stack_win->height, decoration_height * num_clients)) {
286                         xcb_configure_window(connection, stack_win->window,
287                                 XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, &(stack_win->width));
288
289                         uint32_t values[] = { XCB_STACK_MODE_ABOVE };
290                         xcb_configure_window(connection, stack_win->window, XCB_CONFIG_WINDOW_STACK_MODE, values);
291                 }
292
293                 /* Reconfigure the currently focused client, if necessary. It is the only visible one */
294                 client = container->currently_focused;
295
296                 /* Check if we changed client->x or client->y by updating it.
297                  * Note the bitwise OR instead of logical OR to force evaluation of both statements */
298                 if (client->force_reconfigure |
299                     HAS_CHANGED(old_value_1, client->rect.x, container->x) |
300                     HAS_CHANGED(old_value_2, client->rect.y, container->y + (decoration_height * num_clients)))
301                         reposition_client(connection, client);
302
303                 if (client->force_reconfigure |
304                     HAS_CHANGED(old_value_1, client->rect.width, container->width) |
305                     HAS_CHANGED(old_value_2, client->rect.height, container->height - (decoration_height * num_clients)))
306                         resize_client(connection, client);
307
308                 client->force_reconfigure = false;
309
310                 if (container->workspace->fullscreen_client == NULL) {
311                         uint32_t values[] = { XCB_STACK_MODE_ABOVE };
312                         xcb_configure_window(connection, client->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
313                 }
314
315                 /* Render the decorations of all clients */
316                 CIRCLEQ_FOREACH(client, &(container->clients), clients)
317                         decorate_window(connection, client, stack_win->window, stack_win->gc,
318                                         current_client++ * decoration_height);
319         }
320 }
321
322 static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width, int *height) {
323         Client *client;
324         SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
325                 if (client->force_reconfigure |
326                     HAS_CHANGED(old_value_1, client->rect.x, 0) |
327                     HAS_CHANGED(old_value_2, client->rect.y, *height))
328                         reposition_client(connection, client);
329
330                 if (client->force_reconfigure |
331                     HAS_CHANGED(old_value_1, client->rect.width, width) |
332                     HAS_CHANGED(old_value_2, client->rect.height, client->desired_height))
333                         resize_client(connection, client);
334
335                 client->force_reconfigure = false;
336                 *height += client->desired_height;
337         }
338 }
339
340 static void render_internal_bar(xcb_connection_t *connection, Workspace *r_ws, int width, int height) {
341         printf("Rendering internal bar\n");
342         i3Font *font = load_font(connection, config.font);
343         i3Screen *screen = r_ws->screen;
344         enum { SET_NORMAL = 0, SET_FOCUSED = 1 };
345         uint32_t background_color[2],
346                  text_color[2],
347                  border_color[2],
348                  black;
349         char label[3];
350
351         black = get_colorpixel(connection, NULL, screen->bar, "#000000");
352
353         background_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#222222");
354         text_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#888888");
355         border_color[SET_NORMAL] = get_colorpixel(connection, NULL, screen->bar, "#333333");
356
357         background_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#285577");
358         text_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#ffffff");
359         border_color[SET_FOCUSED] = get_colorpixel(connection, NULL, screen->bar, "#4c7899");
360
361         /* Fill the whole bar in black */
362         xcb_change_gc_single(connection, screen->bargc, XCB_GC_FOREGROUND, black);
363         xcb_rectangle_t rect = {0, 0, width, height};
364         xcb_poly_fill_rectangle(connection, screen->bar, screen->bargc, 1, &rect);
365
366         /* Set font */
367         xcb_change_gc_single(connection, screen->bargc, XCB_GC_FONT, font->id);
368
369         int drawn = 0;
370         for (int c = 0; c < 10; c++) {
371                 if (workspaces[c].screen == screen) {
372                         int set = (screen->current_workspace == c ? SET_FOCUSED : SET_NORMAL);
373
374                         xcb_draw_rect(connection, screen->bar, screen->bargc, border_color[set],
375                                       drawn * height, 1, height - 2, height - 2);
376                         xcb_draw_rect(connection, screen->bar, screen->bargc, background_color[set],
377                                       drawn * height + 1, 2, height - 4, height - 4);
378
379                         snprintf(label, sizeof(label), "%d", c+1);
380                         xcb_change_gc_single(connection, screen->bargc, XCB_GC_FOREGROUND, text_color[set]);
381                         xcb_change_gc_single(connection, screen->bargc, XCB_GC_BACKGROUND, background_color[set]);
382                         xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(connection, strlen(label), screen->bar,
383                                                         screen->bargc, drawn * height + 5 /* X */,
384                                                         font->height + 1 /* Y = baseline of font */, label);
385                         check_error(connection, text_cookie, "Could not draw workspace title");
386                         drawn++;
387                 }
388         }
389
390         printf("done rendering internal\n");
391 }
392
393 void render_layout(xcb_connection_t *connection) {
394         i3Screen *screen;
395         i3Font *font = load_font(connection, config.font);
396
397         TAILQ_FOREACH(screen, virtual_screens, screens) {
398                 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
399                 Workspace *r_ws = &(workspaces[screen->current_workspace]);
400
401                 printf("Rendering screen %d\n", screen->num);
402                 if (r_ws->fullscreen_client != NULL)
403                         /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
404                         continue;
405
406                 int width = r_ws->rect.width;
407                 int height = r_ws->rect.height;
408
409                 /* Reserve space for dock clients */
410                 Client *client;
411                 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients)
412                         height -= client->desired_height;
413
414                 /* Space for the internal bar */
415                 height -= (font->height + 6);
416
417                 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
418
419                 int xoffset[r_ws->rows];
420                 int yoffset[r_ws->cols];
421                 /* Initialize offsets */
422                 for (int cols = 0; cols < r_ws->cols; cols++)
423                         yoffset[cols] = r_ws->rect.y;
424                 for (int rows = 0; rows < r_ws->rows; rows++)
425                         xoffset[rows] = r_ws->rect.x;
426
427                 /* Go through the whole table and render what’s necessary */
428                 for (int cols = 0; cols < r_ws->cols; cols++)
429                         for (int rows = 0; rows < r_ws->rows; rows++) {
430                                 Container *container = r_ws->table[cols][rows];
431                                 printf("\n========\ncontainer has %d colspan, %d rowspan\n",
432                                                 container->colspan, container->rowspan);
433                                 printf("container at %d, %d\n", xoffset[rows], yoffset[cols]);
434                                 /* Update position of the container */
435                                 container->row = rows;
436                                 container->col = cols;
437                                 container->x = xoffset[rows];
438                                 container->y = yoffset[cols];
439
440                                 if (container->width_factor == 0)
441                                         container->width = (width / r_ws->cols);
442                                 else container->width = get_unoccupied_x(r_ws, rows) * container->width_factor;
443                                 container->width *= container->colspan;
444
445                                 if (container->height_factor == 0)
446                                         container->height = (height / r_ws->rows);
447                                 else container->height = get_unoccupied_y(r_ws, cols) * container->height_factor;
448                                 container->height *= container->rowspan;
449
450                                 /* Render the container if it is not empty */
451                                 render_container(connection, container);
452
453                                 xoffset[rows] += container->width;
454                                 yoffset[cols] += container->height;
455                                 printf("==========\n");
456                         }
457
458                 render_bars(connection, r_ws, width, &height);
459                 render_internal_bar(connection, r_ws, width, 18);
460         }
461
462         xcb_flush(connection);
463 }