i3
|
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 }