]> git.sur5r.net Git - i3/i3/blob - src/commands.c
Implement borderless / 1-px-bordered windows
[i3/i3] / src / commands.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 <stdbool.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <assert.h>
15 #include <unistd.h>
16 #include <string.h>
17
18 #include <xcb/xcb.h>
19
20 #include "util.h"
21 #include "data.h"
22 #include "table.h"
23 #include "layout.h"
24 #include "i3.h"
25 #include "xinerama.h"
26 #include "client.h"
27 #include "floating.h"
28 #include "xcb.h"
29 #include "config.h"
30 #include "workspace.h"
31
32 bool focus_window_in_container(xcb_connection_t *conn, Container *container, direction_t direction) {
33         /* If this container is empty, we’re done */
34         if (container->currently_focused == NULL)
35                 return false;
36
37         /* Get the previous/next client or wrap around */
38         Client *candidate = NULL;
39         if (direction == D_UP) {
40                 if ((candidate = CIRCLEQ_PREV_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL)
41                         candidate = CIRCLEQ_LAST(&(container->clients));
42         }
43         else if (direction == D_DOWN) {
44                 if ((candidate = CIRCLEQ_NEXT_OR_NULL(&(container->clients), container->currently_focused, clients)) == NULL)
45                         candidate = CIRCLEQ_FIRST(&(container->clients));
46         } else LOG("Direction not implemented!\n");
47
48         /* If we could not switch, the container contains exactly one client. We return false */
49         if (candidate == container->currently_focused)
50                 return false;
51
52         /* Set focus */
53         set_focus(conn, candidate, true);
54
55         return true;
56 }
57
58 typedef enum { THING_WINDOW, THING_CONTAINER } thing_t;
59
60 static void focus_thing(xcb_connection_t *conn, direction_t direction, thing_t thing) {
61         LOG("focusing direction %d\n", direction);
62
63         int new_row = current_row,
64             new_col = current_col;
65         Container *container = CUR_CELL;
66         Workspace *t_ws = c_ws;
67
68         /* Makes sure new_col and new_row are within bounds of the new workspace */
69         void check_colrow_boundaries() {
70                 if (new_col >= t_ws->cols)
71                         new_col = (t_ws->cols - 1);
72                 if (new_row >= t_ws->rows)
73                         new_row = (t_ws->rows - 1);
74         }
75
76         /* There always is a container. If not, current_col or current_row is wrong */
77         assert(container != NULL);
78
79         if (container->workspace->fullscreen_client != NULL) {
80                 LOG("You're in fullscreen mode. Won't switch focus\n");
81                 return;
82         }
83
84         /* TODO: for horizontal default layout, this has to be expanded to LEFT/RIGHT */
85         if (direction == D_UP || direction == D_DOWN) {
86                 if (thing == THING_WINDOW)
87                         /* Let’s see if we can perform up/down focus in the current container */
88                         if (focus_window_in_container(conn, container, direction))
89                                 return;
90
91                 if (direction == D_DOWN && cell_exists(current_col, current_row+1))
92                         new_row = current_row + t_ws->table[current_col][current_row]->rowspan;
93                 else if (direction == D_UP && cell_exists(current_col, current_row-1)) {
94                         /* Set new_row as a sane default, but it may get overwritten in a second */
95                         new_row--;
96
97                         /* Search from the top to correctly handle rowspanned containers */
98                         for (int rows = 0; rows < current_row; rows += t_ws->table[current_col][rows]->rowspan) {
99                                 if (new_row > (rows + (t_ws->table[current_col][rows]->rowspan - 1)))
100                                         continue;
101
102                                 new_row = rows;
103                                 break;
104                         }
105                 } else {
106                         /* Let’s see if there is a screen down/up there to which we can switch */
107                         LOG("container is at %d with height %d\n", container->y, container->height);
108                         i3Screen *screen;
109                         int destination_y = (direction == D_UP ? (container->y - 1) : (container->y + container->height + 1));
110                         if ((screen = get_screen_containing(container->x, destination_y)) == NULL) {
111                                 LOG("Wrapping screen around vertically\n");
112                                 /* No screen found? Then wrap */
113                                 screen = get_screen_most((direction == D_UP ? D_DOWN : D_UP));
114                         }
115                         t_ws = &(workspaces[screen->current_workspace]);
116                         new_row = (direction == D_UP ? (t_ws->rows - 1) : 0);
117                 }
118
119                 check_colrow_boundaries();
120
121                 LOG("new_col = %d, new_row = %d\n", new_col, new_row);
122                 if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
123                         LOG("Cell empty, checking for colspanned client above...\n");
124                         for (int cols = 0; cols < new_col; cols += t_ws->table[cols][new_row]->colspan) {
125                                 if (new_col > (cols + (t_ws->table[cols][new_row]->colspan - 1)))
126                                         continue;
127
128                                 new_col = cols;
129                                 break;
130                         }
131                         LOG("Fixed it to new col %d\n", new_col);
132                 }
133         } else if (direction == D_LEFT || direction == D_RIGHT) {
134                 if (direction == D_RIGHT && cell_exists(current_col+1, current_row))
135                         new_col = current_col + t_ws->table[current_col][current_row]->colspan;
136                 else if (direction == D_LEFT && cell_exists(current_col-1, current_row)) {
137                         /* Set new_col as a sane default, but it may get overwritten in a second */
138                         new_col--;
139
140                         /* Search from the left to correctly handle colspanned containers */
141                         for (int cols = 0; cols < current_col; cols += t_ws->table[cols][current_row]->colspan) {
142                                 if (new_col > (cols + (t_ws->table[cols][current_row]->colspan - 1)))
143                                         continue;
144
145                                 new_col = cols;
146                                 break;
147                         }
148                 } else {
149                         /* Let’s see if there is a screen left/right here to which we can switch */
150                         LOG("container is at %d with width %d\n", container->x, container->width);
151                         i3Screen *screen;
152                         int destination_x = (direction == D_LEFT ? (container->x - 1) : (container->x + container->width + 1));
153                         if ((screen = get_screen_containing(destination_x, container->y)) == NULL) {
154                                 LOG("Wrapping screen around horizontally\n");
155                                 screen = get_screen_most((direction == D_LEFT ? D_RIGHT : D_LEFT));
156                         }
157                         t_ws = &(workspaces[screen->current_workspace]);
158                         new_col = (direction == D_LEFT ? (t_ws->cols - 1) : 0);
159                 }
160
161                 check_colrow_boundaries();
162
163                 LOG("new_col = %d, new_row = %d\n", new_col, new_row);
164                 if (t_ws->table[new_col][new_row]->currently_focused == NULL) {
165                         LOG("Cell empty, checking for rowspanned client above...\n");
166                         for (int rows = 0; rows < new_row; rows += t_ws->table[new_col][rows]->rowspan) {
167                                 if (new_row > (rows + (t_ws->table[new_col][rows]->rowspan - 1)))
168                                         continue;
169
170                                 new_row = rows;
171                                 break;
172                         }
173                         LOG("Fixed it to new row %d\n", new_row);
174                 }
175         } else {
176                 LOG("direction unhandled\n");
177                 return;
178         }
179
180         check_colrow_boundaries();
181
182         if (t_ws->table[new_col][new_row]->currently_focused != NULL)
183                 set_focus(conn, t_ws->table[new_col][new_row]->currently_focused, true);
184 }
185
186 /*
187  * Tries to move the window inside its current container.
188  *
189  * Returns true if the window could be moved, false otherwise.
190  *
191  */
192 static bool move_current_window_in_container(xcb_connection_t *conn, Client *client,
193                 direction_t direction) {
194         assert(client->container != NULL);
195
196         Client *other = (direction == D_UP ? CIRCLEQ_PREV(client, clients) :
197                                              CIRCLEQ_NEXT(client, clients));
198
199         if (other == CIRCLEQ_END(&(client->container->clients)))
200                 return false;
201
202         LOG("i can do that\n");
203         /* We can move the client inside its current container */
204         CIRCLEQ_REMOVE(&(client->container->clients), client, clients);
205         if (direction == D_UP)
206                 CIRCLEQ_INSERT_BEFORE(&(client->container->clients), other, client, clients);
207         else CIRCLEQ_INSERT_AFTER(&(client->container->clients), other, client, clients);
208         render_layout(conn);
209         return true;
210 }
211
212 /*
213  * Moves the current window or whole container to the given direction, creating a column/row if
214  * necessary.
215  *
216  */
217 static void move_current_window(xcb_connection_t *conn, direction_t direction) {
218         LOG("moving window to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" :
219                                             (direction == D_LEFT ? "left" : "right"))));
220         /* Get current window */
221         Container *container = CUR_CELL,
222                   *new = NULL;
223
224         /* There has to be a container, see focus_window() */
225         assert(container != NULL);
226
227         /* If there is no window or the dock window is focused, we’re done */
228         if (container->currently_focused == NULL ||
229             container->currently_focused->dock)
230                 return;
231
232         /* As soon as the client is moved away, the last focused client in the old
233          * container needs to get focus, if any. Therefore, we save it here. */
234         Client *current_client = container->currently_focused;
235         Client *to_focus = get_last_focused_client(conn, container, current_client);
236
237         if (to_focus == NULL) {
238                 to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients);
239                 if (to_focus == NULL)
240                         to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients);
241         }
242
243         switch (direction) {
244                 case D_LEFT:
245                         /* If we’re at the left-most position, move the rest of the table right */
246                         if (current_col == 0) {
247                                 expand_table_cols_at_head(c_ws);
248                                 new = CUR_CELL;
249                         } else
250                                 new = CUR_TABLE[--current_col][current_row];
251                         break;
252                 case D_RIGHT:
253                         if (current_col == (c_ws->cols-1))
254                                 expand_table_cols(c_ws);
255
256                         new = CUR_TABLE[++current_col][current_row];
257                         break;
258                 case D_UP:
259                         if (move_current_window_in_container(conn, current_client, D_UP))
260                                 return;
261
262                         /* if we’re at the up-most position, move the rest of the table down */
263                         if (current_row == 0) {
264                                 expand_table_rows_at_head(c_ws);
265                                 new = CUR_CELL;
266                         } else
267                                 new = CUR_TABLE[current_col][--current_row];
268                         break;
269                 case D_DOWN:
270                         if (move_current_window_in_container(conn, current_client, D_DOWN))
271                                 return;
272
273                         if (current_row == (c_ws->rows-1))
274                                 expand_table_rows(c_ws);
275
276                         new = CUR_TABLE[current_col][++current_row];
277                         break;
278                 /* To make static analyzers happy: */
279                 default:
280                         return;
281         }
282
283         /* Remove it from the old container and put it into the new one */
284         client_remove_from_container(conn, current_client, container, true);
285
286         if (new->currently_focused != NULL)
287                 CIRCLEQ_INSERT_AFTER(&(new->clients), new->currently_focused, current_client, clients);
288         else CIRCLEQ_INSERT_TAIL(&(new->clients), current_client, clients);
289         SLIST_INSERT_HEAD(&(new->workspace->focus_stack), current_client, focus_clients);
290
291         /* Update data structures */
292         current_client->container = new;
293         current_client->workspace = new->workspace;
294         container->currently_focused = to_focus;
295         new->currently_focused = current_client;
296
297         Workspace *workspace = container->workspace;
298
299         /* delete all empty columns/rows */
300         cleanup_table(conn, workspace);
301
302         /* Fix colspan/rowspan if it’d overlap */
303         fix_colrowspan(conn, workspace);
304
305         render_workspace(conn, workspace->screen, workspace);
306         xcb_flush(conn);
307
308         set_focus(conn, current_client, true);
309 }
310
311 static void move_current_container(xcb_connection_t *conn, direction_t direction) {
312         LOG("moving container to direction %s\n", (direction == D_UP ? "up" : (direction == D_DOWN ? "down" :
313                                             (direction == D_LEFT ? "left" : "right"))));
314         /* Get current window */
315         Container *container = CUR_CELL,
316                   *new = NULL;
317
318         Container **old = &CUR_CELL;
319
320         /* There has to be a container, see focus_window() */
321         assert(container != NULL);
322
323         switch (direction) {
324                 case D_LEFT:
325                         /* If we’re at the left-most position, move the rest of the table right */
326                         if (current_col == 0) {
327                                 expand_table_cols_at_head(c_ws);
328                                 new = CUR_CELL;
329                                 old = &CUR_TABLE[current_col+1][current_row];
330                         } else
331                                 new = CUR_TABLE[--current_col][current_row];
332                         break;
333                 case D_RIGHT:
334                         if (current_col == (c_ws->cols-1))
335                                 expand_table_cols(c_ws);
336
337                         new = CUR_TABLE[++current_col][current_row];
338                         break;
339                 case D_UP:
340                         /* if we’re at the up-most position, move the rest of the table down */
341                         if (current_row == 0) {
342                                 expand_table_rows_at_head(c_ws);
343                                 new = CUR_CELL;
344                                 old = &CUR_TABLE[current_col][current_row+1];
345                         } else
346                                 new = CUR_TABLE[current_col][--current_row];
347                         break;
348                 case D_DOWN:
349                         if (current_row == (c_ws->rows-1))
350                                 expand_table_rows(c_ws);
351
352                         new = CUR_TABLE[current_col][++current_row];
353                         break;
354                 /* To make static analyzers happy: */
355                 default:
356                         return;
357         }
358
359         LOG("old = %d,%d and new = %d,%d\n", container->col, container->row, new->col, new->row);
360
361         /* Swap the containers */
362         int col = new->col;
363         int row = new->row;
364
365         *old = new;
366         new->col = container->col;
367         new->row = container->row;
368
369         CUR_CELL = container;
370         container->col = col;
371         container->row = row;
372
373         Workspace *workspace = container->workspace;
374
375         /* delete all empty columns/rows */
376         cleanup_table(conn, workspace);
377
378         /* Fix colspan/rowspan if it’d overlap */
379         fix_colrowspan(conn, workspace);
380
381         render_layout(conn);
382 }
383
384 /*
385  * "Snaps" the current container (not possible for windows, because it works at table base)
386  * to the given direction, that is, adjusts cellspan/rowspan
387  *
388  */
389 static void snap_current_container(xcb_connection_t *conn, direction_t direction) {
390         LOG("snapping container to direction %d\n", direction);
391
392         Container *container = CUR_CELL;
393
394         assert(container != NULL);
395
396         switch (direction) {
397                 case D_LEFT:
398                         /* Snap to the left is actually a move to the left and then a snap right */
399                         if (!cell_exists(container->col - 1, container->row) ||
400                             CUR_TABLE[container->col-1][container->row]->currently_focused != NULL) {
401                                 LOG("cannot snap to left - the cell is already used\n");
402                                 return;
403                         }
404
405                         move_current_window(conn, D_LEFT);
406                         snap_current_container(conn, D_RIGHT);
407                         return;
408                 case D_RIGHT: {
409                         /* Check if the cell is used */
410                         int new_col = container->col + container->colspan;
411                         for (int i = 0; i < container->rowspan; i++)
412                                 if (!cell_exists(new_col, container->row + i) ||
413                                     CUR_TABLE[new_col][container->row + i]->currently_focused != NULL) {
414                                         LOG("cannot snap to right - the cell is already used\n");
415                                         return;
416                                 }
417
418                         /* Check if there are other cells with rowspan, which are in our way.
419                          * If so, reduce their rowspan. */
420                         for (int i = container->row-1; i >= 0; i--) {
421                                 LOG("we got cell %d, %d with rowspan %d\n",
422                                                 new_col, i, CUR_TABLE[new_col][i]->rowspan);
423                                 while ((CUR_TABLE[new_col][i]->rowspan-1) >= (container->row - i))
424                                         CUR_TABLE[new_col][i]->rowspan--;
425                                 LOG("new rowspan = %d\n", CUR_TABLE[new_col][i]->rowspan);
426                         }
427
428                         container->colspan++;
429                         break;
430                 }
431                 case D_UP:
432                         if (!cell_exists(container->col, container->row - 1) ||
433                             CUR_TABLE[container->col][container->row-1]->currently_focused != NULL) {
434                                 LOG("cannot snap to top - the cell is already used\n");
435                                 return;
436                         }
437
438                         move_current_window(conn, D_UP);
439                         snap_current_container(conn, D_DOWN);
440                         return;
441                 case D_DOWN: {
442                         LOG("snapping down\n");
443                         int new_row = container->row + container->rowspan;
444                         for (int i = 0; i < container->colspan; i++)
445                                 if (!cell_exists(container->col + i, new_row) ||
446                                     CUR_TABLE[container->col + i][new_row]->currently_focused != NULL) {
447                                         LOG("cannot snap down - the cell is already used\n");
448                                         return;
449                                 }
450
451                         for (int i = container->col-1; i >= 0; i--) {
452                                 LOG("we got cell %d, %d with colspan %d\n",
453                                                 i, new_row, CUR_TABLE[i][new_row]->colspan);
454                                 while ((CUR_TABLE[i][new_row]->colspan-1) >= (container->col - i))
455                                         CUR_TABLE[i][new_row]->colspan--;
456                                 LOG("new colspan = %d\n", CUR_TABLE[i][new_row]->colspan);
457
458                         }
459
460                         container->rowspan++;
461                         break;
462                 }
463                 /* To make static analyzers happy: */
464                 default:
465                         return;
466         }
467
468         render_layout(conn);
469 }
470
471 static void move_floating_window_to_workspace(xcb_connection_t *conn, Client *client, int workspace) {
472         /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */
473         Workspace *t_ws = &(workspaces[workspace-1]),
474                   *old_ws = client->workspace;
475
476         LOG("moving floating\n");
477
478         if (t_ws->screen == NULL) {
479                 LOG("initializing new workspace, setting num to %d\n", workspace-1);
480                 t_ws->screen = c_ws->screen;
481                 /* Copy the dimensions from the virtual screen */
482                 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
483         } else {
484                 /* Check if there is already a fullscreen client on the destination workspace and
485                  * stop moving if so. */
486                 if (client->fullscreen && (t_ws->fullscreen_client != NULL)) {
487                         LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
488                         return;
489                 }
490         }
491
492         floating_assign_to_workspace(client, t_ws);
493
494         /* If we’re moving it to an invisible screen, we need to unmap it */
495         if (!workspace_is_visible(t_ws)) {
496                 LOG("This workspace is not visible, unmapping\n");
497                 xcb_unmap_window(conn, client->frame);
498         } else {
499                 /* If this is not the case, we move the window to a workspace
500                  * which is on another screen, so we also need to adjust its
501                  * coordinates. */
502                 LOG("before x = %d, y = %d\n", client->rect.x, client->rect.y);
503                 uint32_t relative_x = client->rect.x - old_ws->rect.x,
504                          relative_y = client->rect.y - old_ws->rect.y;
505                 LOG("rel_x = %d, rel_y = %d\n", relative_x, relative_y);
506                 client->rect.x = t_ws->rect.x + relative_x;
507                 client->rect.y = t_ws->rect.y + relative_y;
508                 LOG("after x = %d, y = %d\n", client->rect.x, client->rect.y);
509                 reposition_client(conn, client);
510                 xcb_flush(conn);
511         }
512
513         LOG("done\n");
514
515         render_layout(conn);
516
517         if (workspace_is_visible(t_ws))
518                 set_focus(conn, client, true);
519 }
520
521 /*
522  * Moves the currently selected window to the given workspace
523  *
524  */
525 static void move_current_window_to_workspace(xcb_connection_t *conn, int workspace) {
526         LOG("Moving current window to workspace %d\n", workspace);
527
528         Container *container = CUR_CELL;
529
530         assert(container != NULL);
531
532         /* t_ws (to workspace) is just a container pointer to the workspace we’re switching to */
533         Workspace *t_ws = &(workspaces[workspace-1]);
534
535         Client *current_client = container->currently_focused;
536         if (current_client == NULL) {
537                 LOG("No currently focused client in current container.\n");
538                 return;
539         }
540         Client *to_focus = CIRCLEQ_NEXT_OR_NULL(&(container->clients), current_client, clients);
541         if (to_focus == NULL)
542                 to_focus = CIRCLEQ_PREV_OR_NULL(&(container->clients), current_client, clients);
543
544         if (t_ws->screen == NULL) {
545                 LOG("initializing new workspace, setting num to %d\n", workspace-1);
546                 t_ws->screen = container->workspace->screen;
547                 /* Copy the dimensions from the virtual screen */
548                 memcpy(&(t_ws->rect), &(container->workspace->screen->rect), sizeof(Rect));
549         } else {
550                 /* Check if there is already a fullscreen client on the destination workspace and
551                  * stop moving if so. */
552                 if (current_client->fullscreen && (t_ws->fullscreen_client != NULL)) {
553                         LOG("Not moving: Fullscreen client already existing on destination workspace.\n");
554                         return;
555                 }
556         }
557
558         Container *to_container = t_ws->table[t_ws->current_col][t_ws->current_row];
559
560         assert(to_container != NULL);
561
562         client_remove_from_container(conn, current_client, container, true);
563         if (container->workspace->fullscreen_client == current_client)
564                 container->workspace->fullscreen_client = NULL;
565
566         /* TODO: insert it to the correct position */
567         CIRCLEQ_INSERT_TAIL(&(to_container->clients), current_client, clients);
568
569         SLIST_INSERT_HEAD(&(to_container->workspace->focus_stack), current_client, focus_clients);
570         LOG("Moved.\n");
571
572         current_client->container = to_container;
573         current_client->workspace = to_container->workspace;
574         container->currently_focused = to_focus;
575         to_container->currently_focused = current_client;
576
577         /* If we’re moving it to an invisible screen, we need to unmap it */
578         if (!workspace_is_visible(to_container->workspace)) {
579                 LOG("This workspace is not visible, unmapping\n");
580                 xcb_unmap_window(conn, current_client->frame);
581         } else {
582                 if (current_client->fullscreen) {
583                         LOG("Calling client_enter_fullscreen again\n");
584                         client_enter_fullscreen(conn, current_client);
585                 }
586         }
587
588         /* delete all empty columns/rows */
589         cleanup_table(conn, container->workspace);
590
591         render_layout(conn);
592
593         if (workspace_is_visible(to_container->workspace))
594                 set_focus(conn, current_client, true);
595 }
596
597 /*
598  * Switches to the given workspace
599  *
600  */
601 void show_workspace(xcb_connection_t *conn, int workspace) {
602         Client *client;
603         bool need_warp = false;
604         xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root;
605         /* t_ws (to workspace) is just a convenience pointer to the workspace we’re switching to */
606         Workspace *t_ws = &(workspaces[workspace-1]);
607
608         LOG("show_workspace(%d)\n", workspace);
609
610         /* Store current_row/current_col */
611         c_ws->current_row = current_row;
612         c_ws->current_col = current_col;
613
614         /* Check if the workspace has not been used yet */
615         if (t_ws->screen == NULL) {
616                 LOG("initializing new workspace, setting num to %d\n", workspace);
617                 t_ws->screen = c_ws->screen;
618                 /* Copy the dimensions from the virtual screen */
619                 memcpy(&(t_ws->rect), &(t_ws->screen->rect), sizeof(Rect));
620         }
621
622         if (c_ws->screen != t_ws->screen) {
623                 /* We need to switch to the other screen first */
624                 LOG("moving over to other screen.\n");
625
626                 /* Store the old client */
627                 Client *old_client = CUR_CELL->currently_focused;
628
629                 c_ws = &(workspaces[t_ws->screen->current_workspace]);
630                 current_col = c_ws->current_col;
631                 current_row = c_ws->current_row;
632                 if (CUR_CELL->currently_focused != NULL)
633                         need_warp = true;
634                 else {
635                         Rect *dims = &(c_ws->screen->rect);
636                         xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0,
637                                          dims->x + (dims->width / 2), dims->y + (dims->height / 2));
638                 }
639
640                 /* Re-decorate the old client, it’s not focused anymore */
641                 if ((old_client != NULL) && !old_client->dock)
642                         redecorate_window(conn, old_client);
643                 else xcb_flush(conn);
644         }
645
646         /* Check if we need to change something or if we’re already there */
647         if (c_ws->screen->current_workspace == (workspace-1)) {
648                 Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
649                 if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
650                         set_focus(conn, last_focused, true);
651                         if (need_warp) {
652                                 client_warp_pointer_into(conn, last_focused);
653                                 xcb_flush(conn);
654                         }
655                 }
656
657                 return;
658         }
659
660         t_ws->screen->current_workspace = workspace-1;
661         Workspace *old_workspace = c_ws;
662         c_ws = &workspaces[workspace-1];
663
664         /* Unmap all clients of the old workspace */
665         unmap_workspace(conn, old_workspace);
666
667         current_row = c_ws->current_row;
668         current_col = c_ws->current_col;
669         LOG("new current row = %d, current col = %d\n", current_row, current_col);
670
671         ignore_enter_notify_forall(conn, c_ws, true);
672
673         /* Map all clients on the new workspace */
674         FOR_TABLE(c_ws)
675                 CIRCLEQ_FOREACH(client, &(c_ws->table[cols][rows]->clients), clients)
676                         xcb_map_window(conn, client->frame);
677
678         /* Map all floating clients */
679         if (!c_ws->floating_hidden)
680                 TAILQ_FOREACH(client, &(c_ws->floating_clients), floating_clients)
681                         xcb_map_window(conn, client->frame);
682
683         /* Map all stack windows, if any */
684         struct Stack_Window *stack_win;
685         SLIST_FOREACH(stack_win, &stack_wins, stack_windows)
686                 if (stack_win->container->workspace == c_ws)
687                         xcb_map_window(conn, stack_win->window);
688
689         ignore_enter_notify_forall(conn, c_ws, false);
690
691         /* Restore focus on the new workspace */
692         Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
693         if (last_focused != SLIST_END(&(c_ws->focus_stack))) {
694                 set_focus(conn, last_focused, true);
695                 if (need_warp) {
696                         client_warp_pointer_into(conn, last_focused);
697                         xcb_flush(conn);
698                 }
699         } else xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, root, XCB_CURRENT_TIME);
700
701         render_layout(conn);
702 }
703
704 /*
705  * Jumps to the given window class / title.
706  * Title is matched using strstr, that is, matches if it appears anywhere
707  * in the string. Regular expressions seem to be a bit overkill here. However,
708  * if we need them for something else somewhen, we may introduce them here, too.
709  *
710  */
711 static void jump_to_window(xcb_connection_t *conn, const char *arguments) {
712         char *classtitle;
713         Client *client;
714
715         /* The first character is a quote, this was checked before */
716         classtitle = sstrdup(arguments+1);
717         /* The last character is a quote, we just set it to NULL */
718         classtitle[strlen(classtitle)-1] = '\0';
719
720         if ((client = get_matching_client(conn, classtitle, NULL)) == NULL) {
721                 free(classtitle);
722                 LOG("No matching client found.\n");
723                 return;
724         }
725
726         free(classtitle);
727         set_focus(conn, client, true);
728 }
729
730 /*
731  * Jump directly to the specified workspace, row and col.
732  * Great for reaching windows that you always keep in the same spot (hello irssi, I'm looking at you)
733  *
734  */
735 static void jump_to_container(xcb_connection_t *conn, const char *arguments) {
736         int ws, row, col;
737         int result;
738
739         result = sscanf(arguments, "%d %d %d", &ws, &col, &row);
740         LOG("Jump called with %d parameters (\"%s\")\n", result, arguments);
741
742         /* No match? Either no arguments were specified, or no numbers */
743         if (result < 1) {
744                 LOG("At least one valid argument required\n");
745                 return;
746         }
747
748         /* Move to the target workspace */
749         show_workspace(conn, ws);
750
751         if (result < 3)
752                 return;
753
754         LOG("Boundary-checking col %d, row %d... (max cols %d, max rows %d)\n", col, row, c_ws->cols, c_ws->rows);
755
756         /* Move to row/col */
757         if (row >= c_ws->rows)
758                 row = c_ws->rows - 1;
759         if (col >= c_ws->cols)
760                 col = c_ws->cols - 1;
761
762         LOG("Jumping to col %d, row %d\n", col, row);
763         if (c_ws->table[col][row]->currently_focused != NULL)
764                 set_focus(conn, c_ws->table[col][row]->currently_focused, true);
765 }
766
767 /*
768  * Travels the focus stack by the given number of times (or once, if no argument
769  * was specified). That is, selects the window you were in before you focused
770  * the current window.
771  *
772  * The special values 'floating' (select the next floating window), 'tiling'
773  * (select the next tiling window), 'ft' (if the current window is floating,
774  * select the next tiling window and vice-versa) are also valid
775  *
776  */
777 static void travel_focus_stack(xcb_connection_t *conn, const char *arguments) {
778         /* Start count at -1 to always skip the first element */
779         int times, count = -1;
780         Client *current;
781         bool floating_criteria;
782
783         /* Either it’s one of the special values… */
784         if (strcasecmp(arguments, "floating") == 0) {
785                 floating_criteria = true;
786         } else if (strcasecmp(arguments, "tiling") == 0) {
787                 floating_criteria = false;
788         } else if (strcasecmp(arguments, "ft") == 0) {
789                 Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
790                 if (last_focused == SLIST_END(&(c_ws->focus_stack))) {
791                         LOG("Cannot select the next floating/tiling client because there is no client at all\n");
792                         return;
793                 }
794
795                 floating_criteria = !client_is_floating(last_focused);
796         } else {
797                 /* …or a number was specified */
798                 if (sscanf(arguments, "%u", &times) != 1) {
799                         LOG("No or invalid argument given (\"%s\"), using default of 1 times\n", arguments);
800                         times = 1;
801                 }
802
803                 SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients) {
804                         if (++count < times) {
805                                 LOG("Skipping\n");
806                                 continue;
807                         }
808
809                         LOG("Focussing\n");
810                         set_focus(conn, current, true);
811                         break;
812                 }
813                 return;
814         }
815
816         /* Select the next client matching the criteria parsed above */
817         SLIST_FOREACH(current, &(CUR_CELL->workspace->focus_stack), focus_clients)
818                 if (client_is_floating(current) == floating_criteria) {
819                         set_focus(conn, current, true);
820                         break;
821                 }
822 }
823
824 /*
825  * Goes through the list of arguments (for exec()) and checks if the given argument
826  * is present. If not, it copies the arguments (because we cannot realloc it) and
827  * appends the given argument.
828  *
829  */
830 static char **append_argument(char **original, char *argument) {
831         int num_args;
832         for (num_args = 0; original[num_args] != NULL; num_args++) {
833                 LOG("original argument: \"%s\"\n", original[num_args]);
834                 /* If the argument is already present we return the original pointer */
835                 if (strcmp(original[num_args], argument) == 0)
836                         return original;
837         }
838         /* Copy the original array */
839         char **result = smalloc((num_args+2) * sizeof(char*));
840         memcpy(result, original, num_args * sizeof(char*));
841         result[num_args] = argument;
842         result[num_args+1] = NULL;
843
844         return result;
845 }
846
847 /* 
848  * Switch to next or previous existing workspace
849  *
850  */
851 static void next_previous_workspace(xcb_connection_t *conn, int direction) {
852         Workspace *t_ws;
853         int i;
854
855         if (direction == 'n') {
856                 /* If we are on the last workspace, we cannot go any further */
857                 if (c_ws->num == 9)
858                         return;
859
860                 for (i = c_ws->num + 1; i <= 9; i++) {
861                         t_ws = &(workspaces[i]);
862                         if (t_ws->screen != NULL)
863                                 break;
864                 }
865         } else if (direction == 'p') {
866                 if (c_ws->num == 0)
867                         return;
868                 for (i = c_ws->num - 1; i >= 0 ; i--) {
869                         t_ws = &(workspaces[i]);
870                         if (t_ws->screen != NULL)
871                                 break;
872                 }
873         }
874
875         if (t_ws->screen != NULL)
876                 show_workspace(conn, i+1);
877 }
878
879 /*
880  * Parses a command, see file CMDMODE for more information
881  *
882  */
883 void parse_command(xcb_connection_t *conn, const char *command) {
884         LOG("--- parsing command \"%s\" ---\n", command);
885         /* Get the first client from focus stack because floating clients are not
886          * in any container, therefore CUR_CELL is not appropriate. */
887         Client *last_focused = SLIST_FIRST(&(c_ws->focus_stack));
888         if (last_focused == SLIST_END(&(c_ws->focus_stack)))
889                 last_focused = NULL;
890
891         /* Hmm, just to be sure */
892         if (command[0] == '\0')
893                 return;
894
895         /* Is it an <exec>? Then execute the given command. */
896         if (STARTS_WITH(command, "exec ")) {
897                 LOG("starting \"%s\"\n", command + strlen("exec "));
898                 start_application(command+strlen("exec "));
899                 return;
900         }
901
902         /* Is it an <exit>? */
903         if (STARTS_WITH(command, "exit")) {
904                 LOG("User issued exit-command, exiting without error.\n");
905                 exit(EXIT_SUCCESS);
906         }
907
908         /* Is it a <reload>? */
909         if (STARTS_WITH(command, "reload")) {
910                 load_configuration(conn, NULL, true);
911                 return;
912         }
913
914         /* Is it <restart>? Then restart in place. */
915         if (STARTS_WITH(command, "restart")) {
916                 LOG("restarting \"%s\"...\n", start_argv[0]);
917                 /* make sure -a is in the argument list or append it */
918                 start_argv = append_argument(start_argv, "-a");
919
920                 execvp(start_argv[0], start_argv);
921                 /* not reached */
922         }
923
924         if (STARTS_WITH(command, "kill")) {
925                 if (last_focused == NULL) {
926                         LOG("There is no window to kill\n");
927                         return;
928                 }
929
930                 LOG("Killing current window\n");
931                 client_kill(conn, last_focused);
932                 return;
933         }
934
935         /* Is it a jump to a specified workspace, row, col? */
936         if (STARTS_WITH(command, "jump ")) {
937                 const char *arguments = command + strlen("jump ");
938                 if (arguments[0] == '"')
939                         jump_to_window(conn, arguments);
940                 else jump_to_container(conn, arguments);
941                 return;
942         }
943
944         /* Should we travel the focus stack? */
945         if (STARTS_WITH(command, "focus")) {
946                 const char *arguments = command + strlen("focus ");
947                 travel_focus_stack(conn, arguments);
948                 return;
949         }
950
951         /* Is it 'f' for fullscreen? */
952         if (command[0] == 'f') {
953                 if (last_focused == NULL)
954                         return;
955                 client_toggle_fullscreen(conn, last_focused);
956                 return;
957         }
958
959         /* Is it just 's' for stacking or 'd' for default? */
960         if ((command[0] == 's' || command[0] == 'd') && (command[1] == '\0')) {
961                 if (last_focused == NULL || client_is_floating(last_focused)) {
962                         LOG("not switching, this is a floating client\n");
963                         return;
964                 }
965                 LOG("Switching mode for current container\n");
966                 switch_layout_mode(conn, CUR_CELL, (command[0] == 's' ? MODE_STACK : MODE_DEFAULT));
967                 return;
968         }
969
970         /* Is it 'bn' (border normal), 'bp' (border 1pixel) or 'bb' (border borderless)? */
971         if (command[0] == 'b') {
972                 if (last_focused == NULL) {
973                         LOG("No window focused, cannot change border type\n");
974                         return;
975                 }
976                 client_change_border(conn, last_focused, command[1]);
977                 return;
978         }
979
980         if (command[0] == 'H') {
981                 LOG("Hiding all floating windows\n");
982                 floating_toggle_hide(conn, c_ws);
983                 return;
984         }
985
986         enum { WITH_WINDOW, WITH_CONTAINER, WITH_WORKSPACE } with = WITH_WINDOW;
987
988         /* Is it a <with>? */
989         if (command[0] == 'w') {
990                 command++;
991                 /* TODO: implement */
992                 if (command[0] == 'c') {
993                         with = WITH_CONTAINER;
994                         command++;
995                 } else if (command[0] == 'w') {
996                         with = WITH_WORKSPACE;
997                         command++;
998                 } else {
999                         LOG("not yet implemented.\n");
1000                         return;
1001                 }
1002         }
1003
1004         /* Is it 't' for toggle tiling/floating? */
1005         if (command[0] == 't') {
1006                 if (with == WITH_WORKSPACE) {
1007                         c_ws->auto_float = !c_ws->auto_float;
1008                         LOG("autofloat is now %d\n", c_ws->auto_float);
1009                         return;
1010                 }
1011                 if (last_focused == NULL) {
1012                         LOG("Cannot toggle tiling/floating: workspace empty\n");
1013                         return;
1014                 }
1015
1016                 toggle_floating_mode(conn, last_focused, false);
1017                 /* delete all empty columns/rows */
1018                 cleanup_table(conn, last_focused->workspace);
1019
1020                 /* Fix colspan/rowspan if it’d overlap */
1021                 fix_colrowspan(conn, last_focused->workspace);
1022
1023                 render_workspace(conn, last_focused->workspace->screen, last_focused->workspace);
1024
1025                 /* Re-focus the client because cleanup_table sets the focus to the last
1026                  * focused client inside a container only. */
1027                 set_focus(conn, last_focused, true);
1028
1029                 return;
1030         }
1031
1032         /* Is it 'n' or 'p' for next/previous workspace? (nw) */
1033         if (command[0] == 'n' && command[1] == 'w') {
1034                next_previous_workspace(conn, command[0]);
1035                return;
1036         }
1037
1038         if (command[0] == 'p' && command[1] == 'w') {
1039                next_previous_workspace(conn, command[0]);
1040                return;
1041         }
1042
1043         /* It’s a normal <cmd> */
1044         char *rest = NULL;
1045         enum { ACTION_FOCUS, ACTION_MOVE, ACTION_SNAP } action = ACTION_FOCUS;
1046         direction_t direction;
1047         int times = strtol(command, &rest, 10);
1048         if (rest == NULL) {
1049                 LOG("Invalid command (\"%s\")\n", command);
1050                 return;
1051         }
1052
1053         if (*rest == '\0') {
1054                 /* No rest? This was a workspace number, not a times specification */
1055                 show_workspace(conn, times);
1056                 return;
1057         }
1058
1059         if (*rest == 'm' || *rest == 's') {
1060                 action = (*rest == 'm' ? ACTION_MOVE : ACTION_SNAP);
1061                 rest++;
1062         }
1063
1064         int workspace = strtol(rest, &rest, 10);
1065
1066         if (rest == NULL) {
1067                 LOG("Invalid command (\"%s\")\n", command);
1068                 return;
1069         }
1070
1071         if (*rest == '\0') {
1072                 if (last_focused != NULL && client_is_floating(last_focused))
1073                         move_floating_window_to_workspace(conn, last_focused, workspace);
1074                 else move_current_window_to_workspace(conn, workspace);
1075                 return;
1076         }
1077
1078         if (last_focused == NULL) {
1079                 LOG("Not performing (no window found)\n");
1080                 return;
1081         }
1082
1083         if (client_is_floating(last_focused) &&
1084             (action != ACTION_FOCUS && action != ACTION_MOVE)) {
1085                 LOG("Not performing (floating)\n");
1086                 return;
1087         }
1088
1089         /* Now perform action to <where> */
1090         while (*rest != '\0') {
1091                 if (*rest == 'h')
1092                         direction = D_LEFT;
1093                 else if (*rest == 'j')
1094                         direction = D_DOWN;
1095                 else if (*rest == 'k')
1096                         direction = D_UP;
1097                 else if (*rest == 'l')
1098                         direction = D_RIGHT;
1099                 else {
1100                         LOG("unknown direction: %c\n", *rest);
1101                         return;
1102                 }
1103                 rest++;
1104
1105                 if (action == ACTION_FOCUS) {
1106                         if (client_is_floating(last_focused)) {
1107                                 floating_focus_direction(conn, last_focused, direction);
1108                                 continue;
1109                         }
1110                         focus_thing(conn, direction, (with == WITH_WINDOW ? THING_WINDOW : THING_CONTAINER));
1111                         continue;
1112                 }
1113
1114                 if (action == ACTION_MOVE) {
1115                         if (client_is_floating(last_focused)) {
1116                                 floating_move(conn, last_focused, direction);
1117                                 continue;
1118                         }
1119                         if (with == WITH_WINDOW)
1120                                 move_current_window(conn, direction);
1121                         else move_current_container(conn, direction);
1122                         continue;
1123                 }
1124
1125                 if (action == ACTION_SNAP) {
1126                         snap_current_container(conn, direction);
1127                         continue;
1128                 }
1129         }
1130
1131         LOG("--- done ---\n");
1132 }