diff --git a/CHANGELOG.md b/CHANGELOG.md index a08f6dd6..b4b7f3f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- New window commands `--minimize` and `--deminimize`. Minimized windows are now reported through window queries and there is a new attribute `minimized` to identify the current state [#379](https://github.com/koekeishiya/yabai/issues/379) ## [2.4.3] - 2020-04-14 ### Changed diff --git a/doc/yabai.1 b/doc/yabai.1 index 8c02359e..b54baff5 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-03-03 +.\" Date: 2020-04-20 .\" Manual: Yabai Manual .\" Source: Yabai .\" Language: English .\" -.TH "YABAI" "1" "2020-03-03" "Yabai" "Yabai Manual" +.TH "YABAI" "1" "2020-04-20" "Yabai" "Yabai Manual" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -475,6 +475,18 @@ Send the selected window to the given display. Send the selected window to the given space. .RE .sp +\fB\-\-minimize\fP +.RS 4 +Minimizes the selected window. Only works on windows that provide a minimize button in its titlebar. +.RE +.sp +\fB\-\-deminimize\fP +.RS 4 +Restores the selected window, if it is minimized. +.br +Note that you can also \fI\-\-focus\fP a minimized window to restore it. +.RE +.sp \fB\-\-close\fP .RS 4 Closes the selected window. Only works on windows that provide a close button in its titlebar. diff --git a/doc/yabai.asciidoc b/doc/yabai.asciidoc index 71665ca7..c9bd33ae 100644 --- a/doc/yabai.asciidoc +++ b/doc/yabai.asciidoc @@ -340,6 +340,13 @@ COMMAND *--space* '':: Send the selected window to the given space. +*--minimize*:: + Minimizes the selected window. Only works on windows that provide a minimize button in its titlebar. + +*--deminimize*:: + Restores the selected window, if it is minimized. + + Note that you can also '--focus' a minimized window to restore it. + *--close*:: Closes the selected window. Only works on windows that provide a close button in its titlebar. diff --git a/src/display_manager.c b/src/display_manager.c index 35fe49e5..30569ea9 100644 --- a/src/display_manager.c +++ b/src/display_manager.c @@ -246,7 +246,7 @@ void display_manager_focus_display(uint32_t did) CGPoint point; AXUIElementRef element_ref; - window_list = space_window_list(display_space_id(did), &window_count); + window_list = space_window_list(display_space_id(did), &window_count, false); if (!window_list) goto fallback; for (int i = 0; i < window_count; ++i) { diff --git a/src/message.c b/src/message.c index bf55f535..13c9943a 100644 --- a/src/message.c +++ b/src/message.c @@ -122,6 +122,8 @@ extern bool g_verbose; #define COMMAND_WINDOW_MOVE "--move" #define COMMAND_WINDOW_RESIZE "--resize" #define COMMAND_WINDOW_RATIO "--ratio" +#define COMMAND_WINDOW_MIN "--minimize" +#define COMMAND_WINDOW_DEMIN "--deminimize" #define COMMAND_WINDOW_CLOSE "--close" #define COMMAND_WINDOW_LAYER "--layer" #define COMMAND_WINDOW_TOGGLE "--toggle" @@ -909,7 +911,7 @@ static char *parse_label(FILE *rsp, char **message, enum label_type type) struct token value = get_token(message); if ((!token_is_valid(value)) || (value.text[0] >= '0' && value.text[0] <= '9')) { - daemon_fail(rsp, "'%.*s' is not a valid label\n", value.length, value.text); + daemon_fail(rsp, "'%.*s' is not a valid label.\n", value.length, value.text); return NULL; } @@ -919,7 +921,7 @@ static char *parse_label(FILE *rsp, char **message, enum label_type type) case LABEL_SPACE: { for (int i = 0; i < array_count(reserved_space_identifiers); ++i) { if (token_equals(value, reserved_space_identifiers[i])) { - daemon_fail(rsp, "'%.*s' is a reserved keyword and cannot be used as a label\n", value.length, value.text); + daemon_fail(rsp, "'%.*s' is a reserved keyword and cannot be used as a label.\n", value.length, value.text); return NULL; } } @@ -1600,7 +1602,7 @@ static void handle_domain_window(FILE *rsp, struct token domain, char *message) if (result == WINDOW_OP_ERROR_INVALID_SRC_NODE) { daemon_fail(rsp, "cannot locate bsp node for the managed window.\n"); } else if (result == WINDOW_OP_ERROR_INVALID_DST_NODE) { - daemon_fail(rsp, "cannot locate a bsp node fence"); + daemon_fail(rsp, "cannot locate a bsp node fence.\n"); } } else { daemon_fail(rsp, "unknown value '%.*s' given to command '%.*s' for domain '%.*s'\n", value.length, value.text, command.length, command.text, domain.length, domain.text); @@ -1614,14 +1616,30 @@ static void handle_domain_window(FILE *rsp, struct token domain, char *message) if (result == WINDOW_OP_ERROR_INVALID_SRC_VIEW) { daemon_fail(rsp, "cannot adjust ratio of a non-managed window.\n"); } else if (result == WINDOW_OP_ERROR_INVALID_SRC_NODE) { - daemon_fail(rsp, "cannot adjust ratio of a root node\n"); + daemon_fail(rsp, "cannot adjust ratio of a root node.\n"); } } else { daemon_fail(rsp, "unknown value '%.*s' given to command '%.*s' for domain '%.*s'\n", value.length, value.text, command.length, command.text, domain.length, domain.text); } + } else if (token_equals(command, COMMAND_WINDOW_MIN)) { + enum window_op_error result = window_manager_minimize_window(acting_window); + if (result == WINDOW_OP_ERROR_CANT_MINIMIZE) { + daemon_fail(rsp, "window with id '%d' does not support the minimize operation.\n", acting_window->id); + } else if (result == WINDOW_OP_ERROR_ALREADY_MINIMIZED) { + daemon_fail(rsp, "window with id '%d' is already minimized.\n", acting_window->id); + } else if (result == WINDOW_OP_ERROR_MINIMIZE_FAILED) { + daemon_fail(rsp, "could not minimize window with id '%d'.\n", acting_window->id); + } + } else if (token_equals(command, COMMAND_WINDOW_DEMIN)) { + enum window_op_error result = window_manager_deminimize_window(acting_window); + if (result == WINDOW_OP_ERROR_NOT_MINIMIZED) { + daemon_fail(rsp, "window with id '%d' is not minimized.\n", acting_window->id); + } else if (result == WINDOW_OP_ERROR_DEMINIMIZE_FAILED) { + daemon_fail(rsp, "could not deminimize window with id '%d'.\n", acting_window->id); + } } else if (token_equals(command, COMMAND_WINDOW_CLOSE)) { if (!window_manager_close_window(acting_window)) { - daemon_fail(rsp, "could not close window with id '%d'\n", acting_window->id); + daemon_fail(rsp, "could not close window with id '%d'.\n", acting_window->id); } } else if (token_equals(command, COMMAND_WINDOW_LAYER)) { struct token value = get_token(&message); @@ -1733,7 +1751,7 @@ static void handle_domain_query(FILE *rsp, struct token domain, char *message) display_serialize(rsp, window_display_id(acting_window)); fprintf(rsp, "\n"); } else { - daemon_fail(rsp, "could not find window to retrieve display details\n"); + daemon_fail(rsp, "could not find window to retrieve display details.\n"); } } } else if (token_is_valid(option)) { @@ -1749,14 +1767,14 @@ static void handle_domain_query(FILE *rsp, struct token domain, char *message) if (selector.did_parse || token_is_valid(selector.token)) { if (selector.did) { if (!space_manager_query_spaces_for_display(rsp, selector.did)) { - daemon_fail(rsp, "could not retrieve spaces for display\n"); + daemon_fail(rsp, "could not retrieve spaces for display.\n"); } } else { daemon_fail(rsp, "could not locate the selected display.\n"); } } else { if (!space_manager_query_spaces_for_display(rsp, acting_did)) { - daemon_fail(rsp, "could not retrieve spaces for display\n"); + daemon_fail(rsp, "could not retrieve spaces for display.\n"); } } } else if (token_equals(option, ARGUMENT_QUERY_SPACE)) { @@ -1776,7 +1794,7 @@ static void handle_domain_query(FILE *rsp, struct token domain, char *message) } } else { if (!space_manager_query_active_space(rsp)) { - daemon_fail(rsp, "could not retrieve active space\n"); + daemon_fail(rsp, "could not retrieve active space.\n"); } } } else if (token_equals(option, ARGUMENT_QUERY_WINDOW)) { @@ -1792,14 +1810,14 @@ static void handle_domain_query(FILE *rsp, struct token domain, char *message) if (acting_window) { space_manager_query_spaces_for_window(rsp, acting_window); } else { - daemon_fail(rsp, "could not find window to retrieve space details\n"); + daemon_fail(rsp, "could not find window to retrieve space details.\n"); } } } else if (token_is_valid(option)) { daemon_fail(rsp, "unknown option '%.*s' given to command '%.*s' for domain '%.*s'\n", option.length, option.text, command.length, command.text, domain.length, domain.text); } else { if (!space_manager_query_spaces_for_displays(rsp)) { - daemon_fail(rsp, "could not retrieve spaces for displays\n"); + daemon_fail(rsp, "could not retrieve spaces for displays.\n"); } } } else if (token_equals(command, COMMAND_QUERY_WINDOWS)) { @@ -1843,7 +1861,7 @@ static void handle_domain_query(FILE *rsp, struct token domain, char *message) window_serialize(rsp, acting_window); fprintf(rsp, "\n"); } else { - daemon_fail(rsp, "could not retrieve window details\n"); + daemon_fail(rsp, "could not retrieve window details.\n"); } } } else if (token_is_valid(option)) { diff --git a/src/space.c b/src/space.c index a2512e5a..15213016 100644 --- a/src/space.c +++ b/src/space.c @@ -21,14 +21,15 @@ uint32_t space_display_id(uint64_t sid) return id; } -uint32_t *space_window_list_for_connection(uint64_t sid, int cid, int *count) +uint32_t *space_window_list_for_connection(uint64_t sid, int cid, int *count, bool include_minimized) { uint32_t *window_list = NULL; uint64_t set_tags = 0; uint64_t clear_tags = 0; + uint32_t options = include_minimized ? 0x7 : 0x2; CFArrayRef space_list_ref = cfarray_of_cfnumbers(&sid, sizeof(uint64_t), 1, kCFNumberSInt64Type); - CFArrayRef window_list_ref = SLSCopyWindowsWithOptionsAndTags(g_connection, cid, space_list_ref, 0x2, &set_tags, &clear_tags); + CFArrayRef window_list_ref = SLSCopyWindowsWithOptionsAndTags(g_connection, cid, space_list_ref, options, &set_tags, &clear_tags); if (!window_list_ref) goto err; *count = CFArrayGetCount(window_list_ref); @@ -48,9 +49,9 @@ uint32_t *space_window_list_for_connection(uint64_t sid, int cid, int *count) return window_list; } -uint32_t *space_window_list(uint64_t sid, int *count) +uint32_t *space_window_list(uint64_t sid, int *count, bool include_minimized) { - return space_window_list_for_connection(sid, 0, count); + return space_window_list_for_connection(sid, 0, count, include_minimized); } CFStringRef space_uuid(uint64_t sid) diff --git a/src/space.h b/src/space.h index bc83de10..d80825de 100644 --- a/src/space.h +++ b/src/space.h @@ -8,8 +8,8 @@ extern CFArrayRef SLSCopyWindowsWithOptionsAndTags(int cid, uint32_t owner, CFAr CFStringRef space_display_uuid(uint64_t sid); uint32_t space_display_id(uint64_t sid); -uint32_t *space_window_list_for_connection(uint64_t sid, int cid, int *count); -uint32_t *space_window_list(uint64_t sid, int *count); +uint32_t *space_window_list_for_connection(uint64_t sid, int cid, int *count, bool include_minimized); +uint32_t *space_window_list(uint64_t sid, int *count, bool include_minimized); CFStringRef space_uuid(uint64_t sid); int space_type(uint64_t sid); bool space_is_user(uint64_t sid); diff --git a/src/view.c b/src/view.c index 7b1ddcad..bdb247bb 100644 --- a/src/view.c +++ b/src/view.c @@ -468,7 +468,7 @@ void view_serialize(FILE *rsp, struct view *view) uint32_t windows[MAXLEN] = {}; int window_count = 0; - uint32_t *window_list = space_window_list(view->sid, &window_count); + uint32_t *window_list = space_window_list(view->sid, &window_count, true); if (window_list) { for (int i = 0; i < window_count; ++i) { if (window_manager_find_window(&g_window_manager, window_list[i])) { diff --git a/src/window.c b/src/window.c index 1097c5bc..48dccaba 100644 --- a/src/window.c +++ b/src/window.c @@ -116,6 +116,7 @@ void window_serialize(FILE *rsp, struct window *window) int display = display_arrangement(space_display_id(sid)); bool visible = sticky || space_is_visible(sid); bool is_topmost = window_is_topmost(window); + bool is_minimized = window_is_minimized(window); CFStringRef cfrole = window_role(window); if (cfrole) { @@ -156,6 +157,7 @@ void window_serialize(FILE *rsp, struct window *window) "\t\"split\":\"%s\",\n" "\t\"floating\":%d,\n" "\t\"sticky\":%d,\n" + "\t\"minimized\":%d,\n" "\t\"topmost\":%d,\n" "\t\"border\":%d,\n" "\t\"shadow\":%d,\n" @@ -181,6 +183,7 @@ void window_serialize(FILE *rsp, struct window *window) split, window->is_floating, sticky, + is_minimized, is_topmost, window->border.enabled, window->has_shadow, @@ -260,6 +263,15 @@ bool window_can_resize(struct window *window) return result; } +bool window_can_minimize(struct window *window) +{ + Boolean result; + if (AXUIElementIsAttributeSettable(window->ref, kAXMinimizedAttribute, &result) != kAXErrorSuccess) { + result = 0; + } + return result; +} + bool window_is_undersized(struct window *window) { CGRect frame = window_frame(window); diff --git a/src/window.h b/src/window.h index 3f0954d5..2614e83a 100644 --- a/src/window.h +++ b/src/window.h @@ -60,6 +60,7 @@ CFStringRef window_role(struct window *window); CFStringRef window_subrole(struct window *window); bool window_can_move(struct window *window); bool window_can_resize(struct window *window); +bool window_can_minimize(struct window *window); bool window_level_is_standard(struct window *window); bool window_is_undersized(struct window *window); bool window_is_minimized(struct window *window); diff --git a/src/window_manager.c b/src/window_manager.c index 537eed2f..e16b9c8d 100644 --- a/src/window_manager.c +++ b/src/window_manager.c @@ -17,7 +17,7 @@ static TABLE_COMPARE_FUNC(compare_wm) void window_manager_query_windows_for_space(FILE *rsp, uint64_t sid) { int window_count; - uint32_t *window_list = space_window_list(sid, &window_count); + uint32_t *window_list = space_window_list(sid, &window_count, true); if (!window_list) return; struct window **window_aggregate_list = NULL; @@ -47,7 +47,7 @@ void window_manager_query_windows_for_display(FILE *rsp, uint32_t did) struct window **window_aggregate_list = NULL; for (int i = 0; i < space_count; ++i) { int window_count; - uint32_t *window_list = space_window_list(space_list[i], &window_count); + uint32_t *window_list = space_window_list(space_list[i], &window_count, true); if (!window_list) continue; for (int j = 0; j < window_count; ++j) { @@ -84,7 +84,7 @@ void window_manager_query_windows_for_displays(FILE *rsp) for (int j = 0; j < space_count; ++j) { int window_count; - uint32_t *window_list = space_window_list(space_list[j], &window_count); + uint32_t *window_list = space_window_list(space_list[j], &window_count, true); if (!window_list) continue; for (int k = 0; k < window_count; ++k) { @@ -550,7 +550,7 @@ void window_manager_set_layer(uint32_t wid, int layer) static void window_manager_set_layer_for_children(int cid, uint32_t wid, uint64_t sid, int layer) { int count; - uint32_t *window_list = space_window_list_for_connection(sid, cid, &count); + uint32_t *window_list = space_window_list_for_connection(sid, cid, &count, false); if (!window_list) return; CFArrayRef window_list_ref = cfarray_of_cfnumbers(window_list, sizeof(uint32_t), count, kCFNumberSInt32Type); @@ -631,7 +631,7 @@ void window_manager_purify_window(struct window_manager *wm, struct window *wind static struct window *window_manager_find_window_on_space_by_rank(struct window_manager *wm, uint64_t sid, int rank) { int count; - uint32_t *window_list = space_window_list(sid, &count); + uint32_t *window_list = space_window_list(sid, &count, false); if (!window_list) return NULL; struct window *result = NULL; @@ -716,7 +716,7 @@ struct window *window_manager_find_closest_managed_window_in_direction(struct wi struct window *window_manager_find_closest_window_in_direction(struct window_manager *wm, struct window *window, int direction) { int window_count; - uint32_t *window_list = space_window_list(display_space_id(window_display_id(window)), &window_count); + uint32_t *window_list = space_window_list(display_space_id(window_display_id(window)), &window_count, false); if (!window_list) return NULL; struct window *result = window_manager_find_closest_window_for_direction_in_window_list(wm, window, direction, window_list, window_count); @@ -1257,6 +1257,23 @@ enum window_op_error window_manager_swap_window(struct space_manager *sm, struct return WINDOW_OP_ERROR_SUCCESS; } +enum window_op_error window_manager_minimize_window(struct window *window) +{ + if (!window_can_minimize(window)) return WINDOW_OP_ERROR_CANT_MINIMIZE; + if (window->is_minimized) return WINDOW_OP_ERROR_ALREADY_MINIMIZED; + + AXError result = AXUIElementSetAttributeValue(window->ref, kAXMinimizedAttribute, kCFBooleanTrue); + return result == kAXErrorSuccess ? WINDOW_OP_ERROR_SUCCESS : WINDOW_OP_ERROR_MINIMIZE_FAILED; +} + +enum window_op_error window_manager_deminimize_window(struct window *window) +{ + if (!window->is_minimized) return WINDOW_OP_ERROR_NOT_MINIMIZED; + + AXError result = AXUIElementSetAttributeValue(window->ref, kAXMinimizedAttribute, kCFBooleanFalse); + return result == kAXErrorSuccess ? WINDOW_OP_ERROR_SUCCESS : WINDOW_OP_ERROR_DEMINIMIZE_FAILED; +} + bool window_manager_close_window(struct window *window) { CFTypeRef button = NULL; @@ -1514,7 +1531,7 @@ void window_manager_toggle_window_pip(struct space_manager *sm, struct window_ma void window_manager_validate_windows_on_space(struct space_manager *sm, struct window_manager *wm, uint64_t sid) { int window_count; - uint32_t *window_list = space_window_list(sid, &window_count); + uint32_t *window_list = space_window_list(sid, &window_count, false); if (!window_list) return; struct view *view = space_manager_find_view(sm, sid); @@ -1547,7 +1564,7 @@ void window_manager_validate_windows_on_space(struct space_manager *sm, struct w void window_manager_check_for_windows_on_space(struct space_manager *sm, struct window_manager *wm, uint64_t sid) { int window_count; - uint32_t *window_list = space_window_list(sid, &window_count); + uint32_t *window_list = space_window_list(sid, &window_count, false); if (!window_list) return; for (int i = 0; i < window_count; ++i) { diff --git a/src/window_manager.h b/src/window_manager.h index 2dab2598..c693294b 100644 --- a/src/window_manager.h +++ b/src/window_manager.h @@ -26,7 +26,12 @@ enum window_op_error WINDOW_OP_ERROR_INVALID_SRC_NODE, WINDOW_OP_ERROR_INVALID_DST_VIEW, WINDOW_OP_ERROR_INVALID_DST_NODE, - WINDOW_OP_ERROR_SAME_WINDOW + WINDOW_OP_ERROR_SAME_WINDOW, + WINDOW_OP_ERROR_CANT_MINIMIZE, + WINDOW_OP_ERROR_ALREADY_MINIMIZED, + WINDOW_OP_ERROR_MINIMIZE_FAILED, + WINDOW_OP_ERROR_NOT_MINIMIZED, + WINDOW_OP_ERROR_DEMINIMIZE_FAILED, }; enum purify_mode @@ -146,6 +151,8 @@ void window_manager_set_window_opacity(struct window_manager *wm, struct window enum window_op_error window_manager_set_window_insertion(struct space_manager *sm, struct window_manager *wm, struct window *window, int direction); enum window_op_error window_manager_warp_window(struct space_manager *sm, struct window_manager *wm, struct window *a, struct window *b); enum window_op_error window_manager_swap_window(struct space_manager *sm, struct window_manager *wm, struct window *a, struct window *b); +enum window_op_error window_manager_minimize_window(struct window *window); +enum window_op_error window_manager_deminimize_window(struct window *window); bool window_manager_close_window(struct window *window); void window_manager_send_window_to_space(struct space_manager *sm, struct window_manager *wm, struct window *window, uint64_t sid, bool moved_by_rule); void window_manager_add_application_windows(struct space_manager *sm, struct window_manager *wm, struct application *application);