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