]> git.sur5r.net Git - i3/i3/blob - src/workspace.c
94759cebc3c5ad0fd314f2e5a1b6c0a867baaa32
[i3/i3] / src / workspace.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  * workspace.c: Functions for modifying workspaces
11  *
12  */
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <limits.h>
17 #include <err.h>
18
19 #include "util.h"
20 #include "data.h"
21 #include "i3.h"
22 #include "config.h"
23 #include "xcb.h"
24 #include "table.h"
25 #include "xinerama.h"
26 #include "layout.h"
27 #include "workspace.h"
28 #include "client.h"
29
30 /*
31  * Returns a pointer to the workspace with the given number (starting at 0),
32  * creating the workspace if necessary (by allocating the necessary amount of
33  * memory and initializing the data structures correctly).
34  *
35  */
36 Workspace *workspace_get(int number) {
37         if (number > (num_workspaces-1)) {
38                 int old_num_workspaces = num_workspaces;
39
40                 /* Convert all container->workspace and client->workspace
41                  * pointers to numbers representing their workspace. Necessary
42                  * because the realloc() may make all the pointers invalid, so
43                  * we need to preserve them this way and restore them later.
44                  *
45                  * To distinguish between the first workspace and a NULL
46                  * pointer, we store <workspace number> + 1. */
47                 for (int c = 0; c < num_workspaces; c++)
48                         FOR_TABLE(&(workspaces[c])) {
49                                 Container *con = workspaces[c].table[cols][rows];
50                                 if (con->workspace != NULL) {
51                                         LOG("Handling con %p with pointer %p (num %d)\n", con, con->workspace, con->workspace->num);
52                                         con->workspace = (Workspace*)(con->workspace->num + 1);
53                                 }
54                                 Client *current;
55                                 SLIST_FOREACH(current, &(workspaces[c].focus_stack), focus_clients) {
56                                         if (current->workspace == NULL)
57                                                 continue;
58                                         LOG("Handling client %p with pointer %p (num %d)\n", current, current->workspace, current->workspace->num);
59                                         current->workspace = (Workspace*)(current->workspace->num + 1);
60                                 }
61                         }
62
63                 /* preserve c_ws */
64                 c_ws = (Workspace*)(c_ws->num);
65
66                 LOG("We need to initialize that one\n");
67                 num_workspaces = number+1;
68                 workspaces = realloc(workspaces, num_workspaces * sizeof(Workspace));
69                 /* Zero out the new workspaces so that we have sane default values */
70                 for (int c = old_num_workspaces; c < num_workspaces; c++)
71                         memset(&workspaces[c], 0, sizeof(Workspace));
72
73                 /* Immediately after the realloc(), we restore the pointers.
74                  * They may be used when initializing the new workspaces, for
75                  * example when the user configures containers to be stacking
76                  * by default, thus requiring re-rendering the layout. */
77                 c_ws = workspace_get((int)c_ws);
78
79                 for (int c = 0; c < old_num_workspaces; c++)
80                         FOR_TABLE(&(workspaces[c])) {
81                                 Container *con = workspaces[c].table[cols][rows];
82                                 if (con->workspace != NULL) {
83                                         LOG("Handling con %p with (num %d)\n", con, con->workspace);
84                                         con->workspace = workspace_get((int)con->workspace - 1);
85                                 }
86                                 Client *current;
87                                 SLIST_FOREACH(current, &(workspaces[c].focus_stack), focus_clients) {
88                                         if (current->workspace == NULL)
89                                                 continue;
90                                         LOG("Handling client %p with (num %d)\n", current, current->workspace);
91                                         current->workspace = workspace_get((int)current->workspace - 1);
92                                 }
93                         }
94
95                 /* Initialize the new workspaces */
96                 for (int c = old_num_workspaces; c < num_workspaces; c++) {
97                         memset(&workspaces[c], 0, sizeof(Workspace));
98                         workspaces[c].num = c;
99                         TAILQ_INIT(&(workspaces[c].floating_clients));
100                         expand_table_cols(&(workspaces[c]));
101                         expand_table_rows(&(workspaces[c]));
102                         workspace_set_name(&(workspaces[c]), NULL);
103                 }
104
105                 LOG("done\n");
106         }
107
108         return &(workspaces[number]);
109 }
110
111 /*
112  * Sets the name (or just its number) for the given workspace. This has to
113  * be called for every workspace as the rendering function
114  * (render_internal_bar) relies on workspace->name and workspace->name_len
115  * being ready-to-use.
116  *
117  */
118 void workspace_set_name(Workspace *ws, const char *name) {
119         char *label;
120         int ret;
121
122         if (name != NULL)
123                 ret = asprintf(&label, "%d: %s", ws->num + 1, name);
124         else ret = asprintf(&label, "%d", ws->num + 1);
125
126         if (ret == -1)
127                 errx(1, "asprintf() failed");
128
129         FREE(ws->name);
130
131         ws->name = convert_utf8_to_ucs2(label, &(ws->name_len));
132         if (config.font != NULL)
133                 ws->text_width = predict_text_width(global_conn, config.font, ws->name, ws->name_len);
134         else ws->text_width = 0;
135
136         free(label);
137 }
138
139 /*
140  * Returns true if the workspace is currently visible. Especially important for
141  * multi-monitor environments, as they can have multiple currenlty active
142  * workspaces.
143  *
144  */
145 bool workspace_is_visible(Workspace *ws) {
146         return (ws->screen->current_workspace == ws->num);
147 }
148
149 /*
150  * Switches to the given workspace
151  *
152  */
153 void workspace_show(xcb_connection_t *conn, int workspace) {
154         bool need_warp = false;
155         xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
156         /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */
157         Workspace *t_ws = workspace_get(workspace-1);
158
159         LOG("show_workspace(%d)\n", workspace);
160
161         /* Store current_row/current_col */
162         c_ws->current_row = current_row;
163         c_ws->current_col = current_col;
164
165         /* Check if the workspace has not been used yet */
166         workspace_initialize(t_ws, c_ws->screen);
167
168         if (c_ws->screen != t_ws->screen) {
169                 /* We need to switch to the other screen first */
170                 LOG("moving over to other screen.\n");
171
172                 /* Store the old client */
173                 Client *old_client = CUR_CELL->currently_focused;
174
175                 c_ws = workspace_get(t_ws->screen->current_workspace);
176                 current_col = c_ws->current_col;
177                 current_row = c_ws->current_row;
178                 if (CUR_CELL->currently_focused != NULL)
179                         need_warp = true;
180                 else {
181                         Rect *dims = &(c_ws->screen->rect);
182                         xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
183                                          dims->x + (dims->width / 2), dims->y + (dims->height / 2));
184                 }
185
186                 /* Re-decorate the old client, it’s not focused anymore */
187                 if ((old_client != NULL) && !old_client->dock)
188                         redecorate_window(conn, old_client);
189                 else xcb_flush(conn);
190         }
191
192         /* Check if we need to change something or if we’re already there */
193         if (c_ws->screen->current_workspace == (workspace-1)) {
194                 Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
195                 if (last_focused != SLIST_END(&(c_ws->focus_stack)))
196                         set_focus(conn, last_focused, true);
197                 if (need_warp) {
198                         client_warp_pointer_into(conn, last_focused);
199                         xcb_flush(conn);
200                 }
201
202                 return;
203         }
204
205         t_ws->screen->current_workspace = workspace-1;
206         Workspace *old_workspace = c_ws;
207         c_ws = workspace_get(workspace-1);
208
209         /* Unmap all clients of the old workspace */
210         workspace_unmap_clients(conn, old_workspace);
211
212         current_row = c_ws->current_row;
213         current_col = c_ws->current_col;
214         LOG("new current row = %d, current col = %d\n", current_row, current_col);
215
216         workspace_map_clients(conn, c_ws);
217
218         /* POTENTIAL TO IMPROVE HERE: due to the call to _map_clients first and
219          * render_layout afterwards, there is a short flickering on the source
220          * workspace (assign ws 3 to screen 0, ws 4 to screen 1, create single
221          * client on ws 4, move it to ws 3, switch to ws 3, you’ll see the
222          * flickering). */
223
224         /* Restore focus on the new workspace */
225         Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
226         if (last_focused != SLIST_END(&(c_ws->focus_stack)))
227                 set_focus(conn, last_focused, true);
228         else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
229
230         render_layout(conn);
231
232         /* We can warp the pointer only after the window has been
233          * reconfigured in render_layout, otherwise the pointer will
234          * be warped to the old position, which will not work when we
235          * moved it to another screen. */
236         if (last_focused != SLIST_END(&(c_ws->focus_stack)) && need_warp) {
237                 client_warp_pointer_into(conn, last_focused);
238                 xcb_flush(conn);
239         }
240 }
241
242
243 /*
244  * Parses the preferred_screen property of a workspace. You can either specify
245  * the screen number (it is not given that the screen numbering always stays
246  * the same) or the screen coordinates (exact coordinates, e.g. 1280 will match
247  * the screen starting at x=1280, but 1281 will not). For coordinates, you can
248  * either specify an x coordinate ("1280") or an y coordinate ("x800") or both
249  * ("1280x800").
250  *
251  */
252 static i3Screen *get_screen_from_preference(struct screens_head *slist, char *preference) {
253         i3Screen *screen;
254         char *rest;
255         int preferred_screen = strtol(preference, &rest, 10);
256
257         LOG("Getting screen for preference \"%s\" (%d)\n", preference, preferred_screen);
258
259         if ((rest == preference) || (preferred_screen >= num_screens)) {
260                 int x = INT_MAX, y = INT_MAX;
261                 if (strchr(preference, 'x') != NULL) {
262                         /* Check if only the y coordinate was specified */
263                         if (*preference == 'x')
264                                 y = atoi(preference+1);
265                         else {
266                                 x = atoi(preference);
267                                 y = atoi(strchr(preference, 'x') + 1);
268                         }
269                 } else {
270                         x = atoi(preference);
271                 }
272
273                 LOG("Looking for screen at %d x %d\n", x, y);
274
275                 TAILQ_FOREACH(screen, slist, screens)
276                         if ((x == INT_MAX || screen->rect.x == x) &&
277                             (y == INT_MAX || screen->rect.y == y)) {
278                                 LOG("found %p\n", screen);
279                                 return screen;
280                         }
281
282                 LOG("none found\n");
283                 return NULL;
284         } else {
285                 int c = 0;
286                 TAILQ_FOREACH(screen, slist, screens)
287                         if (c++ == preferred_screen)
288                                 return screen;
289         }
290
291         return NULL;
292 }
293
294 /*
295  * Initializes the given workspace if it is not already initialized. The given
296  * screen is to be understood as a fallback, if the workspace itself either
297  * was not assigned to a particular screen or cannot be placed there because
298  * the screen is not attached at the moment.
299  *
300  */
301 void workspace_initialize(Workspace *ws, i3Screen *screen) {
302         if (ws->screen != NULL) {
303                 LOG("Workspace already initialized\n");
304                 return;
305         }
306
307         /* If this workspace has no preferred screen or if the screen it wants
308          * to be on is not available at the moment, we initialize it with
309          * the screen which was given */
310         if (ws->preferred_screen == NULL ||
311             (ws->screen = get_screen_from_preference(virtual_screens, ws->preferred_screen)) == NULL)
312                 ws->screen = screen;
313
314         /* Copy the dimensions from the virtual screen */
315         memcpy(&(ws->rect), &(ws->screen->rect), sizeof(Rect));
316 }
317
318 /*
319  * Gets the first unused workspace for the given screen, taking into account
320  * the preferred_screen setting of every workspace (workspace assignments).
321  *
322  */
323 Workspace *get_first_workspace_for_screen(struct screens_head *slist, i3Screen *screen) {
324         Workspace *result = NULL;
325
326         for (int c = 0; c < num_workspaces; c++) {
327                 Workspace *ws = workspace_get(c);
328                 if (ws->preferred_screen == NULL ||
329                     !screens_are_equal(get_screen_from_preference(slist, ws->preferred_screen), screen))
330                         continue;
331
332                 result = ws;
333                 break;
334         }
335
336         if (result == NULL) {
337                 /* No assignment found, returning first unused workspace */
338                 for (int c = 0; c < num_workspaces; c++) {
339                         if (workspaces[c].screen != NULL)
340                                 continue;
341
342                         result = workspace_get(c);
343                         break;
344                 }
345         }
346
347         if (result != NULL) {
348                 workspace_initialize(result, screen);
349                 return result;
350         }
351
352         LOG("WARNING: No free workspace found to assign!\n");
353         return NULL;
354 }
355
356 /*
357  * Maps all clients (and stack windows) of the given workspace.
358  *
359  */
360 void workspace_map_clients(xcb_connection_t *conn, Workspace *ws) {
361         Client *client;
362
363         ignore_enter_notify_forall(conn, ws, true);
364
365         /* Map all clients on the new workspace */
366         FOR_TABLE(ws)
367                 CIRCLEQ_FOREACH(client, &(ws->table[cols][rows]->clients), clients)
368                         client_map(conn, client);
369
370         /* Map all floating clients */
371         if (!ws->floating_hidden)
372                 TAILQ_FOREACH(client, &(ws->floating_clients), floating_clients)
373                         client_map(conn, client);
374
375         /* Map all stack windows, if any */
376         struct Stack_Window *stack_win;
377         SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
378                 if (stack_win->container->workspace == ws)
379                         xcb_map_window(conn, stack_win->window);
380
381         ignore_enter_notify_forall(conn, ws, false);
382 }
383
384 /*
385  * Unmaps all clients (and stack windows) of the given workspace.
386  *
387  * This needs to be called separately when temporarily rendering
388  * a workspace which is not the active workspace to force
389  * reconfiguration of all clients, like in src/xinerama.c when
390  * re-assigning a workspace to another screen.
391  *
392  */
393 void workspace_unmap_clients(xcb_connection_t *conn, Workspace *u_ws) {
394         Client *client;
395         struct Stack_Window *stack_win;
396
397         /* Ignore notify events because they would cause focus to be changed */
398         ignore_enter_notify_forall(conn, u_ws, true);
399
400         /* Unmap all clients of the given workspace */
401         int unmapped_clients = 0;
402         FOR_TABLE(u_ws)
403                 CIRCLEQ_FOREACH(client, &(u_ws->table[cols][rows]->clients), clients) {
404                         LOG("unmapping normal client %p / %p / %p\n", client, client->frame, client->child);
405                         client_unmap(conn, client);
406                         unmapped_clients++;
407                 }
408
409         /* To find floating clients, we traverse the focus stack */
410         SLIST_FOREACH(client, &(u_ws->focus_stack), focus_clients) {
411                 if (!client_is_floating(client))
412                         continue;
413
414                 LOG("unmapping floating client %p / %p / %p\n", client, client->frame, client->child);
415
416                 client_unmap(conn, client);
417                 unmapped_clients++;
418         }
419
420         /* If we did not unmap any clients, the workspace is empty and we can destroy it, at least
421          * if it is not the current workspace. */
422         if (unmapped_clients == 0 && u_ws != c_ws) {
423                 /* Re-assign the workspace of all dock clients which use this workspace */
424                 Client *dock;
425                 LOG("workspace %p is empty\n", u_ws);
426                 SLIST_FOREACH(dock, &(u_ws->screen->dock_clients), dock_clients) {
427                         if (dock->workspace != u_ws)
428                                 continue;
429
430                         LOG("Re-assigning dock client to c_ws (%p)\n", c_ws);
431                         dock->workspace = c_ws;
432                 }
433                 u_ws->screen = NULL;
434         }
435
436         /* Unmap the stack windows on the given workspace, if any */
437         SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
438                 if (stack_win->container->workspace == u_ws)
439                         xcb_unmap_window(conn, stack_win->window);
440
441         ignore_enter_notify_forall(conn, u_ws, false);
442 }
443
444 /*
445  * Goes through all clients on the given workspace and updates the workspace’s
446  * urgent flag accordingly.
447  *
448  */
449 void workspace_update_urgent_flag(Workspace *ws) {
450         Client *current;
451
452         SLIST_FOREACH(current, &(ws->focus_stack), focus_clients) {
453                 if (!current->urgent)
454                         continue;
455
456                 ws->urgent = true;
457                 return;
458         }
459
460         ws->urgent = false;
461 }