i3
src/con.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  * con.c contains all functions which deal with containers directly (creating
00008  * containers, searching containers, getting specific properties from
00009  * containers, …).
00010  *
00011  */
00012 #include "all.h"
00013 
00014 char *colors[] = {
00015     "#ff0000",
00016     "#00FF00",
00017     "#0000FF",
00018     "#ff00ff",
00019     "#00ffff",
00020     "#ffff00",
00021     "#aa0000",
00022     "#00aa00",
00023     "#0000aa",
00024     "#aa00aa"
00025 };
00026 
00027 static void con_on_remove_child(Con *con);
00028 
00029 /*
00030  * Create a new container (and attach it to the given parent, if not NULL).
00031  * This function initializes the data structures and creates the appropriate
00032  * X11 IDs using x_con_init().
00033  *
00034  */
00035 Con *con_new(Con *parent, i3Window *window) {
00036     Con *new = scalloc(sizeof(Con));
00037     new->on_remove_child = con_on_remove_child;
00038     TAILQ_INSERT_TAIL(&all_cons, new, all_cons);
00039     new->type = CT_CON;
00040     new->window = window;
00041     new->border_style = config.default_border;
00042     static int cnt = 0;
00043     DLOG("opening window %d\n", cnt);
00044 
00045     /* TODO: remove window coloring after test-phase */
00046     DLOG("color %s\n", colors[cnt]);
00047     new->name = strdup(colors[cnt]);
00048     //uint32_t cp = get_colorpixel(colors[cnt]);
00049     cnt++;
00050     if ((cnt % (sizeof(colors) / sizeof(char*))) == 0)
00051         cnt = 0;
00052 
00053     x_con_init(new);
00054 
00055     TAILQ_INIT(&(new->floating_head));
00056     TAILQ_INIT(&(new->nodes_head));
00057     TAILQ_INIT(&(new->focus_head));
00058     TAILQ_INIT(&(new->swallow_head));
00059 
00060     if (parent != NULL)
00061         con_attach(new, parent, false);
00062 
00063     return new;
00064 }
00065 
00066 /*
00067  * Attaches the given container to the given parent. This happens when moving
00068  * a container or when inserting a new container at a specific place in the
00069  * tree.
00070  *
00071  * ignore_focus is to just insert the Con at the end (useful when creating a
00072  * new split container *around* some containers, that is, detaching and
00073  * attaching them in order without wanting to mess with the focus in between).
00074  *
00075  */
00076 void con_attach(Con *con, Con *parent, bool ignore_focus) {
00077     con->parent = parent;
00078     Con *loop;
00079     Con *current = NULL;
00080     struct nodes_head *nodes_head = &(parent->nodes_head);
00081     struct focus_head *focus_head = &(parent->focus_head);
00082 
00083     /* Workspaces are handled differently: they need to be inserted at the
00084      * right position. */
00085     if (con->type == CT_WORKSPACE) {
00086         DLOG("it's a workspace. num = %d\n", con->num);
00087         if (con->num == -1 || TAILQ_EMPTY(nodes_head)) {
00088             TAILQ_INSERT_TAIL(nodes_head, con, nodes);
00089         } else {
00090             current = TAILQ_FIRST(nodes_head);
00091             if (con->num < current->num) {
00092                 /* we need to insert the container at the beginning */
00093                 TAILQ_INSERT_HEAD(nodes_head, con, nodes);
00094             } else {
00095                 while (current->num != -1 && con->num > current->num) {
00096                     current = TAILQ_NEXT(current, nodes);
00097                     if (current == TAILQ_END(nodes_head)) {
00098                         current = NULL;
00099                         break;
00100                     }
00101                 }
00102                 /* we need to insert con after current, if current is not NULL */
00103                 if (current)
00104                     TAILQ_INSERT_BEFORE(current, con, nodes);
00105                 else TAILQ_INSERT_TAIL(nodes_head, con, nodes);
00106             }
00107         }
00108         goto add_to_focus_head;
00109     }
00110 
00111     if (con->type == CT_FLOATING_CON) {
00112         DLOG("Inserting into floating containers\n");
00113         TAILQ_INSERT_TAIL(&(parent->floating_head), con, floating_windows);
00114     } else {
00115         if (!ignore_focus) {
00116             /* Get the first tiling container in focus stack */
00117             TAILQ_FOREACH(loop, &(parent->focus_head), focused) {
00118                 if (loop->type == CT_FLOATING_CON)
00119                     continue;
00120                 current = loop;
00121                 break;
00122             }
00123         }
00124 
00125         /* When the container is not a split container (but contains a window)
00126          * and is attached to a workspace, we check if the user configured a
00127          * workspace_layout. This is done in workspace_attach_to, which will
00128          * provide us with the container to which we should attach (either the
00129          * workspace or a new split container with the configured
00130          * workspace_layout).
00131          */
00132         if (con->window != NULL && parent->type == CT_WORKSPACE) {
00133             DLOG("Parent is a workspace. Applying default layout...\n");
00134             Con *target = workspace_attach_to(parent);
00135 
00136             /* Attach the original con to this new split con instead */
00137             nodes_head = &(target->nodes_head);
00138             focus_head = &(target->focus_head);
00139             con->parent = target;
00140             current = NULL;
00141 
00142             DLOG("done\n");
00143         }
00144 
00145         /* Insert the container after the tiling container, if found.
00146          * When adding to a CT_OUTPUT, just append one after another. */
00147         if (current && parent->type != CT_OUTPUT) {
00148             DLOG("Inserting con = %p after last focused tiling con %p\n",
00149                  con, current);
00150             TAILQ_INSERT_AFTER(nodes_head, current, con, nodes);
00151         } else TAILQ_INSERT_TAIL(nodes_head, con, nodes);
00152     }
00153 
00154 add_to_focus_head:
00155     /* We insert to the TAIL because con_focus() will correct this.
00156      * This way, we have the option to insert Cons without having
00157      * to focus them. */
00158     TAILQ_INSERT_TAIL(focus_head, con, focused);
00159 }
00160 
00161 /*
00162  * Detaches the given container from its current parent
00163  *
00164  */
00165 void con_detach(Con *con) {
00166     if (con->type == CT_FLOATING_CON) {
00167         TAILQ_REMOVE(&(con->parent->floating_head), con, floating_windows);
00168         TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
00169     } else {
00170         TAILQ_REMOVE(&(con->parent->nodes_head), con, nodes);
00171         TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
00172     }
00173 }
00174 
00175 /*
00176  * Sets input focus to the given container. Will be updated in X11 in the next
00177  * run of x_push_changes().
00178  *
00179  */
00180 void con_focus(Con *con) {
00181     assert(con != NULL);
00182     DLOG("con_focus = %p\n", con);
00183 
00184     /* 1: set focused-pointer to the new con */
00185     /* 2: exchange the position of the container in focus stack of the parent all the way up */
00186     TAILQ_REMOVE(&(con->parent->focus_head), con, focused);
00187     TAILQ_INSERT_HEAD(&(con->parent->focus_head), con, focused);
00188     if (con->parent->parent != NULL)
00189         con_focus(con->parent);
00190 
00191     focused = con;
00192     if (con->urgent) {
00193         con->urgent = false;
00194         workspace_update_urgent_flag(con_get_workspace(con));
00195     }
00196     DLOG("con_focus done = %p\n", con);
00197 }
00198 
00199 /*
00200  * Returns true when this node is a leaf node (has no children)
00201  *
00202  */
00203 bool con_is_leaf(Con *con) {
00204     return TAILQ_EMPTY(&(con->nodes_head));
00205 }
00206 
00207 /*
00208  * Returns true if this node accepts a window (if the node swallows windows,
00209  * it might already have swallowed enough and cannot hold any more).
00210  *
00211  */
00212 bool con_accepts_window(Con *con) {
00213     /* 1: workspaces never accept direct windows */
00214     if (con->type == CT_WORKSPACE)
00215         return false;
00216 
00217     if (con->orientation != NO_ORIENTATION) {
00218         DLOG("container %p does not accepts windows, orientation != NO_ORIENTATION\n", con);
00219         return false;
00220     }
00221 
00222     /* TODO: if this is a swallowing container, we need to check its max_clients */
00223     return (con->window == NULL);
00224 }
00225 
00226 /*
00227  * Gets the output container (first container with CT_OUTPUT in hierarchy) this
00228  * node is on.
00229  *
00230  */
00231 Con *con_get_output(Con *con) {
00232     Con *result = con;
00233     while (result != NULL && result->type != CT_OUTPUT)
00234         result = result->parent;
00235     /* We must be able to get an output because focus can never be set higher
00236      * in the tree (root node cannot be focused). */
00237     assert(result != NULL);
00238     return result;
00239 }
00240 
00241 /*
00242  * Gets the workspace container this node is on.
00243  *
00244  */
00245 Con *con_get_workspace(Con *con) {
00246     Con *result = con;
00247     while (result != NULL && result->type != CT_WORKSPACE)
00248         result = result->parent;
00249     return result;
00250 }
00251 
00252 /*
00253  * Searches parenst of the given 'con' until it reaches one with the specified
00254  * 'orientation'. Aborts when it comes across a floating_con.
00255  *
00256  */
00257 Con *con_parent_with_orientation(Con *con, orientation_t orientation) {
00258     DLOG("Searching for parent of Con %p with orientation %d\n", con, orientation);
00259     Con *parent = con->parent;
00260     if (parent->type == CT_FLOATING_CON)
00261         return NULL;
00262     while (con_orientation(parent) != orientation) {
00263         DLOG("Need to go one level further up\n");
00264         parent = parent->parent;
00265         /* Abort when we reach a floating con */
00266         if (parent && parent->type == CT_FLOATING_CON)
00267             parent = NULL;
00268         if (parent == NULL)
00269             break;
00270     }
00271     DLOG("Result: %p\n", parent);
00272     return parent;
00273 }
00274 
00275 /*
00276  * helper data structure for the breadth-first-search in
00277  * con_get_fullscreen_con()
00278  *
00279  */
00280 struct bfs_entry {
00281     Con *con;
00282 
00283     TAILQ_ENTRY(bfs_entry) entries;
00284 };
00285 
00286 /*
00287  * Returns the first fullscreen node below this node.
00288  *
00289  */
00290 Con *con_get_fullscreen_con(Con *con, int fullscreen_mode) {
00291     Con *current, *child;
00292 
00293     /* TODO: is breadth-first-search really appropriate? (check as soon as
00294      * fullscreen levels and fullscreen for containers is implemented) */
00295     TAILQ_HEAD(bfs_head, bfs_entry) bfs_head = TAILQ_HEAD_INITIALIZER(bfs_head);
00296     struct bfs_entry *entry = smalloc(sizeof(struct bfs_entry));
00297     entry->con = con;
00298     TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
00299 
00300     while (!TAILQ_EMPTY(&bfs_head)) {
00301         entry = TAILQ_FIRST(&bfs_head);
00302         current = entry->con;
00303         if (current != con && current->fullscreen_mode == fullscreen_mode) {
00304             /* empty the queue */
00305             while (!TAILQ_EMPTY(&bfs_head)) {
00306                 entry = TAILQ_FIRST(&bfs_head);
00307                 TAILQ_REMOVE(&bfs_head, entry, entries);
00308                 free(entry);
00309             }
00310             return current;
00311         }
00312 
00313         TAILQ_REMOVE(&bfs_head, entry, entries);
00314         free(entry);
00315 
00316         TAILQ_FOREACH(child, &(current->nodes_head), nodes) {
00317             entry = smalloc(sizeof(struct bfs_entry));
00318             entry->con = child;
00319             TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
00320         }
00321 
00322         TAILQ_FOREACH(child, &(current->floating_head), floating_windows) {
00323             entry = smalloc(sizeof(struct bfs_entry));
00324             entry->con = child;
00325             TAILQ_INSERT_TAIL(&bfs_head, entry, entries);
00326         }
00327     }
00328 
00329     return NULL;
00330 }
00331 
00332 /*
00333  * Returns true if the node is floating.
00334  *
00335  */
00336 bool con_is_floating(Con *con) {
00337     assert(con != NULL);
00338     DLOG("checking if con %p is floating\n", con);
00339     return (con->floating >= FLOATING_AUTO_ON);
00340 }
00341 
00342 /*
00343  * Checks if the given container is either floating or inside some floating
00344  * container. It returns the FLOATING_CON container.
00345  *
00346  */
00347 Con *con_inside_floating(Con *con) {
00348     assert(con != NULL);
00349     if (con->type == CT_FLOATING_CON)
00350         return con;
00351 
00352     if (con->floating >= FLOATING_AUTO_ON)
00353         return con->parent;
00354 
00355     if (con->type == CT_WORKSPACE || con->type == CT_OUTPUT)
00356         return NULL;
00357 
00358     return con_inside_floating(con->parent);
00359 }
00360 
00361 /*
00362  * Returns the container with the given client window ID or NULL if no such
00363  * container exists.
00364  *
00365  */
00366 Con *con_by_window_id(xcb_window_t window) {
00367     Con *con;
00368     TAILQ_FOREACH(con, &all_cons, all_cons)
00369         if (con->window != NULL && con->window->id == window)
00370             return con;
00371     return NULL;
00372 }
00373 
00374 /*
00375  * Returns the container with the given frame ID or NULL if no such container
00376  * exists.
00377  *
00378  */
00379 Con *con_by_frame_id(xcb_window_t frame) {
00380     Con *con;
00381     TAILQ_FOREACH(con, &all_cons, all_cons)
00382         if (con->frame == frame)
00383             return con;
00384     return NULL;
00385 }
00386 
00387 /*
00388  * Returns the first container below 'con' which wants to swallow this window
00389  * TODO: priority
00390  *
00391  */
00392 Con *con_for_window(Con *con, i3Window *window, Match **store_match) {
00393     Con *child;
00394     Match *match;
00395     DLOG("searching con for window %p starting at con %p\n", window, con);
00396     DLOG("class == %s\n", window->class_class);
00397 
00398     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
00399         TAILQ_FOREACH(match, &(child->swallow_head), matches) {
00400             if (!match_matches_window(match, window))
00401                 continue;
00402             if (store_match != NULL)
00403                 *store_match = match;
00404             return child;
00405         }
00406         Con *result = con_for_window(child, window, store_match);
00407         if (result != NULL)
00408             return result;
00409     }
00410 
00411     TAILQ_FOREACH(child, &(con->floating_head), floating_windows) {
00412         TAILQ_FOREACH(match, &(child->swallow_head), matches) {
00413             if (!match_matches_window(match, window))
00414                 continue;
00415             if (store_match != NULL)
00416                 *store_match = match;
00417             return child;
00418         }
00419         Con *result = con_for_window(child, window, store_match);
00420         if (result != NULL)
00421             return result;
00422     }
00423 
00424     return NULL;
00425 }
00426 
00427 /*
00428  * Returns the number of children of this container.
00429  *
00430  */
00431 int con_num_children(Con *con) {
00432     Con *child;
00433     int children = 0;
00434 
00435     TAILQ_FOREACH(child, &(con->nodes_head), nodes)
00436         children++;
00437 
00438     return children;
00439 }
00440 
00441 /*
00442  * Updates the percent attribute of the children of the given container. This
00443  * function needs to be called when a window is added or removed from a
00444  * container.
00445  *
00446  */
00447 void con_fix_percent(Con *con) {
00448     Con *child;
00449     int children = con_num_children(con);
00450 
00451     // calculate how much we have distributed and how many containers
00452     // with a percentage set we have
00453     double total = 0.0;
00454     int children_with_percent = 0;
00455     TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
00456         if (child->percent > 0.0) {
00457             total += child->percent;
00458             ++children_with_percent;
00459         }
00460     }
00461 
00462     // if there were children without a percentage set, set to a value that
00463     // will make those children proportional to all others
00464     if (children_with_percent != children) {
00465         TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
00466             if (child->percent <= 0.0) {
00467                 if (children_with_percent == 0)
00468                     total += (child->percent = 1.0);
00469                 else total += (child->percent = total / children_with_percent);
00470             }
00471         }
00472     }
00473 
00474     // if we got a zero, just distribute the space equally, otherwise
00475     // distribute according to the proportions we got
00476     if (total == 0.0) {
00477         TAILQ_FOREACH(child, &(con->nodes_head), nodes)
00478             child->percent = 1.0 / children;
00479     } else if (total != 1.0) {
00480         TAILQ_FOREACH(child, &(con->nodes_head), nodes)
00481             child->percent /= total;
00482     }
00483 }
00484 
00485 /*
00486  * Toggles fullscreen mode for the given container. Fullscreen mode will not be
00487  * entered when there already is a fullscreen container on this workspace.
00488  *
00489  */
00490 void con_toggle_fullscreen(Con *con, int fullscreen_mode) {
00491     Con *workspace, *fullscreen;
00492 
00493     if (con->type == CT_WORKSPACE) {
00494         DLOG("You cannot make a workspace fullscreen.\n");
00495         return;
00496     }
00497 
00498     DLOG("toggling fullscreen for %p / %s\n", con, con->name);
00499     if (con->fullscreen_mode == CF_NONE) {
00500         /* 1: check if there already is a fullscreen con */
00501         if (fullscreen_mode == CF_GLOBAL)
00502             fullscreen = con_get_fullscreen_con(croot, CF_GLOBAL);
00503         else {
00504             workspace = con_get_workspace(con);
00505             fullscreen = con_get_fullscreen_con(workspace, CF_OUTPUT);
00506         }
00507         if (fullscreen != NULL) {
00508             LOG("Not entering fullscreen mode, container (%p/%s) "
00509                 "already is in fullscreen mode\n",
00510                 fullscreen, fullscreen->name);
00511             goto update_netwm_state;
00512         }
00513 
00514         /* 2: enable fullscreen */
00515         con->fullscreen_mode = fullscreen_mode;
00516     } else {
00517         /* 1: disable fullscreen */
00518         con->fullscreen_mode = CF_NONE;
00519     }
00520 
00521 update_netwm_state:
00522     DLOG("mode now: %d\n", con->fullscreen_mode);
00523 
00524     /* update _NET_WM_STATE if this container has a window */
00525     /* TODO: when a window is assigned to a container which is already
00526      * fullscreened, this state needs to be pushed to the client, too */
00527     if (con->window == NULL)
00528         return;
00529 
00530     uint32_t values[1];
00531     unsigned int num = 0;
00532 
00533     if (con->fullscreen_mode != CF_NONE)
00534         values[num++] = A__NET_WM_STATE_FULLSCREEN;
00535 
00536     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, con->window->id,
00537                         A__NET_WM_STATE, A_ATOM, 32, num, values);
00538 }
00539 
00540 /*
00541  * Moves the given container to the currently focused container on the given
00542  * workspace.
00543  * TODO: is there a better place for this function?
00544  *
00545  */
00546 void con_move_to_workspace(Con *con, Con *workspace) {
00547     if (con->type == CT_WORKSPACE) {
00548         DLOG("Moving workspaces is not yet implemented.\n");
00549         return;
00550     }
00551 
00552     if (con_is_floating(con)) {
00553         DLOG("Using FLOATINGCON instead\n");
00554         con = con->parent;
00555     }
00556 
00557     Con *source_output = con_get_output(con),
00558         *dest_output = con_get_output(workspace);
00559 
00560     /* 1: save the container which is going to be focused after the current
00561      * container is moved away */
00562     Con *focus_next = con_next_focused(con);
00563 
00564     /* 2: get the focused container of this workspace */
00565     Con *next = con_descend_focused(workspace);
00566 
00567     /* 3: we go up one level, but only when next is a normal container */
00568     if (next->type != CT_WORKSPACE) {
00569         DLOG("next originally = %p / %s / type %d\n", next, next->name, next->type);
00570         next = next->parent;
00571     }
00572 
00573     /* 4: if the target container is floating, we get the workspace instead.
00574      * Only tiling windows need to get inserted next to the current container.
00575      * */
00576     Con *floatingcon = con_inside_floating(next);
00577     if (floatingcon != NULL) {
00578         DLOG("floatingcon, going up even further\n");
00579         next = floatingcon->parent;
00580     }
00581 
00582     if (con->type == CT_FLOATING_CON) {
00583         Con *ws = con_get_workspace(next);
00584         DLOG("This is a floating window, using workspace %p / %s\n", ws, ws->name);
00585         next = ws;
00586     }
00587 
00588     DLOG("Re-attaching container to %p / %s\n", next, next->name);
00589     /* 5: re-attach the con to the parent of this focused container */
00590     Con *parent = con->parent;
00591     con_detach(con);
00592     con_attach(con, next, false);
00593 
00594     /* 6: fix the percentages */
00595     con_fix_percent(parent);
00596     con->percent = 0.0;
00597     con_fix_percent(next);
00598 
00599     /* 7: focus the con on the target workspace (the X focus is only updated by
00600      * calling tree_render(), so for the "real" focus this is a no-op) */
00601     con_focus(con);
00602 
00603     /* 8: when moving to a visible workspace on a different output, we keep the
00604      * con focused. Otherwise, we leave the focus on the current workspace as we
00605      * don’t want to focus invisible workspaces */
00606     if (source_output != dest_output &&
00607         workspace_is_visible(workspace)) {
00608         DLOG("Moved to a different output, focusing target\n");
00609     } else {
00610         con_focus(focus_next);
00611     }
00612 
00613     CALL(parent, on_remove_child);
00614 }
00615 
00616 /*
00617  * Returns the orientation of the given container (for stacked containers,
00618  * vertical orientation is used regardless of the actual orientation of the
00619  * container).
00620  *
00621  */
00622 int con_orientation(Con *con) {
00623     /* stacking containers behave like they are in vertical orientation */
00624     if (con->layout == L_STACKED)
00625         return VERT;
00626 
00627     if (con->layout == L_TABBED)
00628         return HORIZ;
00629 
00630     return con->orientation;
00631 }
00632 
00633 /*
00634  * Returns the container which will be focused next when the given container
00635  * is not available anymore. Called in tree_close and con_move_to_workspace
00636  * to properly restore focus.
00637  *
00638  */
00639 Con *con_next_focused(Con *con) {
00640     Con *next;
00641     /* floating containers are attached to a workspace, so we focus either the
00642      * next floating container (if any) or the workspace itself. */
00643     if (con->type == CT_FLOATING_CON) {
00644         DLOG("selecting next for CT_FLOATING_CON\n");
00645         next = TAILQ_NEXT(con, floating_windows);
00646         DLOG("next = %p\n", next);
00647         if (!next) {
00648             next = TAILQ_PREV(con, floating_head, floating_windows);
00649             DLOG("using prev, next = %p\n", next);
00650         }
00651         if (!next) {
00652             Con *ws = con_get_workspace(con);
00653             next = ws;
00654             DLOG("no more floating containers for next = %p, restoring workspace focus\n", next);
00655             while (next != TAILQ_END(&(ws->focus_head)) && !TAILQ_EMPTY(&(next->focus_head))) {
00656                 next = TAILQ_FIRST(&(next->focus_head));
00657                 if (next == con) {
00658                     DLOG("skipping container itself, we want the next client\n");
00659                     next = TAILQ_NEXT(next, focused);
00660                 }
00661             }
00662             if (next == TAILQ_END(&(ws->focus_head))) {
00663                 DLOG("Focus list empty, returning ws\n");
00664                 next = ws;
00665             }
00666         } else {
00667             /* Instead of returning the next CT_FLOATING_CON, we descend it to
00668              * get an actual window to focus. */
00669             next = con_descend_focused(next);
00670         }
00671         return next;
00672     }
00673 
00674     /* dock clients cannot be focused, so we focus the workspace instead */
00675     if (con->parent->type == CT_DOCKAREA) {
00676         DLOG("selecting workspace for dock client\n");
00677         return con_descend_focused(output_get_content(con->parent->parent));
00678     }
00679 
00680     /* if 'con' is not the first entry in the focus stack, use the first one as
00681      * it’s currently focused already */
00682     Con *first = TAILQ_FIRST(&(con->parent->focus_head));
00683     if (first != con) {
00684         DLOG("Using first entry %p\n", first);
00685         next = first;
00686     } else {
00687         /* try to focus the next container on the same level as this one or fall
00688          * back to its parent */
00689         if (!(next = TAILQ_NEXT(con, focused)))
00690             next = con->parent;
00691     }
00692 
00693     /* now go down the focus stack as far as
00694      * possible, excluding the current container */
00695     while (!TAILQ_EMPTY(&(next->focus_head)) &&
00696            TAILQ_FIRST(&(next->focus_head)) != con)
00697         next = TAILQ_FIRST(&(next->focus_head));
00698 
00699     return next;
00700 }
00701 
00702 /*
00703  * Get the next/previous container in the specified orientation. This may
00704  * travel up until it finds a container with suitable orientation.
00705  *
00706  */
00707 Con *con_get_next(Con *con, char way, orientation_t orientation) {
00708     DLOG("con_get_next(way=%c, orientation=%d)\n", way, orientation);
00709     /* 1: get the first parent with the same orientation */
00710     Con *cur = con;
00711     while (con_orientation(cur->parent) != orientation) {
00712         DLOG("need to go one level further up\n");
00713         if (cur->parent->type == CT_WORKSPACE) {
00714             LOG("that's a workspace, we can't go further up\n");
00715             return NULL;
00716         }
00717         cur = cur->parent;
00718     }
00719 
00720     /* 2: chose next (or previous) */
00721     Con *next;
00722     if (way == 'n') {
00723         next = TAILQ_NEXT(cur, nodes);
00724         /* if we are at the end of the list, we need to wrap */
00725         if (next == TAILQ_END(&(parent->nodes_head)))
00726             return NULL;
00727     } else {
00728         next = TAILQ_PREV(cur, nodes_head, nodes);
00729         /* if we are at the end of the list, we need to wrap */
00730         if (next == TAILQ_END(&(cur->nodes_head)))
00731             return NULL;
00732     }
00733     DLOG("next = %p\n", next);
00734 
00735     return next;
00736 }
00737 
00738 /*
00739  * Returns the focused con inside this client, descending the tree as far as
00740  * possible. This comes in handy when attaching a con to a workspace at the
00741  * currently focused position, for example.
00742  *
00743  */
00744 Con *con_descend_focused(Con *con) {
00745     Con *next = con;
00746     while (!TAILQ_EMPTY(&(next->focus_head)))
00747         next = TAILQ_FIRST(&(next->focus_head));
00748     return next;
00749 }
00750 
00751 /*
00752  * Returns the focused con inside this client, descending the tree as far as
00753  * possible. This comes in handy when attaching a con to a workspace at the
00754  * currently focused position, for example.
00755  *
00756  * Works like con_descend_focused but considers only tiling cons.
00757  *
00758  */
00759 Con *con_descend_tiling_focused(Con *con) {
00760     Con *next = con;
00761     Con *before;
00762     Con *child;
00763     do {
00764         before = next;
00765         TAILQ_FOREACH(child, &(next->focus_head), focused) {
00766             if (child->type == CT_FLOATING_CON)
00767                 continue;
00768 
00769             next = child;
00770             break;
00771         }
00772     } while (before != next);
00773     return next;
00774 }
00775 
00776 
00777 /*
00778  * Returns a "relative" Rect which contains the amount of pixels that need to
00779  * be added to the original Rect to get the final position (obviously the
00780  * amount of pixels for normal, 1pixel and borderless are different).
00781  *
00782  */
00783 Rect con_border_style_rect(Con *con) {
00784     switch (con_border_style(con)) {
00785     case BS_NORMAL:
00786         return (Rect){2, 0, -(2 * 2), -2};
00787 
00788     case BS_1PIXEL:
00789         return (Rect){1, 1, -2, -2};
00790 
00791     case BS_NONE:
00792         return (Rect){0, 0, 0, 0};
00793 
00794     default:
00795         assert(false);
00796     }
00797 }
00798 
00799 /*
00800  * Use this function to get a container’s border style. This is important
00801  * because when inside a stack, the border style is always BS_NORMAL.
00802  * For tabbed mode, the same applies, with one exception: when the container is
00803  * borderless and the only element in the tabbed container, the border is not
00804  * rendered.
00805  *
00806  * For children of a CT_DOCKAREA, the border style is always none.
00807  *
00808  */
00809 int con_border_style(Con *con) {
00810     Con *fs = con_get_fullscreen_con(con->parent, CF_OUTPUT);
00811     if (fs == con) {
00812         DLOG("this one is fullscreen! overriding BS_NONE\n");
00813         return BS_NONE;
00814     }
00815 
00816     if (con->parent->layout == L_STACKED)
00817         return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
00818 
00819     if (con->parent->layout == L_TABBED && con->border_style != BS_NORMAL)
00820         return (con_num_children(con->parent) == 1 ? con->border_style : BS_NORMAL);
00821 
00822     if (con->parent->type == CT_DOCKAREA)
00823         return BS_NONE;
00824 
00825     return con->border_style;
00826 }
00827 
00828 /*
00829  * This function changes the layout of a given container. Use it to handle
00830  * special cases like changing a whole workspace to stacked/tabbed (creates a
00831  * new split container before).
00832  *
00833  */
00834 void con_set_layout(Con *con, int layout) {
00835     /* When the container type is CT_WORKSPACE, the user wants to change the
00836      * whole workspace into stacked/tabbed mode. To do this and still allow
00837      * intuitive operations (like level-up and then opening a new window), we
00838      * need to create a new split container. */
00839     if (con->type == CT_WORKSPACE) {
00840         DLOG("Creating new split container\n");
00841         /* 1: create a new split container */
00842         Con *new = con_new(NULL, NULL);
00843         new->parent = con;
00844 
00845         /* 2: set the requested layout on the split con */
00846         new->layout = layout;
00847 
00848         /* 3: While the layout is irrelevant in stacked/tabbed mode, it needs
00849          * to be set. Otherwise, this con will not be interpreted as a split
00850          * container. */
00851         if (config.default_orientation == NO_ORIENTATION) {
00852             new->orientation = (con->rect.height > con->rect.width) ? VERT : HORIZ;
00853         } else {
00854             new->orientation = config.default_orientation;
00855         }
00856 
00857         Con *old_focused = TAILQ_FIRST(&(con->focus_head));
00858         if (old_focused == TAILQ_END(&(con->focus_head)))
00859             old_focused = NULL;
00860 
00861         /* 4: move the existing cons of this workspace below the new con */
00862         DLOG("Moving cons\n");
00863         Con *child;
00864         while (!TAILQ_EMPTY(&(con->nodes_head))) {
00865             child = TAILQ_FIRST(&(con->nodes_head));
00866             con_detach(child);
00867             con_attach(child, new, true);
00868         }
00869 
00870         /* 4: attach the new split container to the workspace */
00871         DLOG("Attaching new split to ws\n");
00872         con_attach(new, con, false);
00873 
00874         if (old_focused)
00875             con_focus(old_focused);
00876 
00877         tree_flatten(croot);
00878 
00879         return;
00880     }
00881 
00882     con->layout = layout;
00883 }
00884 
00885 /*
00886  * Callback which will be called when removing a child from the given con.
00887  * Kills the container if it is empty and replaces it with the child if there
00888  * is exactly one child.
00889  *
00890  */
00891 static void con_on_remove_child(Con *con) {
00892     DLOG("on_remove_child\n");
00893 
00894     /* Every container 'above' (in the hierarchy) the workspace content should
00895      * not be closed when the last child was removed */
00896     if (con->type == CT_WORKSPACE ||
00897         con->type == CT_OUTPUT ||
00898         con->type == CT_ROOT ||
00899         con->type == CT_DOCKAREA) {
00900         DLOG("not handling, type = %d\n", con->type);
00901         return;
00902     }
00903 
00904     /* TODO: check if this container would swallow any other client and
00905      * don’t close it automatically. */
00906     int children = con_num_children(con);
00907     if (children == 0) {
00908         DLOG("Container empty, closing\n");
00909         tree_close(con, DONT_KILL_WINDOW, false);
00910         return;
00911     }
00912 }
00913 
00914 /*
00915  * Determines the minimum size of the given con by looking at its children (for
00916  * split/stacked/tabbed cons). Will be called when resizing floating cons
00917  *
00918  */
00919 Rect con_minimum_size(Con *con) {
00920     DLOG("Determining minimum size for con %p\n", con);
00921 
00922     if (con_is_leaf(con)) {
00923         DLOG("leaf node, returning 75x50\n");
00924         return (Rect){ 0, 0, 75, 50 };
00925     }
00926 
00927     if (con->type == CT_FLOATING_CON) {
00928         DLOG("floating con\n");
00929         Con *child = TAILQ_FIRST(&(con->nodes_head));
00930         return con_minimum_size(child);
00931     }
00932 
00933     if (con->layout == L_STACKED || con->layout == L_TABBED) {
00934         uint32_t max_width = 0, max_height = 0, deco_height = 0;
00935         Con *child;
00936         TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
00937             Rect min = con_minimum_size(child);
00938             deco_height += child->deco_rect.height;
00939             max_width = max(max_width, min.width);
00940             max_height = max(max_height, min.height);
00941         }
00942         DLOG("stacked/tabbed now, returning %d x %d + deco_rect = %d\n",
00943              max_width, max_height, deco_height);
00944         return (Rect){ 0, 0, max_width, max_height + deco_height };
00945     }
00946 
00947     /* For horizontal/vertical split containers we sum up the width (h-split)
00948      * or height (v-split) and use the maximum of the height (h-split) or width
00949      * (v-split) as minimum size. */
00950     if (con->orientation == HORIZ || con->orientation == VERT) {
00951         uint32_t width = 0, height = 0;
00952         Con *child;
00953         TAILQ_FOREACH(child, &(con->nodes_head), nodes) {
00954             Rect min = con_minimum_size(child);
00955             if (con->orientation == HORIZ) {
00956                 width += min.width;
00957                 height = max(height, min.height);
00958             } else {
00959                 height += min.height;
00960                 width = max(width, min.width);
00961             }
00962         }
00963         DLOG("split container, returning width = %d x height = %d\n", width, height);
00964         return (Rect){ 0, 0, width, height };
00965     }
00966 
00967     ELOG("Unhandled case, type = %d, layout = %d, orientation = %d\n",
00968          con->type, con->layout, con->orientation);
00969     assert(false);
00970 }