3 * vim:ts=4:sw=4:expandtab
5 * i3 - an improved dynamic tiling window manager
6 * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
8 * workspace.c: Modifying workspaces, accessing them, moving containers to
14 /* Stores a copy of the name of the last used workspace for the workspace
15 * back-and-forth switching. */
16 static char *previous_workspace_name = NULL;
19 * Sets ws->layout to splith/splitv if default_orientation was specified in the
20 * configfile. Otherwise, it uses splith/splitv depending on whether the output
21 * is higher than wide.
24 static void _workspace_apply_default_orientation(Con *ws) {
25 /* If default_orientation is set to NO_ORIENTATION we determine
26 * orientation depending on output resolution. */
27 if (config.default_orientation == NO_ORIENTATION) {
28 Con *output = con_get_output(ws);
29 ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
30 DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
31 output->rect.width, output->rect.height, ws->layout);
33 ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV;
38 * Returns a pointer to the workspace with the given number (starting at 0),
39 * creating the workspace if necessary (by allocating the necessary amount of
40 * memory and initializing the data structures correctly).
43 Con *workspace_get(const char *num, bool *created) {
44 Con *output, *workspace = NULL;
46 TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
47 GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
49 if (workspace == NULL) {
50 LOG("Creating new workspace \"%s\"\n", num);
51 /* unless an assignment is found, we will create this workspace on the current output */
52 output = con_get_output(focused);
53 /* look for assignments */
54 struct Workspace_Assignment *assignment;
55 TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
56 if (strcmp(assignment->name, num) != 0)
59 LOG("Found workspace assignment to output \"%s\"\n", assignment->output);
60 GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
63 Con *content = output_get_content(output);
64 LOG("got output %p with content %p\n", output, content);
65 /* We need to attach this container after setting its type. con_attach
66 * will handle CT_WORKSPACEs differently */
67 workspace = con_new(NULL, NULL);
69 sasprintf(&name, "[i3 con] workspace %s", num);
70 x_set_name(workspace, name);
72 workspace->type = CT_WORKSPACE;
73 FREE(workspace->name);
74 workspace->name = sstrdup(num);
75 /* We set ->num to the number if this workspace’s name begins with a
76 * positive number. Otherwise it’s a named ws and num will be -1. */
78 long parsed_num = strtol(num, &endptr, 10);
79 if (parsed_num == LONG_MIN ||
80 parsed_num == LONG_MAX ||
84 else workspace->num = parsed_num;
85 LOG("num = %d\n", workspace->num);
87 workspace->parent = content;
88 _workspace_apply_default_orientation(workspace);
90 con_attach(workspace, content, false);
92 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
96 else if (created != NULL) {
104 * Returns a pointer to a new workspace in the given output. The workspace
105 * is created attached to the tree hierarchy through the given content
109 Con *create_workspace_on_output(Output *output, Con *content) {
110 /* add a workspace to this output */
114 Con *ws = con_new(NULL, NULL);
115 ws->type = CT_WORKSPACE;
117 /* try the configured workspace bindings first to find a free name */
119 TAILQ_FOREACH(bind, bindings, bindings) {
120 DLOG("binding with command %s\n", bind->command);
121 if (strlen(bind->command) < strlen("workspace ") ||
122 strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
124 DLOG("relevant command = %s\n", bind->command);
125 char *target = bind->command + strlen("workspace ");
126 /* We check if this is the workspace
127 * next/prev/next_on_output/prev_on_output/back_and_forth/number command.
128 * Beware: The workspace names "next", "prev", "next_on_output",
129 * "prev_on_output", "number", "back_and_forth" and "current" are OK,
130 * so we check before stripping the double quotes */
131 if (strncasecmp(target, "next", strlen("next")) == 0 ||
132 strncasecmp(target, "prev", strlen("prev")) == 0 ||
133 strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
134 strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
135 strncasecmp(target, "number", strlen("number")) == 0 ||
136 strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
137 strncasecmp(target, "current", strlen("current")) == 0)
142 ws->name = strdup(target);
143 if (ws->name[strlen(ws->name)-1] == '"')
144 ws->name[strlen(ws->name)-1] = '\0';
145 DLOG("trying name *%s*\n", ws->name);
147 /* Ensure that this workspace is not assigned to a different output —
148 * otherwise we would create it, then move it over to its output, then
149 * find a new workspace, etc… */
150 bool assigned = false;
151 struct Workspace_Assignment *assignment;
152 TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
153 if (strcmp(assignment->name, ws->name) != 0 ||
154 strcmp(assignment->output, output->name) == 0)
165 TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
166 GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
168 exists = (current != NULL);
170 /* Set ->num to the number of the workspace, if the name actually
171 * is a number or starts with a number */
173 long parsed_num = strtol(ws->name, &endptr, 10);
174 if (parsed_num == LONG_MIN ||
175 parsed_num == LONG_MAX ||
179 else ws->num = parsed_num;
180 LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
187 /* get the next unused workspace number */
188 DLOG("Getting next unused workspace by number\n");
194 sasprintf(&(ws->name), "%d", c);
197 TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
198 GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
199 exists = (current != NULL);
201 DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
205 con_attach(ws, content, false);
207 sasprintf(&name, "[i3 con] workspace %s", ws->name);
208 x_set_name(ws, name);
211 ws->fullscreen_mode = CF_OUTPUT;
213 _workspace_apply_default_orientation(ws);
220 * Returns true if the workspace is currently visible. Especially important for
221 * multi-monitor environments, as they can have multiple currenlty active
225 bool workspace_is_visible(Con *ws) {
226 Con *output = con_get_output(ws);
229 Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
230 LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
235 * XXX: we need to clean up all this recursive walking code.
238 Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
241 TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
242 if (current != exclude &&
243 current->sticky_group != NULL &&
244 current->window != NULL &&
245 strcmp(current->sticky_group, sticky_group) == 0)
248 Con *recurse = _get_sticky(current, sticky_group, exclude);
253 TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
254 if (current != exclude &&
255 current->sticky_group != NULL &&
256 current->window != NULL &&
257 strcmp(current->sticky_group, sticky_group) == 0)
260 Con *recurse = _get_sticky(current, sticky_group, exclude);
269 * Reassigns all child windows in sticky containers. Called when the user
270 * changes workspaces.
272 * XXX: what about sticky containers which contain containers?
275 static void workspace_reassign_sticky(Con *con) {
277 /* 1: go through all containers */
279 /* handle all children and floating windows of this node */
280 TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
281 if (current->sticky_group == NULL) {
282 workspace_reassign_sticky(current);
286 LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
287 /* 2: find a window which we can re-assign */
288 Con *output = con_get_output(current);
289 Con *src = _get_sticky(output, current->sticky_group, current);
292 LOG("No window found for this sticky group\n");
293 workspace_reassign_sticky(current);
297 x_move_win(src, current);
298 current->window = src->window;
299 current->mapped = true;
303 x_reparent_child(current, src);
305 LOG("re-assigned window from src %p to dest %p\n", src, current);
308 TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
309 workspace_reassign_sticky(current);
313 static void _workspace_show(Con *workspace) {
314 Con *current, *old = NULL;
316 /* safe-guard against showing i3-internal workspaces like __i3_scratch */
317 if (workspace->name[0] == '_' && workspace->name[1] == '_')
320 /* disable fullscreen for the other workspaces and get the workspace we are
322 TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
323 if (current->fullscreen_mode == CF_OUTPUT)
325 current->fullscreen_mode = CF_NONE;
328 /* enable fullscreen for the target workspace. If it happens to be the
329 * same one we are currently on anyways, we can stop here. */
330 workspace->fullscreen_mode = CF_OUTPUT;
331 current = con_get_workspace(focused);
332 if (workspace == current) {
333 DLOG("Not switching, already there.\n");
337 /* Remember currently focused workspace for switching back to it later with
338 * the 'workspace back_and_forth' command.
339 * NOTE: We have to duplicate the name as the original will be freed when
340 * the corresponding workspace is cleaned up. */
342 FREE(previous_workspace_name);
344 previous_workspace_name = sstrdup(current->name);
346 workspace_reassign_sticky(workspace);
348 LOG("switching to %p\n", workspace);
349 Con *next = con_descend_focused(workspace);
351 if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
352 /* check if this workspace is currently visible */
353 if (!workspace_is_visible(old)) {
354 LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
355 tree_close(old, DONT_KILL_WINDOW, false, false);
356 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
360 /* Memorize current output */
361 Con *old_output = con_get_output(focused);
364 workspace->fullscreen_mode = CF_OUTPUT;
365 LOG("focused now = %p / %s\n", focused, focused->name);
367 /* Set mouse pointer */
368 Con *new_output = con_get_output(focused);
369 if (old_output != new_output) {
370 x_set_warp_to(&next->rect);
373 /* Update the EWMH hints */
374 ewmh_update_current_desktop();
376 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
380 * Switches to the given workspace
383 void workspace_show(Con *workspace) {
384 _workspace_show(workspace);
388 * Looks up the workspace by name and switches to it.
391 void workspace_show_by_name(const char *num) {
393 bool changed_num_workspaces;
394 workspace = workspace_get(num, &changed_num_workspaces);
395 _workspace_show(workspace);
399 * Focuses the next workspace.
402 Con* workspace_next(void) {
403 Con *current = con_get_workspace(focused);
407 if (current->num == -1) {
408 /* If currently a named workspace, find next named workspace. */
409 next = TAILQ_NEXT(current, nodes);
411 /* If currently a numbered workspace, find next numbered workspace. */
412 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
413 /* Skip outputs starting with __, they are internal. */
414 if (output->name[0] == '_' && output->name[1] == '_')
416 NODES_FOREACH(output_get_content(output)) {
417 if (child->type != CT_WORKSPACE)
419 if (child->num == -1)
421 /* Need to check child against current and next because we are
422 * traversing multiple lists and thus are not guaranteed the
423 * relative order between the list of workspaces. */
424 if (current->num < child->num && (!next || child->num < next->num))
430 /* Find next named workspace. */
432 bool found_current = false;
433 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
434 /* Skip outputs starting with __, they are internal. */
435 if (output->name[0] == '_' && output->name[1] == '_')
437 NODES_FOREACH(output_get_content(output)) {
438 if (child->type != CT_WORKSPACE)
440 if (child == current) {
442 } else if (child->num == -1 && (current->num != -1 || found_current)) {
444 goto workspace_next_end;
450 /* Find first workspace. */
452 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
453 /* Skip outputs starting with __, they are internal. */
454 if (output->name[0] == '_' && output->name[1] == '_')
456 NODES_FOREACH(output_get_content(output)) {
457 if (child->type != CT_WORKSPACE)
459 if (!next || (child->num != -1 && child->num < next->num))
469 * Focuses the previous workspace.
472 Con* workspace_prev(void) {
473 Con *current = con_get_workspace(focused);
477 if (current->num == -1) {
478 /* If named workspace, find previous named workspace. */
479 prev = TAILQ_PREV(current, nodes_head, nodes);
480 if (prev && prev->num != -1)
483 /* If numbered workspace, find previous numbered workspace. */
484 TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
485 /* Skip outputs starting with __, they are internal. */
486 if (output->name[0] == '_' && output->name[1] == '_')
488 NODES_FOREACH_REVERSE(output_get_content(output)) {
489 if (child->type != CT_WORKSPACE || child->num == -1)
491 /* Need to check child against current and previous because we
492 * are traversing multiple lists and thus are not guaranteed
493 * the relative order between the list of workspaces. */
494 if (current->num > child->num && (!prev || child->num > prev->num))
500 /* Find previous named workspace. */
502 bool found_current = false;
503 TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
504 /* Skip outputs starting with __, they are internal. */
505 if (output->name[0] == '_' && output->name[1] == '_')
507 NODES_FOREACH_REVERSE(output_get_content(output)) {
508 if (child->type != CT_WORKSPACE)
510 if (child == current) {
511 found_current = true;
512 } else if (child->num == -1 && (current->num != -1 || found_current)) {
514 goto workspace_prev_end;
520 /* Find last workspace. */
522 TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
523 /* Skip outputs starting with __, they are internal. */
524 if (output->name[0] == '_' && output->name[1] == '_')
526 NODES_FOREACH_REVERSE(output_get_content(output)) {
527 if (child->type != CT_WORKSPACE)
529 if (!prev || child->num > prev->num)
541 * Focuses the next workspace on the same output.
544 Con* workspace_next_on_output(void) {
545 Con *current = con_get_workspace(focused);
547 Con *output = con_get_output(focused);
549 if (current->num == -1) {
550 /* If currently a named workspace, find next named workspace. */
551 next = TAILQ_NEXT(current, nodes);
553 /* If currently a numbered workspace, find next numbered workspace. */
554 NODES_FOREACH(output_get_content(output)) {
555 if (child->type != CT_WORKSPACE)
557 if (child->num == -1)
559 /* Need to check child against current and next because we are
560 * traversing multiple lists and thus are not guaranteed the
561 * relative order between the list of workspaces. */
562 if (current->num < child->num && (!next || child->num < next->num))
567 /* Find next named workspace. */
569 bool found_current = false;
570 NODES_FOREACH(output_get_content(output)) {
571 if (child->type != CT_WORKSPACE)
573 if (child == current) {
575 } else if (child->num == -1 && (current->num != -1 || found_current)) {
577 goto workspace_next_on_output_end;
582 /* Find first workspace. */
584 NODES_FOREACH(output_get_content(output)) {
585 if (child->type != CT_WORKSPACE)
587 if (!next || (child->num != -1 && child->num < next->num))
591 workspace_next_on_output_end:
596 * Focuses the previous workspace on same output.
599 Con* workspace_prev_on_output(void) {
600 Con *current = con_get_workspace(focused);
602 Con *output = con_get_output(focused);
603 DLOG("output = %s\n", output->name);
605 if (current->num == -1) {
606 /* If named workspace, find previous named workspace. */
607 prev = TAILQ_PREV(current, nodes_head, nodes);
608 if (prev && prev->num != -1)
611 /* If numbered workspace, find previous numbered workspace. */
612 NODES_FOREACH_REVERSE(output_get_content(output)) {
613 if (child->type != CT_WORKSPACE || child->num == -1)
615 /* Need to check child against current and previous because we
616 * are traversing multiple lists and thus are not guaranteed
617 * the relative order between the list of workspaces. */
618 if (current->num > child->num && (!prev || child->num > prev->num))
623 /* Find previous named workspace. */
625 bool found_current = false;
626 NODES_FOREACH_REVERSE(output_get_content(output)) {
627 if (child->type != CT_WORKSPACE)
629 if (child == current) {
630 found_current = true;
631 } else if (child->num == -1 && (current->num != -1 || found_current)) {
633 goto workspace_prev_on_output_end;
638 /* Find last workspace. */
640 NODES_FOREACH_REVERSE(output_get_content(output)) {
641 if (child->type != CT_WORKSPACE)
643 if (!prev || child->num > prev->num)
648 workspace_prev_on_output_end:
653 * Focuses the previously focused workspace.
656 void workspace_back_and_forth(void) {
657 if (!previous_workspace_name) {
658 DLOG("No previous workspace name set. Not switching.");
662 workspace_show_by_name(previous_workspace_name);
665 static bool get_urgency_flag(Con *con) {
667 TAILQ_FOREACH(child, &(con->nodes_head), nodes)
668 if (child->urgent || get_urgency_flag(child))
671 TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
672 if (child->urgent || get_urgency_flag(child))
679 * Goes through all clients on the given workspace and updates the workspace’s
680 * urgent flag accordingly.
683 void workspace_update_urgent_flag(Con *ws) {
684 bool old_flag = ws->urgent;
685 ws->urgent = get_urgency_flag(ws);
686 DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
688 if (old_flag != ws->urgent)
689 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
693 * 'Forces' workspace orientation by moving all cons into a new split-con with
694 * the same layout as the workspace and then changing the workspace layout.
697 void ws_force_orientation(Con *ws, orientation_t orientation) {
698 /* 1: create a new split container */
699 Con *split = con_new(NULL, NULL);
702 /* 2: copy layout from workspace */
703 split->layout = ws->layout;
705 Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
707 /* 3: move the existing cons of this workspace below the new con */
708 DLOG("Moving cons\n");
709 while (!TAILQ_EMPTY(&(ws->nodes_head))) {
710 Con *child = TAILQ_FIRST(&(ws->nodes_head));
712 con_attach(child, split, true);
715 /* 4: switch workspace layout */
716 ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
718 /* 5: attach the new split container to the workspace */
719 DLOG("Attaching new split to ws\n");
720 con_attach(split, ws, false);
722 /* 6: fix the percentages */
726 con_focus(old_focused);
730 * Called when a new con (with a window, not an empty or split con) should be
731 * attached to the workspace (for example when managing a new window or when
732 * moving an existing window to the workspace level).
734 * Depending on the workspace_layout setting, this function either returns the
735 * workspace itself (default layout) or creates a new stacked/tabbed con and
739 Con *workspace_attach_to(Con *ws) {
740 DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
742 if (config.default_layout == L_DEFAULT) {
743 DLOG("Default layout, just attaching it to the workspace itself.\n");
747 DLOG("Non-default layout, creating a new split container\n");
748 /* 1: create a new split container */
749 Con *new = con_new(NULL, NULL);
753 /* 2: set the requested layout on the split con */
754 new->layout = config.default_layout;
756 /* 4: attach the new split container to the workspace */
757 DLOG("Attaching new split %p to workspace %p\n", new, ws);
758 con_attach(new, ws, false);