2 * vim:ts=4:sw=4:expandtab
4 * i3 - an improved dynamic tiling window manager
5 * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
7 * workspace.c: Modifying workspaces, accessing them, moving containers to
13 /* Stores a copy of the name of the last used workspace for the workspace
14 * back-and-forth switching. */
15 static char *previous_workspace_name = NULL;
18 * Sets ws->layout to splith/splitv if default_orientation was specified in the
19 * configfile. Otherwise, it uses splith/splitv depending on whether the output
20 * is higher than wide.
23 static void _workspace_apply_default_orientation(Con *ws) {
24 /* If default_orientation is set to NO_ORIENTATION we determine
25 * orientation depending on output resolution. */
26 if (config.default_orientation == NO_ORIENTATION) {
27 Con *output = con_get_output(ws);
28 ws->layout = (output->rect.height > output->rect.width) ? L_SPLITV : L_SPLITH;
29 DLOG("Auto orientation. Workspace size set to (%d,%d), setting layout to %d.\n",
30 output->rect.width, output->rect.height, ws->layout);
32 ws->layout = (config.default_orientation == HORIZ) ? L_SPLITH : L_SPLITV;
37 * Returns a pointer to the workspace with the given number (starting at 0),
38 * creating the workspace if necessary (by allocating the necessary amount of
39 * memory and initializing the data structures correctly).
42 Con *workspace_get(const char *num, bool *created) {
43 Con *output, *workspace = NULL;
45 TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
46 GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
48 if (workspace == NULL) {
49 LOG("Creating new workspace \"%s\"\n", num);
50 /* unless an assignment is found, we will create this workspace on the current output */
51 output = con_get_output(focused);
52 /* look for assignments */
53 struct Workspace_Assignment *assignment;
54 TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
55 if (strcmp(assignment->name, num) != 0)
58 LOG("Found workspace assignment to output \"%s\"\n", assignment->output);
59 GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
62 Con *content = output_get_content(output);
63 LOG("got output %p with content %p\n", output, content);
64 /* We need to attach this container after setting its type. con_attach
65 * will handle CT_WORKSPACEs differently */
66 workspace = con_new(NULL, NULL);
68 sasprintf(&name, "[i3 con] workspace %s", num);
69 x_set_name(workspace, name);
71 workspace->type = CT_WORKSPACE;
72 FREE(workspace->name);
73 workspace->name = sstrdup(num);
74 /* We set ->num to the number if this workspace’s name begins with a
75 * positive number. Otherwise it’s a named ws and num will be -1. */
77 long parsed_num = strtol(num, &endptr, 10);
78 if (parsed_num == LONG_MIN ||
79 parsed_num == LONG_MAX ||
83 else workspace->num = parsed_num;
84 LOG("num = %d\n", workspace->num);
86 workspace->parent = content;
87 _workspace_apply_default_orientation(workspace);
89 con_attach(workspace, content, false);
91 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
95 else if (created != NULL) {
103 * Returns a pointer to a new workspace in the given output. The workspace
104 * is created attached to the tree hierarchy through the given content
108 Con *create_workspace_on_output(Output *output, Con *content) {
109 /* add a workspace to this output */
113 Con *ws = con_new(NULL, NULL);
114 ws->type = CT_WORKSPACE;
116 /* try the configured workspace bindings first to find a free name */
118 TAILQ_FOREACH(bind, bindings, bindings) {
119 DLOG("binding with command %s\n", bind->command);
120 if (strlen(bind->command) < strlen("workspace ") ||
121 strncasecmp(bind->command, "workspace", strlen("workspace")) != 0)
123 DLOG("relevant command = %s\n", bind->command);
124 char *target = bind->command + strlen("workspace ");
125 /* We check if this is the workspace
126 * next/prev/next_on_output/prev_on_output/back_and_forth/number command.
127 * Beware: The workspace names "next", "prev", "next_on_output",
128 * "prev_on_output", "number", "back_and_forth" and "current" are OK,
129 * so we check before stripping the double quotes */
130 if (strncasecmp(target, "next", strlen("next")) == 0 ||
131 strncasecmp(target, "prev", strlen("prev")) == 0 ||
132 strncasecmp(target, "next_on_output", strlen("next_on_output")) == 0 ||
133 strncasecmp(target, "prev_on_output", strlen("prev_on_output")) == 0 ||
134 strncasecmp(target, "number", strlen("number")) == 0 ||
135 strncasecmp(target, "back_and_forth", strlen("back_and_forth")) == 0 ||
136 strncasecmp(target, "current", strlen("current")) == 0)
141 ws->name = strdup(target);
142 if (ws->name[strlen(ws->name)-1] == '"')
143 ws->name[strlen(ws->name)-1] = '\0';
144 DLOG("trying name *%s*\n", ws->name);
146 /* Ensure that this workspace is not assigned to a different output —
147 * otherwise we would create it, then move it over to its output, then
148 * find a new workspace, etc… */
149 bool assigned = false;
150 struct Workspace_Assignment *assignment;
151 TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
152 if (strcmp(assignment->name, ws->name) != 0 ||
153 strcmp(assignment->output, output->name) == 0)
164 TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
165 GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
167 exists = (current != NULL);
169 /* Set ->num to the number of the workspace, if the name actually
170 * is a number or starts with a number */
172 long parsed_num = strtol(ws->name, &endptr, 10);
173 if (parsed_num == LONG_MIN ||
174 parsed_num == LONG_MAX ||
178 else ws->num = parsed_num;
179 LOG("Used number %d for workspace with name %s\n", ws->num, ws->name);
186 /* get the next unused workspace number */
187 DLOG("Getting next unused workspace by number\n");
193 sasprintf(&(ws->name), "%d", c);
196 TAILQ_FOREACH(out, &(croot->nodes_head), nodes)
197 GREP_FIRST(current, output_get_content(out), !strcasecmp(child->name, ws->name));
198 exists = (current != NULL);
200 DLOG("result for ws %s / %d: exists = %d\n", ws->name, c, exists);
204 con_attach(ws, content, false);
206 sasprintf(&name, "[i3 con] workspace %s", ws->name);
207 x_set_name(ws, name);
210 ws->fullscreen_mode = CF_OUTPUT;
212 _workspace_apply_default_orientation(ws);
219 * Returns true if the workspace is currently visible. Especially important for
220 * multi-monitor environments, as they can have multiple currenlty active
224 bool workspace_is_visible(Con *ws) {
225 Con *output = con_get_output(ws);
228 Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
229 LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
234 * XXX: we need to clean up all this recursive walking code.
237 Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
240 TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
241 if (current != exclude &&
242 current->sticky_group != NULL &&
243 current->window != NULL &&
244 strcmp(current->sticky_group, sticky_group) == 0)
247 Con *recurse = _get_sticky(current, sticky_group, exclude);
252 TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
253 if (current != exclude &&
254 current->sticky_group != NULL &&
255 current->window != NULL &&
256 strcmp(current->sticky_group, sticky_group) == 0)
259 Con *recurse = _get_sticky(current, sticky_group, exclude);
268 * Reassigns all child windows in sticky containers. Called when the user
269 * changes workspaces.
271 * XXX: what about sticky containers which contain containers?
274 static void workspace_reassign_sticky(Con *con) {
276 /* 1: go through all containers */
278 /* handle all children and floating windows of this node */
279 TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
280 if (current->sticky_group == NULL) {
281 workspace_reassign_sticky(current);
285 LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
286 /* 2: find a window which we can re-assign */
287 Con *output = con_get_output(current);
288 Con *src = _get_sticky(output, current->sticky_group, current);
291 LOG("No window found for this sticky group\n");
292 workspace_reassign_sticky(current);
296 x_move_win(src, current);
297 current->window = src->window;
298 current->mapped = true;
302 x_reparent_child(current, src);
304 LOG("re-assigned window from src %p to dest %p\n", src, current);
307 TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
308 workspace_reassign_sticky(current);
312 static void _workspace_show(Con *workspace) {
313 Con *current, *old = NULL;
315 /* safe-guard against showing i3-internal workspaces like __i3_scratch */
316 if (workspace->name[0] == '_' && workspace->name[1] == '_')
319 /* disable fullscreen for the other workspaces and get the workspace we are
321 TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
322 if (current->fullscreen_mode == CF_OUTPUT)
324 current->fullscreen_mode = CF_NONE;
327 /* enable fullscreen for the target workspace. If it happens to be the
328 * same one we are currently on anyways, we can stop here. */
329 workspace->fullscreen_mode = CF_OUTPUT;
330 current = con_get_workspace(focused);
331 if (workspace == current) {
332 DLOG("Not switching, already there.\n");
336 /* Remember currently focused workspace for switching back to it later with
337 * the 'workspace back_and_forth' command.
338 * NOTE: We have to duplicate the name as the original will be freed when
339 * the corresponding workspace is cleaned up. */
341 FREE(previous_workspace_name);
343 previous_workspace_name = sstrdup(current->name);
345 workspace_reassign_sticky(workspace);
347 LOG("switching to %p\n", workspace);
348 Con *next = con_descend_focused(workspace);
350 if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
351 /* check if this workspace is currently visible */
352 if (!workspace_is_visible(old)) {
353 LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
354 tree_close(old, DONT_KILL_WINDOW, false, false);
355 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
359 /* Memorize current output */
360 Con *old_output = con_get_output(focused);
363 workspace->fullscreen_mode = CF_OUTPUT;
364 LOG("focused now = %p / %s\n", focused, focused->name);
366 /* Set mouse pointer */
367 Con *new_output = con_get_output(focused);
368 if (old_output != new_output) {
369 x_set_warp_to(&next->rect);
372 /* Update the EWMH hints */
373 ewmh_update_current_desktop();
375 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
379 * Switches to the given workspace
382 void workspace_show(Con *workspace) {
383 _workspace_show(workspace);
387 * Looks up the workspace by name and switches to it.
390 void workspace_show_by_name(const char *num) {
392 bool changed_num_workspaces;
393 workspace = workspace_get(num, &changed_num_workspaces);
394 _workspace_show(workspace);
398 * Focuses the next workspace.
401 Con* workspace_next(void) {
402 Con *current = con_get_workspace(focused);
406 if (current->num == -1) {
407 /* If currently a named workspace, find next named workspace. */
408 next = TAILQ_NEXT(current, nodes);
410 /* If currently a numbered workspace, find next numbered workspace. */
411 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
412 /* Skip outputs starting with __, they are internal. */
413 if (output->name[0] == '_' && output->name[1] == '_')
415 NODES_FOREACH(output_get_content(output)) {
416 if (child->type != CT_WORKSPACE)
418 if (child->num == -1)
420 /* Need to check child against current and next because we are
421 * traversing multiple lists and thus are not guaranteed the
422 * relative order between the list of workspaces. */
423 if (current->num < child->num && (!next || child->num < next->num))
429 /* Find next named workspace. */
431 bool found_current = false;
432 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
433 /* Skip outputs starting with __, they are internal. */
434 if (output->name[0] == '_' && output->name[1] == '_')
436 NODES_FOREACH(output_get_content(output)) {
437 if (child->type != CT_WORKSPACE)
439 if (child == current) {
441 } else if (child->num == -1 && (current->num != -1 || found_current)) {
443 goto workspace_next_end;
449 /* Find first workspace. */
451 TAILQ_FOREACH(output, &(croot->nodes_head), nodes) {
452 /* Skip outputs starting with __, they are internal. */
453 if (output->name[0] == '_' && output->name[1] == '_')
455 NODES_FOREACH(output_get_content(output)) {
456 if (child->type != CT_WORKSPACE)
458 if (!next || (child->num != -1 && child->num < next->num))
468 * Focuses the previous workspace.
471 Con* workspace_prev(void) {
472 Con *current = con_get_workspace(focused);
476 if (current->num == -1) {
477 /* If named workspace, find previous named workspace. */
478 prev = TAILQ_PREV(current, nodes_head, nodes);
479 if (prev && prev->num != -1)
482 /* If numbered workspace, find previous numbered workspace. */
483 TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
484 /* Skip outputs starting with __, they are internal. */
485 if (output->name[0] == '_' && output->name[1] == '_')
487 NODES_FOREACH_REVERSE(output_get_content(output)) {
488 if (child->type != CT_WORKSPACE || child->num == -1)
490 /* Need to check child against current and previous because we
491 * are traversing multiple lists and thus are not guaranteed
492 * the relative order between the list of workspaces. */
493 if (current->num > child->num && (!prev || child->num > prev->num))
499 /* Find previous named workspace. */
501 bool found_current = false;
502 TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
503 /* Skip outputs starting with __, they are internal. */
504 if (output->name[0] == '_' && output->name[1] == '_')
506 NODES_FOREACH_REVERSE(output_get_content(output)) {
507 if (child->type != CT_WORKSPACE)
509 if (child == current) {
510 found_current = true;
511 } else if (child->num == -1 && (current->num != -1 || found_current)) {
513 goto workspace_prev_end;
519 /* Find last workspace. */
521 TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes) {
522 /* Skip outputs starting with __, they are internal. */
523 if (output->name[0] == '_' && output->name[1] == '_')
525 NODES_FOREACH_REVERSE(output_get_content(output)) {
526 if (child->type != CT_WORKSPACE)
528 if (!prev || child->num > prev->num)
540 * Focuses the next workspace on the same output.
543 Con* workspace_next_on_output(void) {
544 Con *current = con_get_workspace(focused);
546 Con *output = con_get_output(focused);
548 if (current->num == -1) {
549 /* If currently a named workspace, find next named workspace. */
550 next = TAILQ_NEXT(current, nodes);
552 /* If currently a numbered workspace, find next numbered workspace. */
553 NODES_FOREACH(output_get_content(output)) {
554 if (child->type != CT_WORKSPACE)
556 if (child->num == -1)
558 /* Need to check child against current and next because we are
559 * traversing multiple lists and thus are not guaranteed the
560 * relative order between the list of workspaces. */
561 if (current->num < child->num && (!next || child->num < next->num))
566 /* Find next named workspace. */
568 bool found_current = false;
569 NODES_FOREACH(output_get_content(output)) {
570 if (child->type != CT_WORKSPACE)
572 if (child == current) {
574 } else if (child->num == -1 && (current->num != -1 || found_current)) {
576 goto workspace_next_on_output_end;
581 /* Find first workspace. */
583 NODES_FOREACH(output_get_content(output)) {
584 if (child->type != CT_WORKSPACE)
586 if (!next || (child->num != -1 && child->num < next->num))
590 workspace_next_on_output_end:
595 * Focuses the previous workspace on same output.
598 Con* workspace_prev_on_output(void) {
599 Con *current = con_get_workspace(focused);
601 Con *output = con_get_output(focused);
602 DLOG("output = %s\n", output->name);
604 if (current->num == -1) {
605 /* If named workspace, find previous named workspace. */
606 prev = TAILQ_PREV(current, nodes_head, nodes);
607 if (prev && prev->num != -1)
610 /* If numbered workspace, find previous numbered workspace. */
611 NODES_FOREACH_REVERSE(output_get_content(output)) {
612 if (child->type != CT_WORKSPACE || child->num == -1)
614 /* Need to check child against current and previous because we
615 * are traversing multiple lists and thus are not guaranteed
616 * the relative order between the list of workspaces. */
617 if (current->num > child->num && (!prev || child->num > prev->num))
622 /* Find previous named workspace. */
624 bool found_current = false;
625 NODES_FOREACH_REVERSE(output_get_content(output)) {
626 if (child->type != CT_WORKSPACE)
628 if (child == current) {
629 found_current = true;
630 } else if (child->num == -1 && (current->num != -1 || found_current)) {
632 goto workspace_prev_on_output_end;
637 /* Find last workspace. */
639 NODES_FOREACH_REVERSE(output_get_content(output)) {
640 if (child->type != CT_WORKSPACE)
642 if (!prev || child->num > prev->num)
647 workspace_prev_on_output_end:
652 * Focuses the previously focused workspace.
655 void workspace_back_and_forth(void) {
656 if (!previous_workspace_name) {
657 DLOG("No previous workspace name set. Not switching.");
661 workspace_show_by_name(previous_workspace_name);
664 static bool get_urgency_flag(Con *con) {
666 TAILQ_FOREACH(child, &(con->nodes_head), nodes)
667 if (child->urgent || get_urgency_flag(child))
670 TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
671 if (child->urgent || get_urgency_flag(child))
678 * Goes through all clients on the given workspace and updates the workspace’s
679 * urgent flag accordingly.
682 void workspace_update_urgent_flag(Con *ws) {
683 bool old_flag = ws->urgent;
684 ws->urgent = get_urgency_flag(ws);
685 DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
687 if (old_flag != ws->urgent)
688 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
692 * 'Forces' workspace orientation by moving all cons into a new split-con with
693 * the same layout as the workspace and then changing the workspace layout.
696 void ws_force_orientation(Con *ws, orientation_t orientation) {
697 /* 1: create a new split container */
698 Con *split = con_new(NULL, NULL);
701 /* 2: copy layout from workspace */
702 split->layout = ws->layout;
704 Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
706 /* 3: move the existing cons of this workspace below the new con */
707 DLOG("Moving cons\n");
708 while (!TAILQ_EMPTY(&(ws->nodes_head))) {
709 Con *child = TAILQ_FIRST(&(ws->nodes_head));
711 con_attach(child, split, true);
714 /* 4: switch workspace layout */
715 ws->layout = (orientation == HORIZ) ? L_SPLITH : L_SPLITV;
717 /* 5: attach the new split container to the workspace */
718 DLOG("Attaching new split to ws\n");
719 con_attach(split, ws, false);
721 /* 6: fix the percentages */
725 con_focus(old_focused);
729 * Called when a new con (with a window, not an empty or split con) should be
730 * attached to the workspace (for example when managing a new window or when
731 * moving an existing window to the workspace level).
733 * Depending on the workspace_layout setting, this function either returns the
734 * workspace itself (default layout) or creates a new stacked/tabbed con and
738 Con *workspace_attach_to(Con *ws) {
739 DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
741 if (config.default_layout == L_DEFAULT) {
742 DLOG("Default layout, just attaching it to the workspace itself.\n");
746 DLOG("Non-default layout, creating a new split container\n");
747 /* 1: create a new split container */
748 Con *new = con_new(NULL, NULL);
752 /* 2: set the requested layout on the split con */
753 new->layout = config.default_layout;
755 /* 4: attach the new split container to the workspace */
756 DLOG("Attaching new split %p to workspace %p\n", new, ws);
757 con_attach(new, ws, false);