diff --git a/src/message.c b/src/message.c index fdaba556..b749bb79 100644 --- a/src/message.c +++ b/src/message.c @@ -51,6 +51,7 @@ extern bool g_verbose; #define COMMAND_CONFIG_MOUSE_ACTION2 "mouse_action2" #define COMMAND_CONFIG_MOUSE_DROP_ACTION "mouse_drop_action" #define COMMAND_CONFIG_EXTERNAL_BAR "external_bar" +#define COMMAND_CONFIG_GRID_COLUMNS "grid_columns" #define SELECTOR_CONFIG_SPACE "--space" @@ -823,6 +824,20 @@ static struct selector parse_space_selector(FILE *rsp, char **message, uint64_t } else { daemon_fail(rsp, "could not locate the selected space.\n"); } + } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_NORTH) + || token_equals(result.token, ARGUMENT_COMMON_SEL_EAST) + || token_equals(result.token, ARGUMENT_COMMON_SEL_SOUTH) + || token_equals(result.token, ARGUMENT_COMMON_SEL_WEST)) { + if (acting_sid) { + uint64_t sid = space_manager_grid_space(acting_sid, result.token.text); + if (sid) { + result.sid = sid; + } else { + daemon_fail(rsp, "could not locate the %s space.\n", result.token.text); + } + } else { + daemon_fail(rsp, "could not locate the selected space.\n"); + } } else if (token_equals(result.token, ARGUMENT_COMMON_SEL_FIRST)) { uint64_t sid = space_manager_first_space(); if (sid) { @@ -1679,6 +1694,31 @@ static void handle_domain_config(FILE *rsp, struct token domain, char *message) } else { fprintf(rsp, "%s:%d:%d\n", external_bar_mode_str[g_display_manager.mode], g_display_manager.top_padding, g_display_manager.bottom_padding); } + } else if (token_equals(command, COMMAND_CONFIG_GRID_COLUMNS)) { + struct token value = get_token(&message); + if (!token_is_valid(value)) { + if (NULL != g_space_manager.grid_columns) { + int count = CFArrayGetCount(g_space_manager.grid_columns); + for (int i = 0; i < count; ++i) { + if (i > 0) { + fprintf(rsp, ","); + } + fprintf(rsp, "%d", CFStringGetIntValue(CFArrayGetValueAtIndex(g_space_manager.grid_columns, i))); + } + fprintf(rsp, "\n"); + } + } else { + if (NULL != g_space_manager.grid_columns) { + CFRelease(g_space_manager.grid_columns); + } + + CFStringRef list = CFStringCreateWithCString(NULL, value.text, kCFStringEncodingUTF8); + CFStringRef separator = CFSTR(","); + // TODO Convert to int list. + g_space_manager.grid_columns = CFStringCreateArrayBySeparatingStrings(NULL, list, separator); + CFRelease(separator); + CFRelease(list); + } } else { daemon_fail(rsp, "unknown command '%.*s' for domain '%.*s'\n", command.length, command.text, domain.length, domain.text); } diff --git a/src/space_manager.c b/src/space_manager.c index 09753861..5f237327 100644 --- a/src/space_manager.c +++ b/src/space_manager.c @@ -608,6 +608,65 @@ uint64_t space_manager_next_space(uint64_t sid) return n_sid != sid ? n_sid : 0; } +uint64_t space_manager_grid_space(uint64_t sid, char *direction) +{ + uint64_t n_sid = 0; + + CFArrayRef display_spaces_ref = SLSCopyManagedDisplaySpaces(g_connection); + int display_spaces_count = CFArrayGetCount(display_spaces_ref); + + for (int i = 0; i < display_spaces_count; ++i) { + CFDictionaryRef display_ref = CFArrayGetValueAtIndex(display_spaces_ref, i); + CFArrayRef spaces_ref = CFDictionaryGetValue(display_ref, CFSTR("Spaces")); + uint64_t spaces_count = CFArrayGetCount(spaces_ref); + + uint64_t grid_columns = INT_MAX; + if (NULL != g_space_manager.grid_columns + && i < CFArrayGetCount(g_space_manager.grid_columns)) { + int value = CFStringGetIntValue(CFArrayGetValueAtIndex(g_space_manager.grid_columns, i)); + if (value > 0) { + grid_columns = value; + } + } + + for (uint64_t j = 0; j < spaces_count; ++j) { + CFDictionaryRef space_ref = CFArrayGetValueAtIndex(spaces_ref, j); + CFNumberRef sid_ref = CFDictionaryGetValue(space_ref, CFSTR("id64")); + CFNumberGetValue(sid_ref, CFNumberGetType(sid_ref), &n_sid); + if (n_sid == sid) { + uint64_t target_space_index = j; + uint64_t focused_column = j%grid_columns; + + if (string_equals(direction, ARGUMENT_COMMON_SEL_NORTH)) { + target_space_index -= grid_columns; + } else if (string_equals(direction, ARGUMENT_COMMON_SEL_EAST)) { + if (focused_column < grid_columns - 1) { + target_space_index += 1; + } + } else if (string_equals(direction, ARGUMENT_COMMON_SEL_SOUTH)) { + target_space_index += grid_columns; + } else if (string_equals(direction, ARGUMENT_COMMON_SEL_WEST)) { + if (0 < focused_column) { + target_space_index -= 1; + } + } + + if (0 <= target_space_index && target_space_index < spaces_count) { + CFDictionaryRef target_space_ref = CFArrayGetValueAtIndex(spaces_ref, target_space_index); + CFNumberRef target_space_id_ref = CFDictionaryGetValue(target_space_ref, CFSTR("id64")); + CFNumberGetValue(target_space_id_ref, CFNumberGetType(target_space_id_ref), &n_sid); + } + + goto out; + } + } + } + +out: + CFRelease(display_spaces_ref); + return n_sid != sid ? n_sid : 0; +} + uint64_t space_manager_first_space(void) { uint64_t sid = 0; @@ -1132,6 +1191,8 @@ void space_manager_begin(struct space_manager *sm) uint32_t *display_list = display_manager_active_display_list(&display_count); if (!display_list) return; + sm->grid_columns = NULL; + for (int i = 0; i < display_count; ++i) { int space_count; uint64_t *space_list = display_space_list(display_list[i], &space_count); diff --git a/src/space_manager.h b/src/space_manager.h index 7a6ea5ab..33623386 100644 --- a/src/space_manager.h +++ b/src/space_manager.h @@ -26,6 +26,7 @@ struct space_manager bool window_zoom_persist; uint32_t auto_balance; struct space_label *labels; + CFArrayRef grid_columns; }; enum space_op_error @@ -63,6 +64,7 @@ uint64_t space_manager_mission_control_space(int desktop_id); uint64_t space_manager_cursor_space(void); uint64_t space_manager_prev_space(uint64_t sid); uint64_t space_manager_next_space(uint64_t sid); +uint64_t space_manager_grid_space(uint64_t sid, char *direction); uint64_t space_manager_first_space(void); uint64_t space_manager_last_space(void); uint64_t space_manager_active_space(void); diff --git a/wiki/grid-spaces.md b/wiki/grid-spaces.md new file mode 100644 index 00000000..ed2045a6 --- /dev/null +++ b/wiki/grid-spaces.md @@ -0,0 +1,30 @@ +# yabai grid spaces + +Grid space navigation (like [TotalSpaces2](https://totalspaces.binaryage.com/)) in +[yabai](https://github.com/koekeishiya/yabai). + +## Installation + +Install by running + +```shell +brew install --HEAD mikkelricky/formulae/myabai +``` + +## Configuration + +```shell +# Navigate a grid with 3 columns. +yabai --message config grid_columns 3 +``` + +![grid spaces](./images/yabai-grid_columns.svg) + +## Usage + +```shell +yabai --message space --focus north +yabai --message space --focus east +yabai --message space --focus south +yabai --message space --focus west +``` diff --git a/wiki/images/yabai-grid_columns.svg b/wiki/images/yabai-grid_columns.svg new file mode 100644 index 00000000..f7b79433 --- /dev/null +++ b/wiki/images/yabai-grid_columns.svg @@ -0,0 +1,295 @@ + + + +southgrid_columns: 3 (with 9 spaces)southnortheastwestwestsouthnorth