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 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 #include "yajl_utils.h"
15 
16 /* Stores a copy of the name of the last used workspace for the workspace
17  * back-and-forth switching. */
18 static char *previous_workspace_name = NULL;
19 
20 /* NULL-terminated list of workspace names (in order) extracted from
21  * keybindings. */
22 static char **binding_workspace_names = NULL;
23 
24 /*
25  * Sets ws->layout to splith/splitv if default_orientation was specified in the
26  * configfile. Otherwise, it uses splith/splitv depending on whether the output
27  * is higher than wide.
28  *
29  */
31  /* If default_orientation is set to NO_ORIENTATION we determine
32  * orientation depending on output resolution. */
34  Con *output = con_get_output(ws);
35  ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
36  ws->rect = output->rect;
37  DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
38  output->rect.width, output->rect.height, ws->layout);
39  } else {
41  }
42 }
43 
44 /*
45  * Returns a pointer to the workspace with the given number (starting at 0),
46  * creating the workspace if necessary (by allocating the necessary amount of
47  * memory and initializing the data structures correctly).
48  *
49  */
50 Con *workspace_get(const char *num, bool *created) {
51  Con *output, *workspace = NULL;
52 
53  TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
54  GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
55 
56  if (workspace == NULL) {
57  LOG("Creating new workspace \"%s\"\n", num);
58  /* unless an assignment is found, we will create this workspace on the current output */
59  output = con_get_output(focused);
60  /* look for assignments */
61  struct Workspace_Assignment *assignment;
62 
63  /* We set workspace->num to the number if this workspace’s name begins
64  * with a positive number. Otherwise it’s a named ws and num will be
65  * -1. */
66  long parsed_num = ws_name_to_number(num);
67 
69  if (strcmp(assignment->name, num) == 0) {
70  DLOG("Found workspace name assignment to output \"%s\"\n", assignment->output);
71  GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
72  break;
73  } else if (parsed_num != -1 && name_is_digits(assignment->name) && ws_name_to_number(assignment->name) == parsed_num) {
74  DLOG("Found workspace number assignment to output \"%s\"\n", assignment->output);
75  GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
76  }
77  }
78 
79  Con *content = output_get_content(output);
80  LOG("got output %p with content %p\n", output, content);
81  /* We need to attach this container after setting its type. con_attach
82  * will handle CT_WORKSPACEs differently */
83  workspace = con_new(NULL, NULL);
84  char *name;
85  sasprintf(&name, "[i3 con] workspace %s", num);
86  x_set_name(workspace, name);
87  free(name);
88  workspace->type = CT_WORKSPACE;
89  FREE(workspace->name);
90  workspace->name = sstrdup(num);
92  workspace->num = parsed_num;
93  LOG("num = %d\n", workspace->num);
94 
95  workspace->parent = content;
97 
98  con_attach(workspace, content, false);
99 
100  ipc_send_workspace_event("init", workspace, NULL);
105  if (created != NULL)
106  *created = true;
107  } else if (created != NULL) {
108  *created = false;
109  }
110 
111  return workspace;
112 }
113 
114 /*
115  * Extracts workspace names from keybindings (e.g. “web” from “bindsym $mod+1
116  * workspace web”), so that when an output needs a workspace, i3 can start with
117  * the first configured one. Needs to be called before reorder_bindings() so
118  * that the config-file order is used, not the i3-internal order.
119  *
120  */
122  Binding *bind;
123  int n = 0;
124  if (binding_workspace_names != NULL) {
125  for (int i = 0; binding_workspace_names[i] != NULL; i++) {
126  free(binding_workspace_names[i]);
127  }
129  }
131  DLOG("binding with command %s\n", bind->command);
132  if (strlen(bind->command) < strlen("workspace ") ||
133  strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
134  continue;
135  DLOG("relevant command = %s\n", bind->command);
136  const char *target = bind->command + strlen("workspace ");
137  while (*target == ' ' || *target == '\t')
138  target++;
139  /* We check if this is the workspace
140  * next/prev/next_on_output/prev_on_output/back_and_forth/number command.
141  * Beware: The workspace names "next", "prev", "next_on_output",
142  * "prev_on_output", "number", "back_and_forth" and "current" are OK,
143  * so we check before stripping the double quotes */
144  if (strncasecmp(target, "next", strlen("next")) == 0 ||
145  strncasecmp(target, "prev", strlen("prev")) == 0 ||
146  strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
147  strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
148  strncasecmp(target, "number", strlen("number")) == 0 ||
149  strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
150  strncasecmp(target, "current", strlen("current")) == 0)
151  continue;
152  char *target_name = parse_string(&target, false);
153  if (target_name == NULL)
154  continue;
155  if (strncasecmp(target_name, "__", strlen("__")) == 0) {
156  LOG("Cannot create workspace \"%s\". Names starting with __ are i3-internal.\n", target);
157  free(target_name);
158  continue;
159  }
160  DLOG("Saving workspace name \"%s\"\n", target_name);
161 
163  binding_workspace_names[n - 1] = target_name;
164  }
166  binding_workspace_names[n - 1] = NULL;
167 }
168 
169 /*
170  * Returns a pointer to a new workspace in the given output. The workspace
171  * is created attached to the tree hierarchy through the given content
172  * container.
173  *
174  */
176  /* add a workspace to this output */
177  Con *out, *current;
178  char *name;
179  bool exists = true;
180  Con *ws = con_new(NULL, NULL);
181  ws->type = CT_WORKSPACE;
182 
183  /* try the configured workspace bindings first to find a free name */
184  for (int n = 0; binding_workspace_names[n] != NULL; n++) {
185  char *target_name = binding_workspace_names[n];
186  /* Ensure that this workspace is not assigned to a different output —
187  * otherwise we would create it, then move it over to its output, then
188  * find a new workspace, etc… */
189  bool assigned = false;
190  struct Workspace_Assignment *assignment;
192  if (strcmp(assignment->name, target_name) != 0 ||
193  strcmp(assignment->output, output->name) == 0)
194  continue;
195 
196  assigned = true;
197  break;
198  }
199 
200  if (assigned)
201  continue;
202 
203  current = NULL;
204  TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
205  GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, target_name));
206  exists = (current != NULL);
207  if (!exists) {
208  ws->name = sstrdup(target_name);
209  /* Set ->num to the number of the workspace, if the name actually
210  * is a number or starts with a number */
211  ws->num = ws_name_to_number(ws->name);
212  LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
213 
214  break;
215  }
216  }
217 
218  if (exists) {
219  /* get the next unused workspace number */
220  DLOG("Getting next unused workspace by number\n");
221  int c = 0;
222  while (exists) {
223  c++;
224 
225  ws->num = c;
226 
227  current = NULL;
228  TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
229  GREP_FIRST(current, output_get_content(out), child->num == ws->num);
230  exists = (current != NULL);
231 
232  DLOG("result for ws %d: exists = %d\n", c, exists);
233  }
234  sasprintf(&(ws->name), "%d", c);
235  }
236  con_attach(ws, content, false);
237 
238  sasprintf(&name, "[i3 con] workspace %s", ws->name);
239  x_set_name(ws, name);
240  free(name);
241 
243 
246 
247  return ws;
248 }
249 
250 /*
251  * Returns true if the workspace is currently visible. Especially important for
252  * multi-monitor environments, as they can have multiple currenlty active
253  * workspaces.
254  *
255  */
257  Con *output = con_get_output(ws);
258  if (output == NULL)
259  return false;
260  Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
261  LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
262  return (fs == ws);
263 }
264 
265 /*
266  * XXX: we need to clean up all this recursive walking code.
267  *
268  */
269 Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
270  Con *current;
271 
272  TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
273  if (current != exclude &&
274  current->sticky_group != NULL &&
275  current->window != NULL &&
276  strcmp(current->sticky_group, sticky_group) == 0)
277  return current;
278 
279  Con *recurse = _get_sticky(current, sticky_group, exclude);
280  if (recurse != NULL)
281  return recurse;
282  }
283 
284  TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
285  if (current != exclude &&
286  current->sticky_group != NULL &&
287  current->window != NULL &&
288  strcmp(current->sticky_group, sticky_group) == 0)
289  return current;
290 
291  Con *recurse = _get_sticky(current, sticky_group, exclude);
292  if (recurse != NULL)
293  return recurse;
294  }
295 
296  return NULL;
297 }
298 
299 /*
300  * Reassigns all child windows in sticky containers. Called when the user
301  * changes workspaces.
302  *
303  * XXX: what about sticky containers which contain containers?
304  *
305  */
306 static void workspace_reassign_sticky(Con *con) {
307  Con *current;
308  /* 1: go through all containers */
309 
310  /* handle all children and floating windows of this node */
311  TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
312  if (current->sticky_group == NULL) {
313  workspace_reassign_sticky(current);
314  continue;
315  }
316 
317  LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
318  /* 2: find a window which we can re-assign */
319  Con *output = con_get_output(current);
320  Con *src = _get_sticky(output, current->sticky_group, current);
321 
322  if (src == NULL) {
323  LOG("No window found for this sticky group\n");
324  workspace_reassign_sticky(current);
325  continue;
326  }
327 
328  x_move_win(src, current);
329  current->window = src->window;
330  current->mapped = true;
331  src->window = NULL;
332  src->mapped = false;
333 
334  x_reparent_child(current, src);
335 
336  LOG("re-assigned window from src %p to dest %p\n", src, current);
337  }
338 
339  TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
340  workspace_reassign_sticky(current);
341 }
342 
343 /*
344  * Callback to reset the urgent flag of the given con to false. May be started by
345  * _workspace_show to avoid urgency hints being lost by switching to a workspace
346  * focusing the con.
347  *
348  */
349 static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents) {
350  Con *con = w->data;
351 
352  ev_timer_stop(main_loop, con->urgency_timer);
353  FREE(con->urgency_timer);
354 
355  if (con->urgent) {
356  DLOG("Resetting urgency flag of con %p by timer\n", con);
357  con_set_urgency(con, false);
360  ipc_send_window_event("urgent", con);
361  tree_render();
362  }
363 }
364 
365 static void _workspace_show(Con *workspace) {
366  Con *current, *old = NULL;
367  Con *old_focus = focused;
368 
369  /* safe-guard against showing i3-internal workspaces like __i3_scratch */
370  if (con_is_internal(workspace))
371  return;
372 
373  /* disable fullscreen for the other workspaces and get the workspace we are
374  * currently on. */
375  TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
376  if (current->fullscreen_mode == CF_OUTPUT)
377  old = current;
378  current->fullscreen_mode = CF_NONE;
379  }
380 
381  /* enable fullscreen for the target workspace. If it happens to be the
382  * same one we are currently on anyways, we can stop here. */
383  workspace->fullscreen_mode = CF_OUTPUT;
384  current = con_get_workspace(focused);
385  if (workspace == current) {
386  DLOG("Not switching, already there.\n");
387  return;
388  }
389 
390  /* Remember currently focused workspace for switching back to it later with
391  * the 'workspace back_and_forth' command.
392  * NOTE: We have to duplicate the name as the original will be freed when
393  * the corresponding workspace is cleaned up.
394  * NOTE: Internal cons such as __i3_scratch (when a scratchpad window is
395  * focused) are skipped, see bug #868. */
396  if (current && !con_is_internal(current)) {
398  if (current) {
400  DLOG("Setting previous_workspace_name = %s\n", previous_workspace_name);
401  }
402  }
403 
404  workspace_reassign_sticky(workspace);
405 
406  DLOG("switching to %p / %s\n", workspace, workspace->name);
407  Con *next = con_descend_focused(workspace);
408 
409  /* Memorize current output */
410  Con *old_output = con_get_output(focused);
411 
412  /* Display urgency hint for a while if the newly visible workspace would
413  * focus and thereby immediately destroy it */
414  if (next->urgent && (int)(config.workspace_urgency_timer * 1000) > 0) {
415  /* focus for now… */
416  next->urgent = false;
417  con_focus(next);
418 
419  /* … but immediately reset urgency flags; they will be set to false by
420  * the timer callback in case the container is focused at the time of
421  * its expiration */
422  focused->urgent = true;
423  workspace->urgent = true;
424 
425  if (focused->urgency_timer == NULL) {
426  DLOG("Deferring reset of urgency flag of con %p on newly shown workspace %p\n",
427  focused, workspace);
428  focused->urgency_timer = scalloc(1, sizeof(struct ev_timer));
429  /* use a repeating timer to allow for easy resets */
432  focused->urgency_timer->data = focused;
433  ev_timer_start(main_loop, focused->urgency_timer);
434  } else {
435  DLOG("Resetting urgency timer of con %p on workspace %p\n",
436  focused, workspace);
437  ev_timer_again(main_loop, focused->urgency_timer);
438  }
439  } else
440  con_focus(next);
441 
442  ipc_send_workspace_event("focus", workspace, current);
443 
444  DLOG("old = %p / %s\n", old, (old ? old->name : "(null)"));
445  /* Close old workspace if necessary. This must be done *after* doing
446  * urgency handling, because tree_close_internal() will do a con_focus() on the next
447  * client, which will clear the urgency flag too early. Also, there is no
448  * way for con_focus() to know about when to clear urgency immediately and
449  * when to defer it. */
450  if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
451  /* check if this workspace is currently visible */
452  if (!workspace_is_visible(old)) {
453  LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
454  yajl_gen gen = ipc_marshal_workspace_event("empty", old, NULL);
455  tree_close_internal(old, DONT_KILL_WINDOW, false, false);
456 
457  const unsigned char *payload;
458  ylength length;
459  y(get_buf, &payload, &length);
460  ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, (const char *)payload);
461 
462  y(free);
463 
468  }
469  }
470 
471  workspace->fullscreen_mode = CF_OUTPUT;
472  LOG("focused now = %p / %s\n", focused, focused->name);
473 
474  /* Set mouse pointer */
475  Con *new_output = con_get_output(focused);
476  if (old_output != new_output) {
477  x_set_warp_to(&next->rect);
478  }
479 
480  /* Update the EWMH hints */
482 
483  /* Push any sticky windows to the now visible workspace. */
484  output_push_sticky_windows(old_focus);
485 }
486 
487 /*
488  * Switches to the given workspace
489  *
490  */
491 void workspace_show(Con *workspace) {
492  _workspace_show(workspace);
493 }
494 
495 /*
496  * Looks up the workspace by name and switches to it.
497  *
498  */
499 void workspace_show_by_name(const char *num) {
500  Con *workspace;
501  workspace = workspace_get(num, NULL);
502  _workspace_show(workspace);
503 }
504 
505 /*
506  * Focuses the next workspace.
507  *
508  */
510  Con *current = con_get_workspace(focused);
511  Con *next = NULL, *first = NULL, *first_opposite = NULL;
512  Con *output;
513 
514  if (current->num == -1) {
515  /* If currently a named workspace, find next named workspace. */
516  if ((next = TAILQ_NEXT(current, nodes)) != NULL)
517  return next;
518  bool found_current = false;
519  TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
520  /* Skip outputs starting with __, they are internal. */
521  if (con_is_internal(output))
522  continue;
524  if (child->type != CT_WORKSPACE)
525  continue;
526  if (!first)
527  first = child;
528  if (!first_opposite && child->num != -1)
529  first_opposite = child;
530  if (child == current) {
531  found_current = true;
532  } else if (child->num == -1 && found_current) {
533  next = child;
534  return next;
535  }
536  }
537  }
538  } else {
539  /* If currently a numbered workspace, find next numbered workspace. */
540  TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
541  /* Skip outputs starting with __, they are internal. */
542  if (con_is_internal(output))
543  continue;
545  if (child->type != CT_WORKSPACE)
546  continue;
547  if (!first)
548  first = child;
549  if (!first_opposite && child->num == -1)
550  first_opposite = child;
551  if (child->num == -1)
552  break;
553  /* Need to check child against current and next because we are
554  * traversing multiple lists and thus are not guaranteed the
555  * relative order between the list of workspaces. */
556  if (current->num < child->num && (!next || child->num < next->num))
557  next = child;
558  }
559  }
560  }
561 
562  if (!next)
563  next = first_opposite ? first_opposite : first;
564 
565  return next;
566 }
567 
568 /*
569  * Focuses the previous workspace.
570  *
571  */
573  Con *current = con_get_workspace(focused);
574  Con *prev = NULL, *first_opposite = NULL, *last = NULL;
575  Con *output;
576 
577  if (current->num == -1) {
578  /* If named workspace, find previous named workspace. */
579  prev = TAILQ_PREV(current, nodes_head, nodes);
580  if (prev && prev->num != -1)
581  prev = NULL;
582  if (!prev) {
583  bool found_current = false;
584  TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
585  /* Skip outputs starting with __, they are internal. */
586  if (con_is_internal(output))
587  continue;
589  if (child->type != CT_WORKSPACE)
590  continue;
591  if (!last)
592  last = child;
593  if (!first_opposite && child->num != -1)
594  first_opposite = child;
595  if (child == current) {
596  found_current = true;
597  } else if (child->num == -1 && found_current) {
598  prev = child;
599  goto workspace_prev_end;
600  }
601  }
602  }
603  }
604  } else {
605  /* If numbered workspace, find previous numbered workspace. */
606  TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
607  /* Skip outputs starting with __, they are internal. */
608  if (con_is_internal(output))
609  continue;
611  if (child->type != CT_WORKSPACE)
612  continue;
613  if (!last)
614  last = child;
615  if (!first_opposite && child->num == -1)
616  first_opposite = child;
617  if (child->num == -1)
618  continue;
619  /* Need to check child against current and previous because we
620  * are traversing multiple lists and thus are not guaranteed
621  * the relative order between the list of workspaces. */
622  if (current->num > child->num && (!prev || child->num > prev->num))
623  prev = child;
624  }
625  }
626  }
627 
628  if (!prev)
629  prev = first_opposite ? first_opposite : last;
630 
631 workspace_prev_end:
632  return prev;
633 }
634 
635 /*
636  * Focuses the next workspace on the same output.
637  *
638  */
640  Con *current = con_get_workspace(focused);
641  Con *next = NULL;
643 
644  if (current->num == -1) {
645  /* If currently a named workspace, find next named workspace. */
646  next = TAILQ_NEXT(current, nodes);
647  } else {
648  /* If currently a numbered workspace, find next numbered workspace. */
650  if (child->type != CT_WORKSPACE)
651  continue;
652  if (child->num == -1)
653  break;
654  /* Need to check child against current and next because we are
655  * traversing multiple lists and thus are not guaranteed the
656  * relative order between the list of workspaces. */
657  if (current->num < child->num && (!next || child->num < next->num))
658  next = child;
659  }
660  }
661 
662  /* Find next named workspace. */
663  if (!next) {
664  bool found_current = false;
666  if (child->type != CT_WORKSPACE)
667  continue;
668  if (child == current) {
669  found_current = true;
670  } else if (child->num == -1 && (current->num != -1 || found_current)) {
671  next = child;
672  goto workspace_next_on_output_end;
673  }
674  }
675  }
676 
677  /* Find first workspace. */
678  if (!next) {
680  if (child->type != CT_WORKSPACE)
681  continue;
682  if (!next || (child->num != -1 && child->num < next->num))
683  next = child;
684  }
685  }
686 workspace_next_on_output_end:
687  return next;
688 }
689 
690 /*
691  * Focuses the previous workspace on same output.
692  *
693  */
695  Con *current = con_get_workspace(focused);
696  Con *prev = NULL;
698  DLOG("output = %s\n", output->name);
699 
700  if (current->num == -1) {
701  /* If named workspace, find previous named workspace. */
702  prev = TAILQ_PREV(current, nodes_head, nodes);
703  if (prev && prev->num != -1)
704  prev = NULL;
705  } else {
706  /* If numbered workspace, find previous numbered workspace. */
708  if (child->type != CT_WORKSPACE || child->num == -1)
709  continue;
710  /* Need to check child against current and previous because we
711  * are traversing multiple lists and thus are not guaranteed
712  * the relative order between the list of workspaces. */
713  if (current->num > child->num && (!prev || child->num > prev->num))
714  prev = child;
715  }
716  }
717 
718  /* Find previous named workspace. */
719  if (!prev) {
720  bool found_current = false;
722  if (child->type != CT_WORKSPACE)
723  continue;
724  if (child == current) {
725  found_current = true;
726  } else if (child->num == -1 && (current->num != -1 || found_current)) {
727  prev = child;
728  goto workspace_prev_on_output_end;
729  }
730  }
731  }
732 
733  /* Find last workspace. */
734  if (!prev) {
736  if (child->type != CT_WORKSPACE)
737  continue;
738  if (!prev || child->num > prev->num)
739  prev = child;
740  }
741  }
742 
743 workspace_prev_on_output_end:
744  return prev;
745 }
746 
747 /*
748  * Focuses the previously focused workspace.
749  *
750  */
753  DLOG("No previous workspace name set. Not switching.\n");
754  return;
755  }
756 
758 }
759 
760 /*
761  * Returns the previously focused workspace con, or NULL if unavailable.
762  *
763  */
766  DLOG("No previous workspace name set.\n");
767  return NULL;
768  }
769 
770  Con *workspace;
771  workspace = workspace_get(previous_workspace_name, NULL);
772 
773  return workspace;
774 }
775 
776 static bool get_urgency_flag(Con *con) {
777  Con *child;
778  TAILQ_FOREACH(child, &(con->nodes_head), nodes)
779  if (child->urgent || get_urgency_flag(child))
780  return true;
781 
782  TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
783  if (child->urgent || get_urgency_flag(child))
784  return true;
785 
786  return false;
787 }
788 
789 /*
790  * Goes through all clients on the given workspace and updates the workspace’s
791  * urgent flag accordingly.
792  *
793  */
795  bool old_flag = ws->urgent;
796  ws->urgent = get_urgency_flag(ws);
797  DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
798 
799  if (old_flag != ws->urgent)
800  ipc_send_workspace_event("urgent", ws, NULL);
801 }
802 
803 /*
804  * 'Forces' workspace orientation by moving all cons into a new split-con with
805  * the same layout as the workspace and then changing the workspace layout.
806  *
807  */
808 void ws_force_orientation(Con *ws, orientation_t orientation) {
809  /* 1: create a new split container */
810  Con *split = con_new(NULL, NULL);
811  split->parent = ws;
812 
813  /* 2: copy layout from workspace */
814  split->layout = ws->layout;
815 
816  Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
817 
818  /* 3: move the existing cons of this workspace below the new con */
819  DLOG("Moving cons\n");
820  while (!TAILQ_EMPTY(&(ws->nodes_head))) {
821  Con *child = TAILQ_FIRST(&(ws->nodes_head));
822  con_detach(child);
823  con_attach(child, split, true);
824  }
825 
826  /* 4: switch workspace layout */
827  ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
828  DLOG("split->layout = %d, ws->layout = %d\n", split->layout, ws->layout);
829 
830  /* 5: attach the new split container to the workspace */
831  DLOG("Attaching new split (%p) to ws (%p)\n", split, ws);
832  con_attach(split, ws, false);
833 
834  /* 6: fix the percentages */
835  con_fix_percent(ws);
836 
837  if (old_focused)
838  con_focus(old_focused);
839 }
840 
841 /*
842  * Called when a new con (with a window, not an empty or split con) should be
843  * attached to the workspace (for example when managing a new window or when
844  * moving an existing window to the workspace level).
845  *
846  * Depending on the workspace_layout setting, this function either returns the
847  * workspace itself (default layout) or creates a new stacked/tabbed con and
848  * returns that.
849  *
850  */
852  DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
853 
854  if (ws->workspace_layout == L_DEFAULT) {
855  DLOG("Default layout, just attaching it to the workspace itself.\n");
856  return ws;
857  }
858 
859  DLOG("Non-default layout, creating a new split container\n");
860  /* 1: create a new split container */
861  Con *new = con_new(NULL, NULL);
862  new->parent = ws;
863 
864  /* 2: set the requested layout on the split con */
865  new->layout = ws->workspace_layout;
866 
867  /* 4: attach the new split container to the workspace */
868  DLOG("Attaching new split %p to workspace %p\n", new, ws);
869  con_attach(new, ws, false);
870 
871  /* 5: fix the percentages */
872  con_fix_percent(ws);
873 
874  return new;
875 }
876 
884  if (TAILQ_EMPTY(&(ws->nodes_head))) {
885  ELOG("Workspace %p / %s has no children to encapsulate\n", ws, ws->name);
886  return NULL;
887  }
888 
889  Con *new = con_new(NULL, NULL);
890  new->parent = ws;
891  new->layout = ws->layout;
892 
893  DLOG("Moving children of workspace %p / %s into container %p\n",
894  ws, ws->name, new);
895 
896  Con *child;
897  while (!TAILQ_EMPTY(&(ws->nodes_head))) {
898  child = TAILQ_FIRST(&(ws->nodes_head));
899  con_detach(child);
900  con_attach(child, new, true);
901  }
902 
903  con_attach(new, ws, true);
904 
905  return new;
906 }
907 
912 bool workspace_move_to_output(Con *ws, const char *name) {
913  LOG("Trying to move workspace %p / %s to output \"%s\".\n", ws, ws->name, name);
914 
915  Con *current_output_con = con_get_output(ws);
916  if (!current_output_con) {
917  ELOG("Could not get the output container for workspace %p / %s.\n", ws, ws->name);
918  return false;
919  }
920 
921  Output *current_output = get_output_by_name(current_output_con->name);
922  if (!current_output) {
923  ELOG("Cannot get current output. This is a bug in i3.\n");
924  return false;
925  }
926  Output *output = get_output_from_string(current_output, name);
927  if (!output) {
928  ELOG("Could not get output from string \"%s\"\n", name);
929  return false;
930  }
931 
932  Con *content = output_get_content(output->con);
933  LOG("got output %p with content %p\n", output, content);
934 
935  Con *previously_visible_ws = TAILQ_FIRST(&(content->focus_head));
936  LOG("Previously visible workspace = %p / %s\n", previously_visible_ws, previously_visible_ws->name);
937 
938  bool workspace_was_visible = workspace_is_visible(ws);
939  if (con_num_children(ws->parent) == 1) {
940  LOG("Creating a new workspace to replace \"%s\" (last on its output).\n", ws->name);
941 
942  /* check if we can find a workspace assigned to this output */
943  bool used_assignment = false;
944  struct Workspace_Assignment *assignment;
946  if (assignment->output == NULL || strcmp(assignment->output, current_output->name) != 0)
947  continue;
948 
949  /* check if this workspace is already attached to the tree */
950  Con *workspace = NULL, *out;
951  TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
952  GREP_FIRST(workspace, output_get_content(out),
953  !strcasecmp(child->name, assignment->name));
954  if (workspace != NULL)
955  continue;
956 
957  /* so create the workspace referenced to by this assignment */
958  LOG("Creating workspace from assignment %s.\n", assignment->name);
959  workspace_get(assignment->name, NULL);
960  used_assignment = true;
961  break;
962  }
963 
964  /* if we couldn't create the workspace using an assignment, create
965  * it on the output */
966  if (!used_assignment)
967  create_workspace_on_output(current_output, ws->parent);
968 
969  /* notify the IPC listeners */
970  ipc_send_workspace_event("init", ws, NULL);
971  }
972  DLOG("Detaching\n");
973 
974  /* detach from the old output and attach to the new output */
975  Con *old_content = ws->parent;
976  con_detach(ws);
977  if (workspace_was_visible) {
978  /* The workspace which we just detached was visible, so focus
979  * the next one in the focus-stack. */
980  Con *focus_ws = TAILQ_FIRST(&(old_content->focus_head));
981  LOG("workspace was visible, focusing %p / %s now\n", focus_ws, focus_ws->name);
982  workspace_show(focus_ws);
983  }
984  con_attach(ws, content, false);
985 
986  /* fix the coordinates of the floating containers */
987  Con *floating_con;
988  TAILQ_FOREACH(floating_con, &(ws->floating_head), floating_windows)
989  floating_fix_coordinates(floating_con, &(old_content->rect), &(content->rect));
990 
991  ipc_send_workspace_event("move", ws, NULL);
992  if (workspace_was_visible) {
993  /* Focus the moved workspace on the destination output. */
994  workspace_show(ws);
995  }
996 
997  /* NB: We cannot simply work with previously_visible_ws since it might
998  * have been cleaned up by workspace_show() already, depending on the
999  * focus order/number of other workspaces on the output.
1000  * Instead, we loop through the available workspaces and only work with
1001  * previously_visible_ws if we still find it. */
1002  TAILQ_FOREACH(ws, &(content->nodes_head), nodes) {
1003  if (ws != previously_visible_ws)
1004  continue;
1005 
1006  /* Call the on_remove_child callback of the workspace which previously
1007  * was visible on the destination output. Since it is no longer
1008  * visible, it might need to get cleaned up. */
1009  CALL(previously_visible_ws, on_remove_child);
1010  break;
1011  }
1012 
1013  return true;
1014 }
uint32_t height
Definition: data.h:145
Con * workspace_next(void)
Returns the next workspace.
Definition: workspace.c:509
int num
the workspace number, if this Con is of type CT_WORKSPACE and the workspace is not a named workspace ...
Definition: data.h:574
struct Window * window
Definition: data.h:611
char * name
Definition: data.h:590
void con_focus(Con *con)
Sets input focus to the given container.
Definition: con.c:198
void con_set_urgency(Con *con, bool urgent)
Set urgency flag to the container, all the parent containers and the workspace.
Definition: con.c:1907
Con * workspace_prev_on_output(void)
Returns the previous workspace on the same output.
Definition: workspace.c:694
Output * get_output_from_string(Output *current_output, const char *output_str)
Returns an &#39;output&#39; corresponding to one of left/right/down/up or a specific output name...
Definition: output.c:33
#define TAILQ_EMPTY(head)
Definition: queue.h:344
Con * output_get_content(Con *output)
Returns the output container below the given output container.
Definition: output.c:18
bool con_is_internal(Con *con)
Returns true if the container is internal, such as __i3_scratch.
Definition: con.c:467
struct bindings_head * bindings
Definition: main.c:73
static char * previous_workspace_name
Definition: workspace.c:18
Con * create_workspace_on_output(Output *output, Con *content)
Returns a pointer to a new workspace in the given output.
Definition: workspace.c:175
#define NODES_FOREACH_REVERSE(head)
Definition: util.h:34
static void workspace_reassign_sticky(Con *con)
Definition: workspace.c:306
void output_push_sticky_windows(Con *to_focus)
Iterates over all outputs and pushes sticky windows to the currently visible workspace on that output...
Definition: output.c:55
void workspace_update_urgent_flag(Con *ws)
Goes through all clients on the given workspace and updates the workspace’s urgent flag accordingly...
Definition: workspace.c:794
bool urgent
Definition: data.h:549
Stores which workspace (by name or number) goes to which output.
Definition: data.h:191
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:347
Con * workspace_get(const char *num, bool *created)
Returns a pointer to the workspace with the given number (starting at 0), creating the workspace if n...
Definition: workspace.c:50
Con * workspace_back_and_forth_get(void)
Returns the previously focused workspace con, or NULL if unavailable.
Definition: workspace.c:764
#define NODES_FOREACH(head)
Definition: util.h:30
orientation_t
Definition: data.h:58
void con_attach(Con *con, Con *parent, bool ignore_focus)
Attaches the given container to the given parent.
Definition: con.c:174
void * scalloc(size_t num, size_t size)
Safe-wrapper around calloc which exits if malloc returns NULL (meaning that there is no more memory a...
bool tree_close_internal(Con *con, kill_window_t kill_window, bool dont_kill_parent, bool force_set_focus)
Closes the given container including all children.
Definition: tree.c:193
Con * con_descend_focused(Con *con)
Returns the focused con inside this client, descending the tree as far as possible.
Definition: con.c:1306
char * parse_string(const char **walk, bool as_word)
Parses a string (or word, if as_word is true).
void ewmh_update_current_desktop(void)
Updates _NET_CURRENT_DESKTOP with the current desktop number.
Definition: ewmh.c:23
fullscreen_mode_t fullscreen_mode
Definition: data.h:627
Definition: data.h:85
#define TAILQ_NEXT(elm, field)
Definition: queue.h:338
Config config
Definition: config.c:17
void tree_render(void)
Renders the tree, that is rendering all outputs using render_con() and pushing the changes to X11 usi...
Definition: tree.c:492
Con * con_get_output(Con *con)
Gets the output container (first container with CT_OUTPUT in hierarchy) this node is on...
Definition: con.c:359
bool mapped
Definition: data.h:545
static void workspace_defer_update_urgent_hint_cb(EV_P_ ev_timer *w, int revents)
Definition: workspace.c:349
void con_detach(Con *con)
Detaches the given container from its current parent.
Definition: con.c:182
Con * workspace_attach_to(Con *ws)
Called when a new con (with a window, not an empty or split con) should be attached to the workspace ...
Definition: workspace.c:851
void ewmh_update_number_of_desktops(void)
Updates _NET_NUMBER_OF_DESKTOPS which we interpret as the number of noninternal workspaces.
Definition: ewmh.c:34
void workspace_show(Con *workspace)
Switches to the given workspace.
Definition: workspace.c:491
struct Con * croot
Definition: tree.c:14
#define ELOG(fmt,...)
Definition: libi3.h:93
char * command
Command, like in command mode.
Definition: data.h:301
uint32_t width
Definition: data.h:144
#define GREP_FIRST(dest, head, condition)
Definition: util.h:39
#define LOG(fmt,...)
Definition: libi3.h:88
Con * _get_sticky(Con *con, const char *sticky_group, Con *exclude)
Definition: workspace.c:269
Con * con
Pointer to the Con which represents this output.
Definition: data.h:348
float workspace_urgency_timer
By default, urgency is cleared immediately when switching to another workspace leads to focusing the ...
Definition: config.h:170
Definition: data.h:91
struct ws_assignments_head ws_assignments
Definition: main.c:86
struct Rect rect
Definition: data.h:580
void x_set_name(Con *con, const char *name)
Sets the WM_NAME property (so, no UTF8, but used only for debugging anyways) of the given name...
Definition: x.c:1204
static char ** binding_workspace_names
Definition: workspace.c:22
#define TAILQ_FOREACH_REVERSE(var, head, headname, field)
Definition: queue.h:352
#define CALL(obj, member,...)
Definition: util.h:56
Con * workspace_next_on_output(void)
Returns the next workspace on the same output.
Definition: workspace.c:639
int default_orientation
Default orientation for new containers.
Definition: config.h:106
void con_update_parents_urgency(Con *con)
Make all parent containers urgent if con is urgent or clear the urgent flag of all parent containers ...
Definition: con.c:1886
void ipc_send_window_event(const char *property, Con *con)
For the window events we send, along the usual &quot;change&quot; field, also the window container, in &quot;container&quot;.
Definition: ipc.c:1249
Definition: data.h:530
Con * con_get_workspace(Con *con)
Gets the workspace container this node is on.
Definition: con.c:373
Con * workspace_prev(void)
Returns the previous workspace.
Definition: workspace.c:572
bool workspace_is_visible(Con *ws)
Returns true if the workspace is currently visible.
Definition: workspace.c:256
struct ev_loop * main_loop
Definition: main.c:65
void * srealloc(void *ptr, size_t size)
Safe-wrapper around realloc which exits if realloc returns NULL (meaning that there is no more memory...
void ipc_send_event(const char *event, uint32_t message_type, const char *payload)
Sends the specified event to all IPC clients which are currently connected and subscribed to this kin...
Definition: ipc.c:45
static void _workspace_show(Con *workspace)
Definition: workspace.c:365
void ewmh_update_wm_desktop(void)
Updates _NET_WM_DESKTOP for all windows.
Definition: ewmh.c:175
Con * con_get_fullscreen_con(Con *con, fullscreen_mode_t fullscreen_mode)
Returns the first fullscreen node below this node.
Definition: con.c:421
#define FREE(pointer)
Definition: util.h:48
void x_set_warp_to(Rect *rect)
Set warp_to coordinates.
Definition: x.c:1246
#define DLOG(fmt,...)
Definition: libi3.h:98
layout_t default_layout
Definition: config.h:99
uint32_t y
Definition: data.h:121
A &#39;Con&#39; represents everything from the X11 root window down to a single X11 window.
Definition: data.h:544
void x_move_win(Con *src, Con *dest)
Moves a child window from Container src to Container dest.
Definition: x.c:198
static bool get_urgency_flag(Con *con)
Definition: workspace.c:776
#define TAILQ_FIRST(head)
Definition: queue.h:336
int sasprintf(char **strp, const char *fmt,...)
Safe-wrapper around asprintf which exits if it returns -1 (meaning that there is no more memory avail...
void con_fix_percent(Con *con)
Updates the percent attribute of the children of the given container.
Definition: con.c:736
enum Con::@20 type
char * sstrdup(const char *str)
Safe-wrapper around strdup which exits if malloc returns NULL (meaning that there is no more memory a...
Definition: data.h:59
int con_num_children(Con *con)
Returns the number of children of this container.
Definition: con.c:720
layout_t layout
Definition: data.h:648
void x_reparent_child(Con *con, Con *old)
Reparents the child window of the given container (necessary for sticky containers).
Definition: x.c:183
struct ev_timer * urgency_timer
Definition: data.h:614
static void _workspace_apply_default_orientation(Con *ws)
Definition: workspace.c:30
void workspace_show_by_name(const char *num)
Looks up the workspace by name and switches to it.
Definition: workspace.c:499
void workspace_back_and_forth(void)
Focuses the previously focused workspace.
Definition: workspace.c:751
char * sticky_group
Definition: data.h:598
long ws_name_to_number(const char *name)
Parses the workspace name as a number.
Definition: util.c:76
void floating_fix_coordinates(Con *con, Rect *old_rect, Rect *new_rect)
Fixes the coordinates of the floating window whenever the window gets reassigned to a different outpu...
Definition: floating.c:862
Output * get_output_by_name(const char *name)
Returns the output with the given name if it is active (!) or NULL.
Definition: randr.c:52
void ws_force_orientation(Con *ws, orientation_t orientation)
&#39;Forces&#39; workspace orientation by moving all cons into a new split-con with the same orientation as t...
Definition: workspace.c:808
An Output is a physical output on your graphics driver.
Definition: data.h:330
bool workspace_move_to_output(Con *ws, const char *name)
Move the given workspace to the specified output.
Definition: workspace.c:912
Con * focused
Definition: tree.c:15
Con * con_new(Con *parent, i3Window *window)
Definition: con.c:69
Definition: data.h:90
layout_t workspace_layout
Definition: data.h:648
void extract_workspace_names_from_bindings(void)
Extracts workspace names from keybindings (e.g.
Definition: workspace.c:121
#define TAILQ_PREV(elm, headname, field)
Definition: queue.h:342
void ewmh_update_desktop_names(void)
Updates _NET_DESKTOP_NAMES: "The names of all virtual desktops.
Definition: ewmh.c:55
struct Con * parent
Definition: data.h:576
char * name
Name of the output.
Definition: data.h:345
Con * workspace_encapsulate(Con *ws)
Creates a new container and re-parents all of children from the given workspace into it...
Definition: workspace.c:883
void ewmh_update_desktop_viewport(void)
Updates _NET_DESKTOP_VIEWPORT, which is an array of pairs of cardinals that define the top left corne...
Definition: ewmh.c:93
size_t ylength
Definition: yajl_utils.h:22
void ipc_send_workspace_event(const char *change, Con *current, Con *old)
For the workspace events we send, along with the usual &quot;change&quot; field, also the workspace container i...
Definition: ipc.c:1233
yajl_gen ipc_marshal_workspace_event(const char *change, Con *current, Con *old)
Generates a json workspace event.
Definition: ipc.c:1200
Holds a keybinding, consisting of a keycode combined with modifiers and the command which is executed...
Definition: data.h:250