diff --git a/doc/yabai.1 b/doc/yabai.1 index 5749cc0d..e9a6053c 100644 --- a/doc/yabai.1 +++ b/doc/yabai.1 @@ -81,7 +81,7 @@ Loads the scripting\-addition into Dock.app. .sp .if n .RS 4 .nf -DIR_SEL := north | east | south | west +DIR_SEL := north | east | south | west | above | below WINDOW_SEL := prev | next | first | last | recent | mouse | largest | smallest | DIR_SEL | window id diff --git a/doc/yabai.asciidoc b/doc/yabai.asciidoc index 2ff05def..d5050afb 100644 --- a/doc/yabai.asciidoc +++ b/doc/yabai.asciidoc @@ -62,7 +62,7 @@ Definitions [subs=+macros] ---- -DIR_SEL := north | east | south | west +DIR_SEL := north | east | south | west | above | below WINDOW_SEL := prev | next | first | last | recent | mouse | largest | smallest | DIR_SEL | window id diff --git a/src/border.c b/src/border.c index 36948b6d..ec7c1f74 100644 --- a/src/border.c +++ b/src/border.c @@ -65,25 +65,33 @@ static CGMutablePathRef border_insert_shape(struct border *border, CGRect frame, CGPathMoveToPoint(insert, NULL, maxx, midy); CGPathAddArcToPoint(insert, NULL, maxx, maxy, midx, maxy, radius); CGPathAddArcToPoint(insert, NULL, minx, maxy, minx, midy, radius); - CGPathAddArcToPoint(insert, NULL, minx, midy, minx, miny, radius); + CGPathAddLineToPoint(insert, NULL, minx, midy); } break; case DIR_EAST: { CGPathMoveToPoint(insert, NULL, midx, miny); CGPathAddArcToPoint(insert, NULL, maxx, miny, maxx, midy, radius); CGPathAddArcToPoint(insert, NULL, maxx, maxy, midx, maxy, radius); - CGPathAddArcToPoint(insert, NULL, midx, maxy, maxx, maxy, radius); + CGPathAddLineToPoint(insert, NULL, midx, maxy); } break; case DIR_SOUTH: { CGPathMoveToPoint(insert, NULL, minx, midy); CGPathAddArcToPoint(insert, NULL, minx, miny, midx, miny, radius); CGPathAddArcToPoint(insert, NULL, maxx, miny, maxx, midy, radius); - CGPathAddArcToPoint(insert, NULL, maxx, midy, maxx, maxy, radius); + CGPathAddLineToPoint(insert, NULL, maxx, midy); } break; case DIR_WEST: { CGPathMoveToPoint(insert, NULL, midx, miny); CGPathAddArcToPoint(insert, NULL, minx, miny, minx, midy, radius); CGPathAddArcToPoint(insert, NULL, minx, maxy, midx, maxy, radius); - CGPathAddArcToPoint(insert, NULL, midx, maxy, midx, maxy, radius); + CGPathAddLineToPoint(insert, NULL, midx, maxy); + } break; + case DIR_ABOVE: case DIR_BELOW: { + CGPathMoveToPoint(insert, NULL, maxx, midy); + CGPathAddArcToPoint(insert, NULL, maxx, maxy, midx, maxy, radius); + CGPathAddArcToPoint(insert, NULL, minx, maxy, minx, midy, radius); + CGPathAddArcToPoint(insert, NULL, minx, miny, midx, miny, radius); + CGPathAddArcToPoint(insert, NULL, maxx, miny, maxx, midy, radius); + CGPathAddLineToPoint(insert, NULL, maxx, midy); } break; } diff --git a/src/message.c b/src/message.c index 7245cd41..57c98f8f 100644 --- a/src/message.c +++ b/src/message.c @@ -130,6 +130,8 @@ extern bool g_verbose; #define ARGUMENT_WINDOW_DIR_EAST "east" #define ARGUMENT_WINDOW_DIR_SOUTH "south" #define ARGUMENT_WINDOW_DIR_WEST "west" +#define ARGUMENT_WINDOW_DIR_ABOVE "above" +#define ARGUMENT_WINDOW_DIR_BELOW "below" #define ARGUMENT_WINDOW_SEL_MOUSE "mouse" #define ARGUMENT_WINDOW_SEL_LARGEST "largest" #define ARGUMENT_WINDOW_SEL_SMALLEST "smallest" @@ -1129,6 +1131,28 @@ static struct selector parse_window_selector(FILE *rsp, char **message, struct w } else { daemon_fail(rsp, "could not locate the selected window.\n"); } + } else if (token_equals(result.token, ARGUMENT_WINDOW_DIR_ABOVE)) { + if (acting_window) { + struct window *closest_window = window_manager_find_managed_window_above(&g_space_manager, &g_window_manager, acting_window); + if (closest_window) { + result.window = closest_window; + } else { + daemon_fail(rsp, "could not locate a managed window above.\n"); + } + } else { + daemon_fail(rsp, "could not locate the selected window.\n"); + } + } else if (token_equals(result.token, ARGUMENT_WINDOW_DIR_BELOW)) { + if (acting_window) { + struct window *closest_window = window_manager_find_managed_window_below(&g_space_manager, &g_window_manager, acting_window); + if (closest_window) { + result.window = closest_window; + } else { + daemon_fail(rsp, "could not locate a managed window below.\n"); + } + } else { + daemon_fail(rsp, "could not locate the selected window.\n"); + } } else if (token_equals(result.token, ARGUMENT_WINDOW_SEL_MOUSE)) { struct window *mouse_window = window_manager_find_window_below_cursor(&g_window_manager); if (mouse_window) { @@ -1233,6 +1257,10 @@ static struct selector parse_dir_selector(FILE *rsp, char **message) result.dir = DIR_SOUTH; } else if (token_equals(result.token, ARGUMENT_WINDOW_DIR_WEST)) { result.dir = DIR_WEST; + } else if (token_equals(result.token, ARGUMENT_WINDOW_DIR_ABOVE)) { + result.dir = DIR_ABOVE; + } else if (token_equals(result.token, ARGUMENT_WINDOW_DIR_BELOW)) { + result.dir = DIR_BELOW; } else { result.did_parse = false; daemon_fail(rsp, "value '%.*s' is not a valid option for DIR_SEL\n", result.token.length, result.token.text); diff --git a/src/misc/macros.h b/src/misc/macros.h index e5985f2d..7c8a70ef 100644 --- a/src/misc/macros.h +++ b/src/misc/macros.h @@ -16,6 +16,8 @@ #define DIR_EAST 90 #define DIR_SOUTH 180 #define DIR_WEST 270 +#define DIR_ABOVE 1 +#define DIR_BELOW -1 #define TYPE_ABS 0x1 #define TYPE_REL 0x2 diff --git a/src/space_manager.c b/src/space_manager.c index 3712df1e..1eeb11bf 100644 --- a/src/space_manager.c +++ b/src/space_manager.c @@ -412,7 +412,8 @@ void space_manager_toggle_window_split(struct space_manager *sm, struct window * struct window_node *node = view_find_window_node(view, window->id); if (node && window_node_is_intermediate(node)) { - node->parent->split = node->parent->split == SPLIT_Y ? SPLIT_X : SPLIT_Y; + enum window_node_split split = node->parent->split; + node->parent->split = split == SPLIT_Z ? SPLIT_Y : split == SPLIT_Y ? SPLIT_X : SPLIT_Z; if (g_space_manager.auto_balance) { window_node_equalize(view->root); diff --git a/src/view.c b/src/view.c index 27281e43..7738739b 100644 --- a/src/view.c +++ b/src/view.c @@ -24,6 +24,7 @@ static enum window_node_child window_node_get_child(struct window_node *node) static enum window_node_split window_node_get_split(struct window_node *node) { if (node->split != SPLIT_NONE) return node->split; + if (node->parent != NULL && node->parent->split == SPLIT_Z) return SPLIT_Z; return node->area.w / node->area.h >= 1.1618f ? SPLIT_Y : SPLIT_X; } @@ -44,7 +45,10 @@ static void area_make_pair(struct view *view, struct window_node *node) float ratio = window_node_get_ratio(node); float gap = window_node_get_gap(view); - if (split == SPLIT_Y) { + if (split == SPLIT_Z) { + node->left->area = node->area; + node->right->area = node->area; + } else if (split == SPLIT_Y) { node->left->area = node->area; node->left->area.w *= ratio; node->left->area.w -= gap; @@ -267,6 +271,29 @@ struct window_node *window_node_find_next_leaf(struct window_node *node) return window_node_find_first_leaf(node->parent->right->left); } +struct window_node *window_node_find_common_ancestor(struct window_node *node, struct window_node *a, struct window_node *b) { + if (!node) return NULL; + if (node == a || node == b) return node; + + struct window_node *left = window_node_find_common_ancestor(node->left, a, b); + struct window_node *right = window_node_find_common_ancestor(node->right, a, b); + + if (left && right) return node; + + return left ? left : right; +} + +bool window_nodes_are_stacked(struct window_node *ancestor, struct window_node *node) { + do { + node = node->parent; + + if (node->split != SPLIT_Z) + return false; + } while (node != ancestor); + + return true; +} + void window_node_rotate(struct window_node *node, int degrees) { if ((degrees == 90 && node->split == SPLIT_Y) || diff --git a/src/view.h b/src/view.h index 15de29d5..d74dd9a7 100644 --- a/src/view.h +++ b/src/view.h @@ -34,6 +34,7 @@ static const char *window_node_child_str[] = enum window_node_split { SPLIT_NONE, + SPLIT_Z, SPLIT_Y, SPLIT_X }; @@ -41,6 +42,7 @@ enum window_node_split static const char *window_node_split_str[] = { "none", + "normal", "vertical", "horizontal" }; diff --git a/src/window_manager.c b/src/window_manager.c index 807f5d48..e289d0e3 100644 --- a/src/window_manager.c +++ b/src/window_manager.c @@ -707,6 +707,42 @@ struct window *window_manager_find_closest_window_in_direction(struct window_man return result; } +struct window *window_manager_find_managed_window_below(struct space_manager *sm, struct window_manager *wm, struct window *window) +{ + struct view *view = space_manager_find_view(sm, space_manager_active_space()); + if (!view) return NULL; + + struct window_node *node = view_find_window_node(view, window->id); + if (!node) return NULL; + + struct window_node *prev = window_node_find_prev_leaf(node); + if (!prev) return NULL; + + struct window_node *ancestor = window_node_find_common_ancestor(view->root, node, prev); + if (!window_nodes_are_stacked(ancestor, node) || !window_nodes_are_stacked(ancestor, prev)) + return NULL; + + return window_manager_find_window(wm, prev->window_id); +} + +struct window *window_manager_find_managed_window_above(struct space_manager *sm, struct window_manager *wm, struct window *window) +{ + struct view *view = space_manager_find_view(sm, space_manager_active_space()); + if (!view) return NULL; + + struct window_node *node = view_find_window_node(view, window->id); + if (!node) return NULL; + + struct window_node *next = window_node_find_next_leaf(node); + if (!next) return NULL; + + struct window_node *ancestor = window_node_find_common_ancestor(view->root, node, next); + if (!window_nodes_are_stacked(ancestor, node) || !window_nodes_are_stacked(ancestor, next)) + return NULL; + + return window_manager_find_window(wm, next->window_id); +} + struct window *window_manager_find_prev_managed_window(struct space_manager *sm, struct window_manager *wm, struct window *window) { struct view *view = space_manager_find_view(sm, space_manager_active_space()); @@ -1134,6 +1170,18 @@ void window_manager_set_window_insertion(struct space_manager *sm, struct window window->border.insert_active = true; window->border.insert_dir = direction; view->insertion_point = node->window_id; + } else if (direction == DIR_ABOVE) { + node->split = SPLIT_Z; + node->child = CHILD_SECOND; + window->border.insert_active = true; + window->border.insert_dir = direction; + view->insertion_point = node->window_id; + } else if (direction == DIR_BELOW) { + node->split = SPLIT_Z; + node->child = CHILD_FIRST; + window->border.insert_active = true; + window->border.insert_dir = direction; + view->insertion_point = node->window_id; } border_window_refresh(window); diff --git a/src/window_manager.h b/src/window_manager.h index d4be8fd9..2f72543a 100644 --- a/src/window_manager.h +++ b/src/window_manager.h @@ -95,6 +95,8 @@ struct window *window_manager_find_window_at_point(struct window_manager *wm, CG struct window *window_manager_find_window_below_cursor(struct window_manager *wm); struct window *window_manager_find_closest_managed_window_in_direction(struct window_manager *wm, struct window *window, int direction); struct window *window_manager_find_closest_window_in_direction(struct window_manager *wm, struct window *window, int direction); +struct window *window_manager_find_managed_window_below(struct space_manager *sm, struct window_manager *wm, struct window *window); +struct window *window_manager_find_managed_window_above(struct space_manager *sm, struct window_manager *wm, struct window *window); struct window *window_manager_find_prev_managed_window(struct space_manager *sm, struct window_manager *wm, struct window *window); struct window *window_manager_find_next_managed_window(struct space_manager *sm, struct window_manager *wm, struct window *window); struct window *window_manager_find_first_managed_window(struct space_manager *sm, struct window_manager *wm);