i3
src/workspace.c
Go to the documentation of this file.
00001 /*
00002  * vim:ts=4:sw=4:expandtab
00003  *
00004  * i3 - an improved dynamic tiling window manager
00005  * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
00006  *
00007  * workspace.c: Modifying workspaces, accessing them, moving containers to
00008  *              workspaces.
00009  *
00010  */
00011 #include "all.h"
00012 
00013 /* Stores a copy of the name of the last used workspace for the workspace
00014  * back-and-forth switching. */
00015 static char *previous_workspace_name = NULL;
00016 
00017 /*
00018  * Returns a pointer to the workspace with the given number (starting at 0),
00019  * creating the workspace if necessary (by allocating the necessary amount of
00020  * memory and initializing the data structures correctly).
00021  *
00022  */
00023 Con *workspace_get(const char *num, bool *created) {
00024     Con *output, *workspace = NULL;
00025 
00026     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
00027         GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
00028 
00029     if (workspace == NULL) {
00030         LOG("Creating new workspace \"%s\"\n", num);
00031         /* unless an assignment is found, we will create this workspace on the current output */
00032         output = con_get_output(focused);
00033         /* look for assignments */
00034         struct Workspace_Assignment *assignment;
00035         TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
00036             if (strcmp(assignment->name, num) != 0)
00037                 continue;
00038 
00039             LOG("Found workspace assignment to output \"%s\"\n", assignment->output);
00040             GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
00041             break;
00042         }
00043         Con *content = output_get_content(output);
00044         LOG("got output %p with content %p\n", output, content);
00045         /* We need to attach this container after setting its type. con_attach
00046          * will handle CT_WORKSPACEs differently */
00047         workspace = con_new(NULL, NULL);
00048         char *name;
00049         sasprintf(&name, "[i3 con] workspace %s", num);
00050         x_set_name(workspace, name);
00051         free(name);
00052         workspace->type = CT_WORKSPACE;
00053         FREE(workspace->name);
00054         workspace->name = sstrdup(num);
00055         /* We set ->num to the number if this workspace’s name consists only of
00056          * a positive number. Otherwise it’s a named ws and num will be -1. */
00057         char *endptr = NULL;
00058         long parsed_num = strtol(num, &endptr, 10);
00059         if (parsed_num == LONG_MIN ||
00060             parsed_num == LONG_MAX ||
00061             parsed_num < 0 ||
00062             endptr == num)
00063             workspace->num = -1;
00064         else workspace->num = parsed_num;
00065         LOG("num = %d\n", workspace->num);
00066 
00067         /* If default_orientation is set to NO_ORIENTATION we
00068          * determine workspace orientation from workspace size.
00069          * Otherwise we just set the orientation to default_orientation. */
00070         if (config.default_orientation == NO_ORIENTATION) {
00071             workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
00072             DLOG("Auto orientation. Output resolution set to (%d,%d), setting orientation to %d.\n",
00073                  workspace->rect.width, workspace->rect.height, workspace->orientation);
00074         } else {
00075             workspace->orientation = config.default_orientation;
00076         }
00077 
00078         con_attach(workspace, content, false);
00079 
00080         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
00081         if (created != NULL)
00082             *created = true;
00083     }
00084     else if (created != NULL) {
00085         *created = false;
00086     }
00087 
00088     return workspace;
00089 }
00090 
00091 /*
00092  * Returns true if the workspace is currently visible. Especially important for
00093  * multi-monitor environments, as they can have multiple currenlty active
00094  * workspaces.
00095  *
00096  */
00097 bool workspace_is_visible(Con *ws) {
00098     Con *output = con_get_output(ws);
00099     if (output == NULL)
00100         return false;
00101     Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
00102     LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
00103     return (fs == ws);
00104 }
00105 
00106 /*
00107  * XXX: we need to clean up all this recursive walking code.
00108  *
00109  */
00110 Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
00111     Con *current;
00112 
00113     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
00114         if (current != exclude &&
00115             current->sticky_group != NULL &&
00116             current->window != NULL &&
00117             strcmp(current->sticky_group, sticky_group) == 0)
00118             return current;
00119 
00120         Con *recurse = _get_sticky(current, sticky_group, exclude);
00121         if (recurse != NULL)
00122             return recurse;
00123     }
00124 
00125     TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
00126         if (current != exclude &&
00127             current->sticky_group != NULL &&
00128             current->window != NULL &&
00129             strcmp(current->sticky_group, sticky_group) == 0)
00130             return current;
00131 
00132         Con *recurse = _get_sticky(current, sticky_group, exclude);
00133         if (recurse != NULL)
00134             return recurse;
00135     }
00136 
00137     return NULL;
00138 }
00139 
00140 /*
00141  * Reassigns all child windows in sticky containers. Called when the user
00142  * changes workspaces.
00143  *
00144  * XXX: what about sticky containers which contain containers?
00145  *
00146  */
00147 static void workspace_reassign_sticky(Con *con) {
00148     Con *current;
00149     /* 1: go through all containers */
00150 
00151     /* handle all children and floating windows of this node */
00152     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
00153         if (current->sticky_group == NULL) {
00154             workspace_reassign_sticky(current);
00155             continue;
00156         }
00157 
00158         LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
00159         /* 2: find a window which we can re-assign */
00160         Con *output = con_get_output(current);
00161         Con *src = _get_sticky(output, current->sticky_group, current);
00162 
00163         if (src == NULL) {
00164             LOG("No window found for this sticky group\n");
00165             workspace_reassign_sticky(current);
00166             continue;
00167         }
00168 
00169         x_move_win(src, current);
00170         current->window = src->window;
00171         current->mapped = true;
00172         src->window = NULL;
00173         src->mapped = false;
00174 
00175         x_reparent_child(current, src);
00176 
00177         LOG("re-assigned window from src %p to dest %p\n", src, current);
00178     }
00179 
00180     TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
00181         workspace_reassign_sticky(current);
00182 }
00183 
00184 
00185 static void _workspace_show(Con *workspace, bool changed_num_workspaces) {
00186     Con *current, *old = NULL;
00187 
00188     /* disable fullscreen for the other workspaces and get the workspace we are
00189      * currently on. */
00190     TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
00191         if (current->fullscreen_mode == CF_OUTPUT)
00192             old = current;
00193         current->fullscreen_mode = CF_NONE;
00194     }
00195 
00196     /* enable fullscreen for the target workspace. If it happens to be the
00197      * same one we are currently on anyways, we can stop here. */
00198     workspace->fullscreen_mode = CF_OUTPUT;
00199     current = con_get_workspace(focused);
00200     if (workspace == current) {
00201         DLOG("Not switching, already there.\n");
00202         return;
00203     }
00204 
00205     /* Remember currently focused workspace for switching back to it later with
00206      * the 'workspace back_and_forth' command.
00207      * NOTE: We have to duplicate the name as the original will be freed when
00208      * the corresponding workspace is cleaned up. */
00209 
00210     FREE(previous_workspace_name);
00211     if (current)
00212         previous_workspace_name = sstrdup(current->name);
00213 
00214     workspace_reassign_sticky(workspace);
00215 
00216     LOG("switching to %p\n", workspace);
00217     Con *next = con_descend_focused(workspace);
00218 
00219     if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
00220         /* check if this workspace is currently visible */
00221         if (!workspace_is_visible(old)) {
00222             LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
00223             tree_close(old, DONT_KILL_WINDOW, false, false);
00224             ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
00225             changed_num_workspaces = true;
00226         }
00227     }
00228 
00229     /* Memorize current output */
00230     Con *old_output = con_get_output(focused);
00231 
00232     con_focus(next);
00233     workspace->fullscreen_mode = CF_OUTPUT;
00234     LOG("focused now = %p / %s\n", focused, focused->name);
00235 
00236     /* Set mouse pointer */
00237     Con *new_output = con_get_output(focused);
00238     if (old_output != new_output) {
00239         x_set_warp_to(&next->rect);
00240     }
00241 
00242     /* Update the EWMH hints */
00243     ewmh_update_current_desktop();
00244 
00245     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
00246 }
00247 
00248 /*
00249  * Switches to the given workspace
00250  *
00251  */
00252 void workspace_show(Con *workspace) {
00253     _workspace_show(workspace, false);
00254 }
00255 
00256 /*
00257  * Looks up the workspace by name and switches to it.
00258  *
00259  */
00260 void workspace_show_by_name(const char *num) {
00261     Con *workspace;
00262     bool changed_num_workspaces;
00263     workspace = workspace_get(num, &changed_num_workspaces);
00264     _workspace_show(workspace, changed_num_workspaces);
00265 }
00266 
00267 /*
00268  * Focuses the next workspace.
00269  *
00270  */
00271 Con* workspace_next() {
00272     Con *current = con_get_workspace(focused);
00273     Con *next = NULL;
00274     Con *output;
00275 
00276     if (current->num == -1) {
00277         /* If currently a named workspace, find next named workspace. */
00278         next = TAILQ_NEXT(current, nodes);
00279     } else {
00280         /* If currently a numbered workspace, find next numbered workspace. */
00281         TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
00282             NODES_FOREACH(output_get_content(output)) {
00283                 if (child->type != CT_WORKSPACE)
00284                     continue;
00285                 if (child->num == -1)
00286                     break;
00287                 /* Need to check child against current and next because we are
00288                  * traversing multiple lists and thus are not guaranteed the
00289                  * relative order between the list of workspaces. */
00290                 if (current->num < child->num && (!next || child->num < next->num))
00291                     next = child;
00292             }
00293     }
00294 
00295     /* Find next named workspace. */
00296     if (!next) {
00297         bool found_current = false;
00298         TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
00299             NODES_FOREACH(output_get_content(output)) {
00300                 if (child->type != CT_WORKSPACE)
00301                     continue;
00302                 if (child == current) {
00303                     found_current = 1;
00304                 } else if (child->num == -1 && (current->num != -1 || found_current)) {
00305                     next = child;
00306                     goto workspace_next_end;
00307                 }
00308             }
00309     }
00310 
00311     /* Find first workspace. */
00312     if (!next) {
00313         TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
00314             NODES_FOREACH(output_get_content(output)) {
00315                 if (child->type != CT_WORKSPACE)
00316                     continue;
00317                 if (!next || (child->num != -1 && child->num < next->num))
00318                     next = child;
00319             }
00320     }
00321 workspace_next_end:
00322     return next;
00323 }
00324 
00325 /*
00326  * Focuses the previous workspace.
00327  *
00328  */
00329 Con* workspace_prev() {
00330     Con *current = con_get_workspace(focused);
00331     Con *prev = NULL;
00332     Con *output;
00333 
00334     if (current->num == -1) {
00335         /* If named workspace, find previous named workspace. */
00336         prev = TAILQ_PREV(current, nodes_head, nodes);
00337         if (prev && prev->num != -1)
00338             prev = NULL;
00339     } else {
00340         /* If numbered workspace, find previous numbered workspace. */
00341         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes)
00342             NODES_FOREACH_REVERSE(output_get_content(output)) {
00343                 if (child->type != CT_WORKSPACE || child->num == -1)
00344                     continue;
00345                 /* Need to check child against current and previous because we
00346                  * are traversing multiple lists and thus are not guaranteed
00347                  * the relative order between the list of workspaces. */
00348                 if (current->num > child->num && (!prev || child->num > prev->num))
00349                     prev = child;
00350             }
00351     }
00352 
00353     /* Find previous named workspace. */
00354     if (!prev) {
00355         bool found_current = false;
00356         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes)
00357             NODES_FOREACH_REVERSE(output_get_content(output)) {
00358                 if (child->type != CT_WORKSPACE)
00359                     continue;
00360                 if (child == current) {
00361                     found_current = true;
00362                 } else if (child->num == -1 && (current->num != -1 || found_current)) {
00363                     prev = child;
00364                     goto workspace_prev_end;
00365                 }
00366             }
00367     }
00368 
00369     /* Find last workspace. */
00370     if (!prev) {
00371         TAILQ_FOREACH_REVERSE(output, &(croot->nodes_head), nodes_head, nodes)
00372             NODES_FOREACH_REVERSE(output_get_content(output)) {
00373                 if (child->type != CT_WORKSPACE)
00374                     continue;
00375                 if (!prev || child->num > prev->num)
00376                     prev = child;
00377             }
00378     }
00379 
00380 workspace_prev_end:
00381     return prev;
00382 }
00383 
00384 /*
00385  * Focuses the previously focused workspace.
00386  *
00387  */
00388 void workspace_back_and_forth() {
00389     if (!previous_workspace_name) {
00390         DLOG("No previous workspace name set. Not switching.");
00391         return;
00392     }
00393 
00394     workspace_show_by_name(previous_workspace_name);
00395 }
00396 
00397 static bool get_urgency_flag(Con *con) {
00398     Con *child;
00399     TAILQ_FOREACH(child, &(con->nodes_head), nodes)
00400         if (child->urgent || get_urgency_flag(child))
00401             return true;
00402 
00403     TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
00404         if (child->urgent || get_urgency_flag(child))
00405             return true;
00406 
00407     return false;
00408 }
00409 
00410 /*
00411  * Goes through all clients on the given workspace and updates the workspace’s
00412  * urgent flag accordingly.
00413  *
00414  */
00415 void workspace_update_urgent_flag(Con *ws) {
00416     bool old_flag = ws->urgent;
00417     ws->urgent = get_urgency_flag(ws);
00418     DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
00419 
00420     if (old_flag != ws->urgent)
00421         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
00422 }
00423 
00424 /*
00425  * 'Forces' workspace orientation by moving all cons into a new split-con with
00426  * the same orientation as the workspace and then changing the workspace
00427  * orientation.
00428  *
00429  */
00430 void ws_force_orientation(Con *ws, orientation_t orientation) {
00431     /* 1: create a new split container */
00432     Con *split = con_new(NULL, NULL);
00433     split->parent = ws;
00434 
00435     /* 2: copy layout and orientation from workspace */
00436     split->layout = ws->layout;
00437     split->orientation = ws->orientation;
00438 
00439     Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
00440 
00441     /* 3: move the existing cons of this workspace below the new con */
00442     DLOG("Moving cons\n");
00443     while (!TAILQ_EMPTY(&(ws->nodes_head))) {
00444         Con *child = TAILQ_FIRST(&(ws->nodes_head));
00445         con_detach(child);
00446         con_attach(child, split, true);
00447     }
00448 
00449     /* 4: switch workspace orientation */
00450     ws->orientation = orientation;
00451 
00452     /* 5: attach the new split container to the workspace */
00453     DLOG("Attaching new split to ws\n");
00454     con_attach(split, ws, false);
00455 
00456     /* 6: fix the percentages */
00457     con_fix_percent(ws);
00458 
00459     if (old_focused)
00460         con_focus(old_focused);
00461 }
00462 
00463 /*
00464  * Called when a new con (with a window, not an empty or split con) should be
00465  * attached to the workspace (for example when managing a new window or when
00466  * moving an existing window to the workspace level).
00467  *
00468  * Depending on the workspace_layout setting, this function either returns the
00469  * workspace itself (default layout) or creates a new stacked/tabbed con and
00470  * returns that.
00471  *
00472  */
00473 Con *workspace_attach_to(Con *ws) {
00474     DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
00475 
00476     if (config.default_layout == L_DEFAULT) {
00477         DLOG("Default layout, just attaching it to the workspace itself.\n");
00478         return ws;
00479     }
00480 
00481     DLOG("Non-default layout, creating a new split container\n");
00482     /* 1: create a new split container */
00483     Con *new = con_new(NULL, NULL);
00484     new->parent = ws;
00485 
00486     /* 2: set the requested layout on the split con */
00487     new->layout = config.default_layout;
00488 
00489     /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
00490      * to be set. Otherwise, this con will not be interpreted as a split
00491      * container. */
00492     if (config.default_orientation == NO_ORIENTATION) {
00493         new->orientation = (ws->rect.height > ws->rect.width) ? VERT : HORIZ;
00494     } else {
00495         new->orientation = config.default_orientation;
00496     }
00497 
00498     /* 4: attach the new split container to the workspace */
00499     DLOG("Attaching new split %p to workspace %p\n", new, ws);
00500     con_attach(new, ws, false);
00501 
00502     return new;
00503 }