i3
workspace.c
Go to the documentation of this file.
1 #undef I3__FILE__
2 #define I3__FILE__ "workspace.c"
3 /*
4  * vim:ts=4:sw=4:expandtab
5  *
6  * i3 - an improved dynamic tiling window manager
7  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
8  *
9  * workspace.c: Modifying workspaces, accessing them, moving containers to
10  * workspaces.
11  *
12  */
13 #include "all.h"
14 
15 /* Stores a copy of the name of the last used workspace for the workspace
16  * back-and-forth switching. */
17 static char *previous_workspace_name = NULL;
18 
19 /*
20  * Sets ws->layout to splith/splitv if default_orientation was specified in the
21  * configfile. Otherwise, it uses splith/splitv depending on whether the output
22  * is higher than wide.
23  *
24  */
26  /* If default_orientation is set to NO_ORIENTATION we determine
27  * orientation depending on output resolution. */
29  Con *output = con_get_output(ws);
30  ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
31  DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
32  output->rect.width, output->rect.height, ws->layout);
33  } else {
34  ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV;
35  }
36 }
37 
38 /*
39  * Returns a pointer to the workspace with the given number (starting at 0),
40  * creating the workspace if necessary (by allocating the necessary amount of
41  * memory and initializing the data structures correctly).
42  *
43  */
44 Con *workspace_get(const char *num, bool *created) {
45  Con *output, *workspace = NULL;
46 
47  TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
48  GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
49 
50  if (workspace == NULL) {
51  LOG("Creating new workspace \"%s\"\n", num);
52  /* unless an assignment is found, we will create this workspace on the current output */
53  output = con_get_output(focused);
54  /* look for assignments */
55  struct Workspace_Assignment *assignment;
57  if (strcmp(assignment->name, num) != 0)
58  continue;
59 
60  LOG("Found workspace assignment to output \"%s\"\n", assignment->output);
61  GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
62  break;
63  }
64  Con *content = output_get_content(output);
65  LOG("got output %p with content %p\n", output, content);
66  /* We need to attach this container after setting its type. con_attach
67  * will handle CT_WORKSPACEs differently */
68  workspace = con_new(NULL, NULL);
69  char *name;
70  sasprintf(&name, "[i3 con] workspace %s", num);
71  x_set_name(workspace, name);
72  free(name);
73  workspace->type = CT_WORKSPACE;
74  FREE(workspace->name);
75  workspace->name = sstrdup(num);
77  /* We set ->num to the number if this workspace’s name begins with a
78  * positive number. Otherwise it’s a named ws and num will be -1. */
79  char *endptr = NULL;
80  long parsed_num = strtol(num, &endptr, 10);
81  if (parsed_num == LONG_MIN ||
82  parsed_num == LONG_MAX ||
83  parsed_num < 0 ||
84  endptr == num)
85  workspace->num = -1;
86  else workspace->num = parsed_num;
87  LOG("num = %d\n", workspace->num);
88 
89  workspace->parent = content;
91 
92  con_attach(workspace, content, false);
93 
94  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
95  if (created != NULL)
96  *created = true;
97  }
98  else if (created != NULL) {
99  *created = false;
100  }
101 
102  return workspace;
103 }
104 
105 /*
106  * Returns a pointer to a new workspace in the given output. The workspace
107  * is created attached to the tree hierarchy through the given content
108  * container.
109  *
110  */
112  /* add a workspace to this output */
113  Con *out, *current;
114  char *name;
115  bool exists = true;
116  Con *ws = con_new(NULL, NULL);
117  ws->type = CT_WORKSPACE;
118 
119  /* try the configured workspace bindings first to find a free name */
120  Binding *bind;
122  DLOG("binding with command %s\n", bind->command);
123  if (strlen(bind->command) < strlen("workspace ") ||
124  strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
125  continue;
126  DLOG("relevant command = %s\n", bind->command);
127  char *target = bind->command + strlen("workspace ");
128  /* We check if this is the workspace
129  * next/prev/next_on_output/prev_on_output/back_and_forth/number command.
130  * Beware: The workspace names "next", "prev", "next_on_output",
131  * "prev_on_output", "number", "back_and_forth" and "current" are OK,
132  * so we check before stripping the double quotes */
133  if (strncasecmp(target, "next", strlen("next")) == 0 ||
134  strncasecmp(target, "prev", strlen("prev")) == 0 ||
135  strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
136  strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
137  strncasecmp(target, "number", strlen("number")) == 0 ||
138  strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
139  strncasecmp(target, "current", strlen("current")) == 0)
140  continue;
141  if (*target == '"')
142  target++;
143  FREE(ws->name);
144  ws->name = strdup(target);
145  if (ws->name[strlen(ws->name)-1] == '"')
146  ws->name[strlen(ws->name)-1] = '\0';
147  DLOG("trying name *%s*\n", ws->name);
148 
149  /* Ensure that this workspace is not assigned to a different output —
150  * otherwise we would create it, then move it over to its output, then
151  * find a new workspace, etc… */
152  bool assigned = false;
153  struct Workspace_Assignment *assignment;
155  if (strcmp(assignment->name, ws->name) != 0 ||
156  strcmp(assignment->output, output->name) == 0)
157  continue;
158 
159  assigned = true;
160  break;
161  }
162 
163  if (assigned)
164  continue;
165 
166  current = NULL;
167  TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
168  GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
169 
170  exists = (current != NULL);
171  if (!exists) {
172  /* Set ->num to the number of the workspace, if the name actually
173  * is a number or starts with a number */
174  char *endptr = NULL;
175  long parsed_num = strtol(ws->name, &endptr, 10);
176  if (parsed_num == LONG_MIN ||
177  parsed_num == LONG_MAX ||
178  parsed_num < 0 ||
179  endptr == ws->name)
180  ws->num = -1;
181  else ws->num = parsed_num;
182  LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
183 
184  break;
185  }
186  }
187 
188  if (exists) {
189  /* get the next unused workspace number */
190  DLOG("Getting next unused workspace by number\n");
191  int c = 0;
192  while (exists) {
193  c++;
194 
195  FREE(ws->name);
196  sasprintf(&(ws->name), "%d", c);
197 
198  current = NULL;
199  TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
200  GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
201  exists = (current != NULL);
202 
203  DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
204  }
205  ws->num = c;
206  }
207  con_attach(ws, content, false);
208 
209  sasprintf(&name, "[i3 con] workspace %s", ws->name);
210  x_set_name(ws, name);
211  free(name);
212 
213  ws->fullscreen_mode = CF_OUTPUT;
214 
216 
217  return ws;
218 }
219 
220 
221 /*
222  * Returns true if the workspace is currently visible. Especially important for
223  * multi-monitor environments, as they can have multiple currenlty active
224  * workspaces.
225  *
226  */
228  Con *output = con_get_output(ws);
229  if (output == NULL)
230  return false;
231  Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
232  LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
233  return (fs == ws);
234 }
235 
236 /*
237  * XXX: we need to clean up all this recursive walking code.
238  *
239  */
240 Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
241  Con *current;
242 
243  TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
244  if (current != exclude &&
245  current->sticky_group != NULL &&
246  current->window != NULL &&
247  strcmp(current->sticky_group, sticky_group) == 0)
248  return current;
249 
250  Con *recurse = _get_sticky(current, sticky_group, exclude);
251  if (recurse != NULL)
252  return recurse;
253  }
254 
255  TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
256  if (current != exclude &&
257  current->sticky_group != NULL &&
258  current->window != NULL &&
259  strcmp(current->sticky_group, sticky_group) == 0)
260  return current;
261 
262  Con *recurse = _get_sticky(current, sticky_group, exclude);
263  if (recurse != NULL)
264  return recurse;
265  }
266 
267  return NULL;
268 }
269 
270 /*
271  * Reassigns all child windows in sticky containers. Called when the user
272  * changes workspaces.
273  *
274  * XXX: what about sticky containers which contain containers?
275  *
276  */
277 static void workspace_reassign_sticky(Con *con) {
278  Con *current;
279  /* 1: go through all containers */
280 
281  /* handle all children and floating windows of this node */
282  TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
283  if (current->sticky_group == NULL) {
284  workspace_reassign_sticky(current);
285  continue;
286  }
287 
288  LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
289  /* 2: find a window which we can re-assign */
290  Con *output = con_get_output(current);
291  Con *src = _get_sticky(output, current->sticky_group, current);
292 
293  if (src == NULL) {
294  LOG("No window found for this sticky group\n");
295  workspace_reassign_sticky(current);
296  continue;
297  }
298 
299  x_move_win(src, current);
300  current->window = src->window;
301  current->mapped = true;
302  src->window = NULL;
303  src->mapped = false;
304 
305  x_reparent_child(current, src);
306 
307  LOG("re-assigned window from src %p to dest %p\n", src, current);
308  }
309 
310  TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
311  workspace_reassign_sticky(current);
312 }
313 
314 
315 static void _workspace_show(Con *workspace) {
316  Con *current, *old = NULL;
317 
318  /* safe-guard against showing i3-internal workspaces like __i3_scratch */
319  if (workspace->name[0] == '_' && workspace->name[1] == '_')
320  return;
321 
322  /* disable fullscreen for the other workspaces and get the workspace we are
323  * currently on. */
324  TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
325  if (current->fullscreen_mode == CF_OUTPUT)
326  old = current;
327  current->fullscreen_mode = CF_NONE;
328  }
329 
330  /* enable fullscreen for the target workspace. If it happens to be the
331  * same one we are currently on anyways, we can stop here. */
332  workspace->fullscreen_mode = CF_OUTPUT;
333  current = con_get_workspace(focused);
334  if (workspace == current) {
335  DLOG("Not switching, already there.\n");
336  return;
337  }
338 
339  /* Remember currently focused workspace for switching back to it later with
340  * the 'workspace back_and_forth' command.
341  * NOTE: We have to duplicate the name as the original will be freed when
342  * the corresponding workspace is cleaned up. */
343 
345  if (current)
347 
348  workspace_reassign_sticky(workspace);
349 
350  LOG("switching to %p\n", workspace);
351  Con *next = con_descend_focused(workspace);
352 
353  if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
354  /* check if this workspace is currently visible */
355  if (!workspace_is_visible(old)) {
356  LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
357  tree_close(old, DONT_KILL_WINDOW, false, false);
358  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
359  }
360  }
361 
362  /* Memorize current output */
363  Con *old_output = con_get_output(focused);
364 
365  con_focus(next);
366  workspace->fullscreen_mode = CF_OUTPUT;
367  LOG("focused now = %p / %s\n", focused, focused->name);
368 
369  /* Set mouse pointer */
370  Con *new_output = con_get_output(focused);
371  if (old_output != new_output) {
372  x_set_warp_to(&next->rect);
373  }
374 
375  /* Update the EWMH hints */
377 
378  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
379 }
380 
381 /*
382  * Switches to the given workspace
383  *
384  */
385 void workspace_show(Con *workspace) {
386  _workspace_show(workspace);
387 }
388 
389 /*
390  * Looks up the workspace by name and switches to it.
391  *
392  */
393 void workspace_show_by_name(const char *num) {
394  Con *workspace;
395  bool changed_num_workspaces;
396  workspace = workspace_get(num, &changed_num_workspaces);
397  _workspace_show(workspace);
398 }
399 
400 /*
401  * Focuses the next workspace.
402  *
403  */
405  Con *current = con_get_workspace(focused);
406  Con *next = NULL;
407  Con *output;
408 
409  if (current->num == -1) {
410  /* If currently a named workspace, find next named workspace. */
411  next = TAILQ_NEXT(current, nodes);
412  } else {
413  /* If currently a numbered workspace, find next numbered workspace. */
414  TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
415  /* Skip outputs starting with __, they are internal. */
416  if (output->name[0] == '_' && output->name[1] == '_')
417  continue;
419  if (child->type != CT_WORKSPACE)
420  continue;
421  if (child->num == -1)
422  break;
423  /* Need to check child against current and next because we are
424  * traversing multiple lists and thus are not guaranteed the
425  * relative order between the list of workspaces. */
426  if (current->num < child->num && (!next || child->num < next->num))
427  next = child;
428  }
429  }
430  }
431 
432  /* Find next named workspace. */
433  if (!next) {
434  bool found_current = false;
435  TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
436  /* Skip outputs starting with __, they are internal. */
437  if (output->name[0] == '_' && output->name[1] == '_')
438  continue;
440  if (child->type != CT_WORKSPACE)
441  continue;
442  if (child == current) {
443  found_current = 1;
444  } else if (child->num == -1 && (current->num != -1 || found_current)) {
445  next = child;
446  goto workspace_next_end;
447  }
448  }
449  }
450  }
451 
452  /* Find first workspace. */
453  if (!next) {
454  TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
455  /* Skip outputs starting with __, they are internal. */
456  if (output->name[0] == '_' && output->name[1] == '_')
457  continue;
459  if (child->type != CT_WORKSPACE)
460  continue;
461  if (!next || (child->num != -1 && child->num < next->num))
462  next = child;
463  }
464  }
465  }
466 workspace_next_end:
467  return next;
468 }
469 
470 /*
471  * Focuses the previous workspace.
472  *
473  */
475  Con *current = con_get_workspace(focused);
476  Con *prev = NULL;
477  Con *output;
478 
479  if (current->num == -1) {
480  /* If named workspace, find previous named workspace. */
481  prev = TAILQ_PREV(current, nodes_head, nodes);
482  if (prev && prev->num != -1)
483  prev = NULL;
484  } else {
485  /* If numbered workspace, find previous numbered workspace. */
486  TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
487  /* Skip outputs starting with __, they are internal. */
488  if (output->name[0] == '_' && output->name[1] == '_')
489  continue;
491  if (child->type != CT_WORKSPACE || child->num == -1)
492  continue;
493  /* Need to check child against current and previous because we
494  * are traversing multiple lists and thus are not guaranteed
495  * the relative order between the list of workspaces. */
496  if (current->num > child->num && (!prev || child->num > prev->num))
497  prev = child;
498  }
499  }
500  }
501 
502  /* Find previous named workspace. */
503  if (!prev) {
504  bool found_current = false;
505  TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
506  /* Skip outputs starting with __, they are internal. */
507  if (output->name[0] == '_' && output->name[1] == '_')
508  continue;
510  if (child->type != CT_WORKSPACE)
511  continue;
512  if (child == current) {
513  found_current = true;
514  } else if (child->num == -1 && (current->num != -1 || found_current)) {
515  prev = child;
516  goto workspace_prev_end;
517  }
518  }
519  }
520  }
521 
522  /* Find last workspace. */
523  if (!prev) {
524  TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
525  /* Skip outputs starting with __, they are internal. */
526  if (output->name[0] == '_' && output->name[1] == '_')
527  continue;
529  if (child->type != CT_WORKSPACE)
530  continue;
531  if (!prev || child->num > prev->num)
532  prev = child;
533  }
534  }
535  }
536 
537 workspace_prev_end:
538  return prev;
539 }
540 
541 
542 /*
543  * Focuses the next workspace on the same output.
544  *
545  */
547  Con *current = con_get_workspace(focused);
548  Con *next = NULL;
550 
551  if (current->num == -1) {
552  /* If currently a named workspace, find next named workspace. */
553  next = TAILQ_NEXT(current, nodes);
554  } else {
555  /* If currently a numbered workspace, find next numbered workspace. */
557  if (child->type != CT_WORKSPACE)
558  continue;
559  if (child->num == -1)
560  break;
561  /* Need to check child against current and next because we are
562  * traversing multiple lists and thus are not guaranteed the
563  * relative order between the list of workspaces. */
564  if (current->num < child->num && (!next || child->num < next->num))
565  next = child;
566  }
567  }
568 
569  /* Find next named workspace. */
570  if (!next) {
571  bool found_current = false;
573  if (child->type != CT_WORKSPACE)
574  continue;
575  if (child == current) {
576  found_current = 1;
577  } else if (child->num == -1 && (current->num != -1 || found_current)) {
578  next = child;
579  goto workspace_next_on_output_end;
580  }
581  }
582  }
583 
584  /* Find first workspace. */
585  if (!next) {
587  if (child->type != CT_WORKSPACE)
588  continue;
589  if (!next || (child->num != -1 && child->num < next->num))
590  next = child;
591  }
592  }
593 workspace_next_on_output_end:
594  return next;
595 }
596 
597 /*
598  * Focuses the previous workspace on same output.
599  *
600  */
602  Con *current = con_get_workspace(focused);
603  Con *prev = NULL;
605  DLOG("output = %s\n", output->name);
606 
607  if (current->num == -1) {
608  /* If named workspace, find previous named workspace. */
609  prev = TAILQ_PREV(current, nodes_head, nodes);
610  if (prev && prev->num != -1)
611  prev = NULL;
612  } else {
613  /* If numbered workspace, find previous numbered workspace. */
615  if (child->type != CT_WORKSPACE || child->num == -1)
616  continue;
617  /* Need to check child against current and previous because we
618  * are traversing multiple lists and thus are not guaranteed
619  * the relative order between the list of workspaces. */
620  if (current->num > child->num && (!prev || child->num > prev->num))
621  prev = child;
622  }
623  }
624 
625  /* Find previous named workspace. */
626  if (!prev) {
627  bool found_current = false;
629  if (child->type != CT_WORKSPACE)
630  continue;
631  if (child == current) {
632  found_current = true;
633  } else if (child->num == -1 && (current->num != -1 || found_current)) {
634  prev = child;
635  goto workspace_prev_on_output_end;
636  }
637  }
638  }
639 
640  /* Find last workspace. */
641  if (!prev) {
643  if (child->type != CT_WORKSPACE)
644  continue;
645  if (!prev || child->num > prev->num)
646  prev = child;
647  }
648  }
649 
650 workspace_prev_on_output_end:
651  return prev;
652 }
653 
654 /*
655  * Focuses the previously focused workspace.
656  *
657  */
660  DLOG("No previous workspace name set. Not switching.");
661  return;
662  }
663 
665 }
666 
667 static bool get_urgency_flag(Con *con) {
668  Con *child;
669  TAILQ_FOREACH(child, &(con->nodes_head), nodes)
670  if (child->urgent || get_urgency_flag(child))
671  return true;
672 
673  TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
674  if (child->urgent || get_urgency_flag(child))
675  return true;
676 
677  return false;
678 }
679 
680 /*
681  * Goes through all clients on the given workspace and updates the workspace’s
682  * urgent flag accordingly.
683  *
684  */
686  bool old_flag = ws->urgent;
687  ws->urgent = get_urgency_flag(ws);
688  DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
689 
690  if (old_flag != ws->urgent)
691  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
692 }
693 
694 /*
695  * 'Forces' workspace orientation by moving all cons into a new split-con with
696  * the same layout as the workspace and then changing the workspace layout.
697  *
698  */
699 void ws_force_orientation(Con *ws, orientation_t orientation) {
700  /* 1: create a new split container */
701  Con *split = con_new(NULL, NULL);
702  split->parent = ws;
703  split->split = true;
704 
705  /* 2: copy layout from workspace */
706  split->layout = ws->layout;
707 
708  Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
709 
710  /* 3: move the existing cons of this workspace below the new con */
711  DLOG("Moving cons\n");
712  while (!TAILQ_EMPTY(&(ws->nodes_head))) {
713  Con *child = TAILQ_FIRST(&(ws->nodes_head));
714  con_detach(child);
715  con_attach(child, split, true);
716  }
717 
718  /* 4: switch workspace layout */
719  ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
720  DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout);
721 
722  /* 5: attach the new split container to the workspace */
723  DLOG("Attaching new split (%p) to ws (%p)\n", split, ws);
724  con_attach(split, ws, false);
725 
726  /* 6: fix the percentages */
727  con_fix_percent(ws);
728 
729  if (old_focused)
730  con_focus(old_focused);
731 }
732 
733 /*
734  * Called when a new con (with a window, not an empty or split con) should be
735  * attached to the workspace (for example when managing a new window or when
736  * moving an existing window to the workspace level).
737  *
738  * Depending on the workspace_layout setting, this function either returns the
739  * workspace itself (default layout) or creates a new stacked/tabbed con and
740  * returns that.
741  *
742  */
744  DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
745 
746  if (ws->workspace_layout == L_DEFAULT) {
747  DLOG("Default layout, just attaching it to the workspace itself.\n");
748  return ws;
749  }
750 
751  DLOG("Non-default layout, creating a new split container\n");
752  /* 1: create a new split container */
753  Con *new = con_new(NULL, NULL);
754  new->parent = ws;
755  new->split = true;
756 
757  /* 2: set the requested layout on the split con */
758  new->layout = ws->workspace_layout;
759 
760  /* 4: attach the new split container to the workspace */
761  DLOG("Attaching new split %p to workspace %p\n", new, ws);
762  con_attach(new, ws, false);
763 
764  return new;
765 }