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