]> git.sur5r.net Git - i3/i3/blob - src/layout.c
Correctly handle _NET_WM_WINDOW_TYPE == _NET_WM_WINDOW_TYPE_DOCK (for dzen2 -dock)
[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
18 #include "font.h"
19 #include "i3.h"
20 #include "xcb.h"
21 #include "table.h"
22 #include "util.h"
23 #include "xinerama.h"
24
25 /*
26  * For resizing containers (= cells), we need to know the space which is unoccupied by "default"
27  * windows. The resized containers will be rendered relatively to this space, meaning that newly
28  * created columns/rows after a container was resized will start with their normal size.
29  *
30  */
31 Rect get_unoccupied_space(Workspace *workspace) {
32         printf("getting unoccupied space\n");
33         float default_factor_w = ((float)workspace->rect.width / (float)workspace->cols) / (float)workspace->rect.width;
34         float default_factor_h = (workspace->rect.height / workspace->rows) / workspace->rect.height;
35         Rect result = {0, 0, workspace->rect.width, workspace->rect.height};
36
37         printf("default factor is %f and %f\n", default_factor_w, default_factor_h);
38         printf("start w = %d, h = %d\n", result.width, result.height);
39         /* TODO: colspan/rowspan*/
40
41         for (int cols = 0; cols < workspace->cols; cols++)
42                 for (int rows = 0; rows < workspace->rows; rows++) {
43                         printf("oh hai. wf[%d][%d] = %f\n", cols, rows, workspace->table[cols][rows]->width_factor);
44                         if (workspace->table[cols][rows]->width_factor == 0)
45                                 result.width -= workspace->rect.width * default_factor_w;
46                         if (workspace->table[cols][rows]->height_factor == 0)
47                                 result.height -= workspace->rect.height * default_factor_h;
48                 }
49
50         /* If every container is using the default factor, we have the whole space available */
51         if (result.width == 0)
52                 result.width = workspace->rect.width;
53
54         if (result.height == 0)
55                 result.height = workspace->rect.height;
56
57         printf("unoccupied x = %d, unoccupied y = %d\n", result.width, result.height);
58
59         return result;
60 }
61
62 /*
63  * (Re-)draws window decorations for a given Client
64  *
65  */
66 void decorate_window(xcb_connection_t *conn, Client *client) {
67         uint32_t mask = 0;
68         uint32_t values[3];
69         i3Font *font = load_font(conn, pattern);
70         uint32_t background_color,
71                  text_color,
72                  border_color;
73
74         if (client->container->currently_focused == client) {
75                 background_color = get_colorpixel(conn, client->frame, "#285577");
76                 text_color = get_colorpixel(conn, client->frame, "#ffffff");
77                 border_color = get_colorpixel(conn, client->frame, "#4c7899");
78         } else {
79                 background_color = get_colorpixel(conn, client->frame, "#222222");
80                 text_color = get_colorpixel(conn, client->frame, "#888888");
81                 border_color = get_colorpixel(conn, client->frame, "#333333");
82         }
83
84         /* Our plan is the following:
85            - Draw a rect around the whole client in background_color
86            - Draw two lines in a lighter color
87            - Draw the window’s title
88
89            Note that xcb_image_text apparently adds 1xp border around the font? Can anyone confirm this?
90          */
91
92         /* Draw a green rectangle around the window */
93         mask = XCB_GC_FOREGROUND;
94         values[0] = background_color;
95         xcb_change_gc(conn, client->titlegc, mask, values);
96
97         xcb_rectangle_t rect = {0, 0, client->rect.width, client->rect.height};
98         xcb_poly_fill_rectangle(conn, client->frame, client->titlegc, 1, &rect);
99
100         /* Draw the lines */
101         /* TODO: this needs to be more beautiful somewhen. maybe stdarg + change_gc(gc, ...) ? */
102 #define DRAW_LINE(colorpixel, x, y, to_x, to_y) { \
103                 uint32_t draw_values[1]; \
104                 draw_values[0] = colorpixel; \
105                 xcb_change_gc(conn, client->titlegc, XCB_GC_FOREGROUND, draw_values); \
106                 xcb_point_t points[] = {{x, y}, {to_x, to_y}}; \
107                 xcb_poly_line(conn, XCB_COORD_MODE_ORIGIN, client->frame, client->titlegc, 2, points); \
108         }
109
110         DRAW_LINE(border_color, 2, 0, client->rect.width, 0);
111         DRAW_LINE(border_color, 2, font->height + 3, 2 + client->rect.width, font->height + 3);
112
113         /* Draw the font */
114         mask = XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
115
116         values[0] = text_color;
117         values[1] = background_color;
118         values[2] = font->id;
119
120         xcb_change_gc(conn, client->titlegc, mask, values);
121
122         /* TODO: utf8? */
123         char *label;
124         asprintf(&label, "(%08x) %.*s", client->frame, client->name_len, client->name);
125         xcb_void_cookie_t text_cookie = xcb_image_text_8_checked(conn, strlen(label), client->frame,
126                                         client->titlegc, 3 /* X */, font->height /* Y = baseline of font */, label);
127         check_error(conn, text_cookie, "Could not draw client's title");
128         free(label);
129 }
130
131 /*
132  * Pushes the client’s x and y coordinates to X11
133  *
134  */
135 static void reposition_client(xcb_connection_t *connection, Client *client) {
136         printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
137         /* Note: We can use a pointer to client->x like an array of uint32_ts
138            because it is followed by client->y by definition */
139         xcb_configure_window(connection, client->frame,
140                         XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
141 }
142
143 /*
144  * Pushes the client’s width/height to X11 and resizes the child window
145  *
146  */
147 static void resize_client(xcb_connection_t *connection, Client *client) {
148         i3Font *font = load_font(connection, pattern);
149
150         printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
151         xcb_configure_window(connection, client->frame,
152                         XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
153                         &(client->rect.width));
154
155         /* Adjust the position of the child inside its frame.
156          * The coordinates of the child are relative to its frame, we
157          * add a border of 2 pixel to each value */
158         uint32_t mask = XCB_CONFIG_WINDOW_X |
159                         XCB_CONFIG_WINDOW_Y |
160                         XCB_CONFIG_WINDOW_WIDTH |
161                         XCB_CONFIG_WINDOW_HEIGHT;
162         Rect rect;
163         if (client->titlebar_position == TITLEBAR_OFF) {
164                 rect.x = 0;
165                 rect.y = 0;
166                 rect.width = client->rect.width;
167                 rect.height = client->rect.height;
168         } else {
169                 rect.x = 2;
170                 rect.y = font->height + 2 + 2;
171                 rect.width = client->rect.width - (2 + 2);
172                 rect.height = client->rect.height - ((font->height + 2 + 2) + 2);
173         }
174
175         printf("child will be at %dx%d with size %dx%d\n", rect.x, rect.y, rect.width, rect.height);
176
177         xcb_configure_window(connection, client->child, mask, &(rect.x));
178 }
179
180 static void render_container(xcb_connection_t *connection, Container *container) {
181         Client *client;
182
183         if (container->mode == MODE_DEFAULT) {
184                 int num_clients = 0;
185                 CIRCLEQ_FOREACH(client, &(container->clients), clients)
186                         if (!client->dock)
187                                 num_clients++;
188                 printf("got %d clients in this default container.\n", num_clients);
189
190                 int current_client = 0;
191                 CIRCLEQ_FOREACH(client, &(container->clients), clients) {
192                         /* TODO: currently, clients are assigned to the current container.
193                            Therefore, we need to skip them here. Does anything harmful happen
194                            if clients *do not* have a container. Is this the more desired
195                            situation? Let’s find out… */
196                         if (client->dock)
197                                 continue;
198                         /* TODO: at the moment, every column/row is screen / num_cols. This
199                          * needs to be changed to "percentage of the screen" by
200                          * default and adjustable by the user if necessary.
201                          */
202
203                         /* Check if we changed client->x or client->y by updating it…
204                          * Note the bitwise OR instead of logical OR to force evaluation of both statements */
205                         if (client->force_reconfigure |
206                             (client->rect.x != (client->rect.x = container->x)) |
207                             (client->rect.y != (client->rect.y = container->y +
208                                           (container->height / num_clients) * current_client))) {
209                                 reposition_client(connection, client);
210                         }
211
212                         /* TODO: vertical default layout */
213                         if (client->force_reconfigure |
214                             (client->rect.width != (client->rect.width = container->width)) |
215                             (client->rect.height != (client->rect.height = container->height / num_clients))) {
216                                 resize_client(connection, client);
217                         }
218
219                         if (client->force_reconfigure)
220                                 client->force_reconfigure = false;
221
222                         current_client++;
223                 }
224         } else {
225                 /* TODO: Implement stacking */
226         }
227 }
228
229 static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width, int height) {
230         Client *client;
231         SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
232                 if (client->force_reconfigure |
233                     (client->rect.x != (client->rect.x = 0)) |
234                     (client->rect.y != (client->rect.y = height)))
235                         reposition_client(connection, client);
236
237                 if (client->force_reconfigure |
238                     (client->rect.width != (client->rect.width = width)) |
239                     (client->rect.height != (client->rect.height = client->desired_height)))
240                         resize_client(connection, client);
241
242                 client->force_reconfigure = false;
243                 height += client->desired_height;
244         }
245 }
246
247 void render_layout(xcb_connection_t *connection) {
248         i3Screen *screen;
249
250         TAILQ_FOREACH(screen, &virtual_screens, screens) {
251                 /* r_ws (rendering workspace) is just a shortcut to the Workspace being currently rendered */
252                 Workspace *r_ws = &(workspaces[screen->current_workspace]);
253
254                 printf("Rendering screen %d\n", screen->num);
255                 if (r_ws->fullscreen_client != NULL)
256                         /* This is easy: A client has entered fullscreen mode, so we don’t render at all */
257                         continue;
258
259                 int width = r_ws->rect.width;
260                 int height = r_ws->rect.height;
261                 int x = r_ws->rect.x;
262                 int y = r_ws->rect.y;
263
264                 Client *client;
265                 SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
266                         printf("got dock client: %p\n", client);
267                         printf("it wants to be this height: %d\n", client->desired_height);
268                         height -= client->desired_height;
269                 }
270
271                 printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
272                 printf("each of them therefore is %d px width and %d px height\n",
273                                 width / r_ws->cols, height / r_ws->rows);
274
275                 Rect space = get_unoccupied_space(r_ws);
276                 printf("got %d / %d unoc space\n", space.width, space.height);
277
278                 /* Go through the whole table and render what’s necessary */
279                 for (int cols = 0; cols < r_ws->cols; cols++)
280                         for (int rows = 0; rows < r_ws->rows; rows++) {
281                                 Container *container = r_ws->table[cols][rows];
282                                 printf("container has %d colspan, %d rowspan\n",
283                                                 container->colspan, container->rowspan);
284                                 printf("container at %d, %d\n", x, y);
285                                 /* Update position of the container */
286                                 container->row = rows;
287                                 container->col = cols;
288                                 container->x = x;
289                                 container->y = y;
290                                 if (container->width_factor == 0)
291                                         container->width = (width / r_ws->cols) * container->colspan;
292                                 else container->width = space.width * container->width_factor;
293                                 container->height = (height / r_ws->rows) * container->rowspan;
294
295                                 /* Render it */
296                                 render_container(connection, container);
297
298                                 x += container->width;
299                         }
300
301                 render_bars(connection, r_ws, width, height);
302         }
303
304         xcb_flush(connection);
305 }