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-2010 Michael Stapelberg and contributors (see also: LICENSE)
00006  *
00007  * workspace.c: Functions for modifying workspaces
00008  *
00009  */
00010 #include <limits.h>
00011 
00012 #include "all.h"
00013 
00014 /*
00015  * Returns a pointer to the workspace with the given number (starting at 0),
00016  * creating the workspace if necessary (by allocating the necessary amount of
00017  * memory and initializing the data structures correctly).
00018  *
00019  */
00020 Con *workspace_get(const char *num, bool *created) {
00021     Con *output, *workspace = NULL;
00022 
00023     TAILQ_FOREACH(output, &(croot->nodes_head), nodes)
00024         GREP_FIRST(workspace, output_get_content(output), !strcasecmp(child->name, num));
00025 
00026     if (workspace == NULL) {
00027         LOG("Creating new workspace \"%s\"\n", num);
00028         /* unless an assignment is found, we will create this workspace on the current output */
00029         output = con_get_output(focused);
00030         /* look for assignments */
00031         struct Workspace_Assignment *assignment;
00032         TAILQ_FOREACH(assignment, &ws_assignments, ws_assignments) {
00033             if (strcmp(assignment->name, num) != 0)
00034                 continue;
00035 
00036             LOG("Found workspace assignment to output \"%s\"\n", assignment->output);
00037             GREP_FIRST(output, croot, !strcmp(child->name, assignment->output));
00038             break;
00039         }
00040         Con *content = output_get_content(output);
00041         LOG("got output %p with content %p\n", output, content);
00042         /* We need to attach this container after setting its type. con_attach
00043          * will handle CT_WORKSPACEs differently */
00044         workspace = con_new(NULL, NULL);
00045         char *name;
00046         asprintf(&name, "[i3 con] workspace %s", num);
00047         x_set_name(workspace, name);
00048         free(name);
00049         workspace->type = CT_WORKSPACE;
00050         FREE(workspace->name);
00051         workspace->name = sstrdup(num);
00052         /* We set ->num to the number if this workspace’s name consists only of
00053          * a positive number. Otherwise it’s a named ws and num will be -1. */
00054         char *end;
00055         long parsed_num = strtol(num, &end, 10);
00056         if (parsed_num == LONG_MIN ||
00057             parsed_num == LONG_MAX ||
00058             parsed_num < 0 ||
00059             (end && *end != '\0'))
00060             workspace->num = -1;
00061         else workspace->num = parsed_num;
00062         LOG("num = %d\n", workspace->num);
00063 
00064         /* If default_orientation is set to NO_ORIENTATION we
00065          * determine workspace orientation from workspace size.
00066          * Otherwise we just set the orientation to default_orientation. */
00067         if (config.default_orientation == NO_ORIENTATION) {
00068             workspace->orientation = (output->rect.height > output->rect.width) ? VERT : HORIZ;
00069             DLOG("Auto orientation. Output resolution set to (%d,%d), setting orientation to %d.\n",
00070                  workspace->rect.width, workspace->rect.height, workspace->orientation);
00071         } else {
00072             workspace->orientation = config.default_orientation;
00073         }
00074 
00075         con_attach(workspace, content, false);
00076 
00077         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"init\"}");
00078         if (created != NULL)
00079             *created = true;
00080     }
00081     else if (created != NULL) {
00082         *created = false;
00083     }
00084 
00085     return workspace;
00086 }
00087 
00088 /*
00089  * Returns true if the workspace is currently visible. Especially important for
00090  * multi-monitor environments, as they can have multiple currenlty active
00091  * workspaces.
00092  *
00093  */
00094 bool workspace_is_visible(Con *ws) {
00095     Con *output = con_get_output(ws);
00096     if (output == NULL)
00097         return false;
00098     Con *fs = con_get_fullscreen_con(output, CF_OUTPUT);
00099     LOG("workspace visible? fs = %p, ws = %p\n", fs, ws);
00100     return (fs == ws);
00101 }
00102 
00103 /*
00104  * XXX: we need to clean up all this recursive walking code.
00105  *
00106  */
00107 Con *_get_sticky(Con *con, const char *sticky_group, Con *exclude) {
00108     Con *current;
00109 
00110     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
00111         if (current != exclude &&
00112             current->sticky_group != NULL &&
00113             current->window != NULL &&
00114             strcmp(current->sticky_group, sticky_group) == 0)
00115             return current;
00116 
00117         Con *recurse = _get_sticky(current, sticky_group, exclude);
00118         if (recurse != NULL)
00119             return recurse;
00120     }
00121 
00122     TAILQ_FOREACH(current, &(con->floating_head), floating_windows) {
00123         if (current != exclude &&
00124             current->sticky_group != NULL &&
00125             current->window != NULL &&
00126             strcmp(current->sticky_group, sticky_group) == 0)
00127             return current;
00128 
00129         Con *recurse = _get_sticky(current, sticky_group, exclude);
00130         if (recurse != NULL)
00131             return recurse;
00132     }
00133 
00134     return NULL;
00135 }
00136 
00137 /*
00138  * Reassigns all child windows in sticky containers. Called when the user
00139  * changes workspaces.
00140  *
00141  * XXX: what about sticky containers which contain containers?
00142  *
00143  */
00144 static void workspace_reassign_sticky(Con *con) {
00145     Con *current;
00146     /* 1: go through all containers */
00147 
00148     /* handle all children and floating windows of this node */
00149     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
00150         if (current->sticky_group == NULL) {
00151             workspace_reassign_sticky(current);
00152             continue;
00153         }
00154 
00155         LOG("Ah, this one is sticky: %s / %p\n", current->name, current);
00156         /* 2: find a window which we can re-assign */
00157         Con *output = con_get_output(current);
00158         Con *src = _get_sticky(output, current->sticky_group, current);
00159 
00160         if (src == NULL) {
00161             LOG("No window found for this sticky group\n");
00162             workspace_reassign_sticky(current);
00163             continue;
00164         }
00165 
00166         x_move_win(src, current);
00167         current->window = src->window;
00168         current->mapped = true;
00169         src->window = NULL;
00170         src->mapped = false;
00171 
00172         x_reparent_child(current, src);
00173 
00174         LOG("re-assigned window from src %p to dest %p\n", src, current);
00175     }
00176 
00177     TAILQ_FOREACH(current, &(con->floating_head), floating_windows)
00178         workspace_reassign_sticky(current);
00179 }
00180 
00181 /*
00182  * Switches to the given workspace
00183  *
00184  */
00185 void workspace_show(const char *num) {
00186     Con *workspace, *current, *old = NULL;
00187 
00188     bool changed_num_workspaces;
00189     workspace = workspace_get(num, &changed_num_workspaces);
00190 
00191     /* disable fullscreen for the other workspaces and get the workspace we are
00192      * currently on. */
00193     TAILQ_FOREACH(current, &(workspace->parent->nodes_head), nodes) {
00194         if (current->fullscreen_mode == CF_OUTPUT)
00195             old = current;
00196         current->fullscreen_mode = CF_NONE;
00197     }
00198 
00199     /* enable fullscreen for the target workspace. If it happens to be the
00200      * same one we are currently on anyways, we can stop here. */
00201     workspace->fullscreen_mode = CF_OUTPUT;
00202     if (workspace == con_get_workspace(focused)) {
00203         DLOG("Not switching, already there.\n");
00204         return;
00205     }
00206 
00207     workspace_reassign_sticky(workspace);
00208 
00209     LOG("switching to %p\n", workspace);
00210     Con *next = con_descend_focused(workspace);
00211 
00212     if (old && TAILQ_EMPTY(&(old->nodes_head)) && TAILQ_EMPTY(&(old->floating_head))) {
00213         /* check if this workspace is currently visible */
00214         if (!workspace_is_visible(old)) {
00215             LOG("Closing old workspace (%p / %s), it is empty\n", old, old->name);
00216             tree_close(old, DONT_KILL_WINDOW, false);
00217             ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
00218             changed_num_workspaces = true;
00219         }
00220     }
00221 
00222     con_focus(next);
00223     workspace->fullscreen_mode = CF_OUTPUT;
00224     LOG("focused now = %p / %s\n", focused, focused->name);
00225 
00226     /* Update the EWMH hints */
00227     if (changed_num_workspaces)
00228         ewmh_update_workarea();
00229     ewmh_update_current_desktop();
00230 
00231     ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"focus\"}");
00232 }
00233 
00234 /*
00235  * Focuses the next workspace.
00236  *
00237  */
00238 void workspace_next() {
00239     Con *ws = con_get_workspace(focused);
00240     Con *next = TAILQ_NEXT(ws, nodes);
00241     if (!next)
00242         next = TAILQ_FIRST(&(ws->parent->nodes_head));
00243 
00244     workspace_show(next->name);
00245 }
00246 
00247 /*
00248  * Focuses the previous workspace.
00249  *
00250  */
00251 void workspace_prev() {
00252     Con *ws = con_get_workspace(focused);
00253     Con *prev = TAILQ_PREV(ws, nodes_head, nodes);
00254     if (!prev)
00255         prev = TAILQ_LAST(&(ws->parent->nodes_head), nodes_head);
00256 
00257     workspace_show(prev->name);
00258 }
00259 
00260 static bool get_urgency_flag(Con *con) {
00261     Con *child;
00262     TAILQ_FOREACH(child, &(con->nodes_head), nodes)
00263         if (child->urgent || get_urgency_flag(child))
00264             return true;
00265 
00266     TAILQ_FOREACH(child, &(con->floating_head), floating_windows)
00267         if (child->urgent || get_urgency_flag(child))
00268             return true;
00269 
00270     return false;
00271 }
00272 
00273 /*
00274  * Goes through all clients on the given workspace and updates the workspace’s
00275  * urgent flag accordingly.
00276  *
00277  */
00278 void workspace_update_urgent_flag(Con *ws) {
00279     bool old_flag = ws->urgent;
00280     ws->urgent = get_urgency_flag(ws);
00281     DLOG("Workspace urgency flag changed from %d to %d\n", old_flag, ws->urgent);
00282 
00283     if (old_flag != ws->urgent)
00284         ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"urgent\"}");
00285 }
00286 
00287 /*
00288  * 'Forces' workspace orientation by moving all cons into a new split-con with
00289  * the same orientation as the workspace and then changing the workspace
00290  * orientation.
00291  *
00292  */
00293 void ws_force_orientation(Con *ws, orientation_t orientation) {
00294     /* 1: create a new split container */
00295     Con *split = con_new(NULL, NULL);
00296     split->parent = ws;
00297 
00298     /* 2: copy layout and orientation from workspace */
00299     split->layout = ws->layout;
00300     split->orientation = ws->orientation;
00301 
00302     Con *old_focused = TAILQ_FIRST(&(ws->focus_head));
00303 
00304     /* 3: move the existing cons of this workspace below the new con */
00305     DLOG("Moving cons\n");
00306     while (!TAILQ_EMPTY(&(ws->nodes_head))) {
00307         Con *child = TAILQ_FIRST(&(ws->nodes_head));
00308         con_detach(child);
00309         con_attach(child, split, true);
00310     }
00311 
00312     /* 4: switch workspace orientation */
00313     ws->orientation = orientation;
00314 
00315     /* 5: attach the new split container to the workspace */
00316     DLOG("Attaching new split to ws\n");
00317     con_attach(split, ws, false);
00318 
00319     /* 6: fix the percentages */
00320     con_fix_percent(ws);
00321 
00322     if (old_focused)
00323         con_focus(old_focused);
00324 }
00325 
00326 /*
00327  * Called when a new con (with a window, not an empty or split con) should be
00328  * attached to the workspace (for example when managing a new window or when
00329  * moving an existing window to the workspace level).
00330  *
00331  * Depending on the workspace_layout setting, this function either returns the
00332  * workspace itself (default layout) or creates a new stacked/tabbed con and
00333  * returns that.
00334  *
00335  */
00336 Con *workspace_attach_to(Con *ws) {
00337     DLOG("Attaching a window to workspace %p / %s\n", ws, ws->name);
00338 
00339     if (config.default_layout == L_DEFAULT) {
00340         DLOG("Default layout, just attaching it to the workspace itself.\n");
00341         return ws;
00342     }
00343 
00344     DLOG("Non-default layout, creating a new split container\n");
00345     /* 1: create a new split container */
00346     Con *new = con_new(NULL, NULL);
00347     new->parent = ws;
00348 
00349     /* 2: set the requested layout on the split con */
00350     new->layout = config.default_layout;
00351 
00352     /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
00353      * to be set. Otherwise, this con will not be interpreted as a split
00354      * container. */
00355     if (config.default_orientation == NO_ORIENTATION) {
00356         new->orientation = (ws->rect.height > ws->rect.width) ? VERT : HORIZ;
00357     } else {
00358         new->orientation = config.default_orientation;
00359     }
00360 
00361     /* 4: attach the new split container to the workspace */
00362     DLOG("Attaching new split %p to workspace %p\n", new, ws);
00363     con_attach(new, ws, false);
00364 
00365     return new;
00366 }