From c565304691c03c20a0c80b3cf0829db46588fc51 Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Sat, 6 Jun 2020 00:27:31 +0200 Subject: [PATCH] #225 extend display_sel to include dir_sel to target displays using cardinal directions --- CHANGELOG.md | 1 + doc/yabai.1 | 6 ++-- doc/yabai.asciidoc | 2 +- src/display_manager.c | 52 ++++++++++++++++++++++++++++++++ src/display_manager.h | 1 + src/message.c | 70 +++++++++++++++++++++++++++++++++++-------- 6 files changed, 115 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 072ec0fb..ed1cc8e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] ### Changed - If *focus follows mouse* is enabled, moving the cursor to a different display will now focus that display even if it is empty [#459](https://github.com/koekeishiya/yabai/issues/459) +- Extend definition of *DISPLAY_SEL* to include *DIR_SEL* so that displays can be targetted using cardinal directions [#225](https://github.com/koekeishiya/yabai/issues/225) ## [3.1.0] - 2020-06-05 ### Added diff --git a/doc/yabai.1 b/doc/yabai.1 index bca01f2d..e671742f 100644 --- a/doc/yabai.1 +++ b/doc/yabai.1 @@ -2,12 +2,12 @@ .\" Title: yabai .\" Author: [see the "AUTHOR(S)" section] .\" Generator: Asciidoctor 2.0.10 -.\" Date: 2020-06-02 +.\" Date: 2020-06-06 .\" Manual: Yabai Manual .\" Source: Yabai .\" Language: English .\" -.TH "YABAI" "1" "2020-06-02" "Yabai" "Yabai Manual" +.TH "YABAI" "1" "2020-06-06" "Yabai" "Yabai Manual" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -104,7 +104,7 @@ DIR_SEL := north | east | south | west WINDOW_SEL := prev | next | first | last | recent | mouse | largest | smallest | DIR_SEL | -DISPLAY_SEL := prev | next | first | last | recent | +DISPLAY_SEL := prev | next | first | last | recent | DIR_SEL | SPACE_SEL := prev | next | first | last | recent | | LABEL .fi diff --git a/doc/yabai.asciidoc b/doc/yabai.asciidoc index 3f7a1f72..8b25e6c8 100644 --- a/doc/yabai.asciidoc +++ b/doc/yabai.asciidoc @@ -82,7 +82,7 @@ DIR_SEL := north | east | south | west WINDOW_SEL := prev | next | first | last | recent | mouse | largest | smallest | DIR_SEL | -DISPLAY_SEL := prev | next | first | last | recent | +DISPLAY_SEL := prev | next | first | last | recent | DIR_SEL | SPACE_SEL := prev | next | first | last | recent | | LABEL diff --git a/src/display_manager.c b/src/display_manager.c index 3e59e521..4552526e 100644 --- a/src/display_manager.c +++ b/src/display_manager.c @@ -140,6 +140,58 @@ uint32_t display_manager_last_display_id(void) return display_manager_arrangement_display_id(arrangement); } +uint32_t display_manager_find_closest_display_in_direction(uint32_t source_did, int direction) +{ + uint32_t display_count; + uint32_t *display_list = display_manager_active_display_list(&display_count); + if (!display_list) return 0; + + uint32_t best_did = 0; + int best_distance = INT_MAX; + CGRect source_bounds = display_bounds(source_did); + CGPoint source_point = (CGPoint) { source_bounds.origin.x + source_bounds.size.width/2, source_bounds.origin.y + source_bounds.size.height/2 }; + + for (int i = 0; i < display_count; ++i) { + uint32_t did = display_list[i]; + if (did == source_did) continue; + + CGRect bounds = display_bounds(did); + CGPoint point = (CGPoint) { bounds.origin.x + bounds.size.width/2, bounds.origin.y + bounds.size.height/2 }; + int distance = euclidean_distance(source_point, point); + if (distance >= best_distance) continue; + + switch (direction) { + case DIR_EAST: { + if (bounds.origin.x >= source_bounds.origin.x + source_bounds.size.width) { + best_did = did; + best_distance = distance; + } + } break; + case DIR_SOUTH: { + if (bounds.origin.y >= source_bounds.origin.y + source_bounds.size.height) { + best_did = did; + best_distance = distance; + } + } break; + case DIR_WEST: { + if (bounds.origin.x + bounds.size.width <= source_bounds.origin.x) { + best_did = did; + best_distance = distance; + } + } break; + case DIR_NORTH: { + if (bounds.origin.y + bounds.size.height <= source_bounds.origin.y) { + best_did = did; + best_distance = distance; + } + } break; + } + } + + free(display_list); + return best_did; +} + bool display_manager_menu_bar_hidden(void) { int status = 0; diff --git a/src/display_manager.h b/src/display_manager.h index 92c63b8f..4981975f 100644 --- a/src/display_manager.h +++ b/src/display_manager.h @@ -53,6 +53,7 @@ uint32_t display_manager_prev_display_id(uint32_t did); uint32_t display_manager_next_display_id(uint32_t did); uint32_t display_manager_first_display_id(void); uint32_t display_manager_last_display_id(void); +uint32_t display_manager_find_closest_display_in_direction(uint32_t acting_did, int direction); bool display_manager_menu_bar_hidden(void); CGRect display_manager_menu_bar_rect(uint32_t did); bool display_manager_dock_hidden(void); diff --git a/src/message.c b/src/message.c index a8268a8c..502a250b 100644 --- a/src/message.c +++ b/src/message.c @@ -114,10 +114,6 @@ extern bool g_verbose; #define COMMAND_WINDOW_DISPLAY "--display" #define COMMAND_WINDOW_SPACE "--space" -#define ARGUMENT_WINDOW_DIR_NORTH "north" -#define ARGUMENT_WINDOW_DIR_EAST "east" -#define ARGUMENT_WINDOW_DIR_SOUTH "south" -#define ARGUMENT_WINDOW_DIR_WEST "west" #define ARGUMENT_WINDOW_SEL_MOUSE "mouse" #define ARGUMENT_WINDOW_SEL_LARGEST "largest" #define ARGUMENT_WINDOW_SEL_SMALLEST "smallest" @@ -192,6 +188,10 @@ extern bool g_verbose; #define ARGUMENT_COMMON_SEL_FIRST "first" #define ARGUMENT_COMMON_SEL_LAST "last" #define ARGUMENT_COMMON_SEL_RECENT "recent" +#define ARGUMENT_COMMON_SEL_NORTH "north" +#define ARGUMENT_COMMON_SEL_EAST "east" +#define ARGUMENT_COMMON_SEL_SOUTH "south" +#define ARGUMENT_COMMON_SEL_WEST "west" /* ----------------------------------------------------------------------------- */ static bool token_equals(struct token token, char *match) @@ -821,7 +821,51 @@ static struct selector parse_display_selector(FILE *rsp, char **message, uint32_ .did_parse = true }; - if (token_equals(result.token, ARGUMENT_COMMON_SEL_PREV)) { + if (token_equals(result.token, ARGUMENT_COMMON_SEL_NORTH)) { + if (acting_did) { + uint32_t did = display_manager_find_closest_display_in_direction(acting_did, DIR_NORTH); + if (did) { + result.did = did; + } else { + daemon_fail(rsp, "could not locate a northward display.\n"); + } + } else { + daemon_fail(rsp, "could not locate the selected display.\n"); + } + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_EAST)) { + if (acting_did) { + uint32_t did = display_manager_find_closest_display_in_direction(acting_did, DIR_EAST); + if (did) { + result.did = did; + } else { + daemon_fail(rsp, "could not locate a eastward display.\n"); + } + } else { + daemon_fail(rsp, "could not locate the selected display.\n"); + } + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_SOUTH)) { + if (acting_did) { + uint32_t did = display_manager_find_closest_display_in_direction(acting_did, DIR_SOUTH); + if (did) { + result.did = did; + } else { + daemon_fail(rsp, "could not locate a southward display.\n"); + } + } else { + daemon_fail(rsp, "could not locate the selected display.\n"); + } + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_WEST)) { + if (acting_did) { + uint32_t did = display_manager_find_closest_display_in_direction(acting_did, DIR_WEST); + if (did) { + result.did = did; + } else { + daemon_fail(rsp, "could not locate a westward display.\n"); + } + } else { + daemon_fail(rsp, "could not locate the selected display.\n"); + } + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_PREV)) { if (acting_did) { uint32_t did = display_manager_prev_display_id(acting_did); if (did) { @@ -967,7 +1011,7 @@ static struct selector parse_window_selector(FILE *rsp, char **message, struct w .did_parse = true }; - if (token_equals(result.token, ARGUMENT_WINDOW_DIR_NORTH)) { + if (token_equals(result.token, ARGUMENT_COMMON_SEL_NORTH)) { if (acting_window) { struct window *closest_window = window_manager_find_closest_window_in_direction(&g_window_manager, acting_window, DIR_NORTH); if (closest_window) { @@ -978,7 +1022,7 @@ 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_EAST)) { + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_EAST)) { if (acting_window) { struct window *closest_window = window_manager_find_closest_window_in_direction(&g_window_manager, acting_window, DIR_EAST); if (closest_window) { @@ -989,7 +1033,7 @@ 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_SOUTH)) { + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_SOUTH)) { if (acting_window) { struct window *closest_window = window_manager_find_closest_window_in_direction(&g_window_manager, acting_window, DIR_SOUTH); if (closest_window) { @@ -1000,7 +1044,7 @@ 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_WEST)) { + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_WEST)) { if (acting_window) { struct window *closest_window = window_manager_find_closest_window_in_direction(&g_window_manager, acting_window, DIR_WEST); if (closest_window) { @@ -1107,13 +1151,13 @@ static struct selector parse_dir_selector(FILE *rsp, char **message) .did_parse = true }; - if (token_equals(result.token, ARGUMENT_WINDOW_DIR_NORTH)) { + if (token_equals(result.token, ARGUMENT_COMMON_SEL_NORTH)) { result.dir = DIR_NORTH; - } else if (token_equals(result.token, ARGUMENT_WINDOW_DIR_EAST)) { + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_EAST)) { result.dir = DIR_EAST; - } else if (token_equals(result.token, ARGUMENT_WINDOW_DIR_SOUTH)) { + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_SOUTH)) { result.dir = DIR_SOUTH; - } else if (token_equals(result.token, ARGUMENT_WINDOW_DIR_WEST)) { + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_WEST)) { result.dir = DIR_WEST; } else { result.did_parse = false;