From b7d75d4c13d9a190fa10c368563a4a4477366e9a Mon Sep 17 00:00:00 2001 From: koekeishiya Date: Tue, 27 Feb 2024 20:27:19 +0100 Subject: [PATCH] #2123 rework window rules --- CHANGELOG.md | 8 +- doc/yabai.1 | 32 ++-- doc/yabai.asciidoc | 21 ++- src/event_loop.c | 13 +- src/message.c | 378 ++++++++++++++++++++++--------------------- src/misc/helpers.h | 6 +- src/rule.c | 35 ++-- src/rule.h | 33 ++-- src/window.c | 2 +- src/window_manager.c | 89 ++++++---- src/window_manager.h | 6 +- 11 files changed, 357 insertions(+), 266 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c101f6e..c6391579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Make space visible on display without stealing focus (the space must belong to the display) [#2113](https://github.com/koekeishiya/yabai/issues/2113) - Allow re-applying existing rules to all known windows [#2121](https://github.com/koekeishiya/yabai/issues/2121) - Restore application_activated and application_deactivated signals [#2122](https://github.com/koekeishiya/yabai/issues/2122) -- Rules can now be configured to apply *only once* for all known windows [#2123](https://github.com/koekeishiya/yabai/issues/2123) - Restore system_woke signal [#2124](https://github.com/koekeishiya/yabai/issues/2124) +- Added new window rules command `--apply` to apply the effects of (specific or all existing rules), or an ad-hoc rule that should only apply once, to all known windows [#2123](https://github.com/koekeishiya/yabai/issues/2123) +- Added new argument `--one-shot` to window rules command `--add` to specify that this rule only runs once [#2123](https://github.com/koekeishiya/yabai/issues/2123) +- Window rules marked as `--one-shot` will be ignored completely by the `--apply` command [#2123](https://github.com/koekeishiya/yabai/issues/2123) ### Changed - Preserve relative space ordering when moving spaces to other displays [#2114](https://github.com/koekeishiya/yabai/issues/2114) @@ -20,6 +22,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Managed windows should correct their frame when modified by external means [#2117](https://github.com/koekeishiya/yabai/issues/2117) - Window frame would sometimes not be set correctly when using window animations [#2120](https://github.com/koekeishiya/yabai/issues/2120) - Allow resetting window opacity in window rules [#2127](https://github.com/koekeishiya/yabai/issues/2127) +- When adding new window rules, their effects will only apply to *windows that open after the rule has been added* [#2123](https://github.com/koekeishiya/yabai/issues/2123) + +### Removed +- When adding new window rules, their effects will only apply to *windows that open after the rule has been added*. To restore old behavior, run `yabai -m rule --apply` after adding all rules [#2123](https://github.com/koekeishiya/yabai/issues/2123) ## [6.0.15] - 2024-02-22 ### Changed diff --git a/doc/yabai.1 b/doc/yabai.1 index 25600364..c246b1ea 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.20 -.\" Date: 2024-02-26 +.\" Date: 2024-02-27 .\" Manual: Yabai Manual .\" Source: Yabai .\" Language: English .\" -.TH "YABAI" "1" "2024-02-26" "Yabai" "Yabai Manual" +.TH "YABAI" "1" "2024-02-27" "Yabai" "Yabai Manual" .ie \n(.g .ds Aq \(aq .el .ds Aq ' .ss \n[.ss] 0 @@ -685,9 +685,9 @@ WINDOW .if n .RE .SS "Rule" .sp -All registered rules that match the given filter will apply to a window in the order they were added. +All rules that match the given filter will be applied in the order they were registered. .br -If multiple rules specify a value for the same argument, the latter rule will override that value as it was applied last. +If multiple rules specify a value for the same property, the latter rule will end up overriding that value. .br The following properties require System Integrity Protection to be partially disabled: sticky, layer, opacity. .SS "General Syntax" @@ -697,19 +697,25 @@ yabai \-m rule \fI\fP .sp \fB\-\-add [\-\-one\-shot] [\fI\fP]\fP .RS 4 -Add a new rule. If \fI\-\-one\-shot\fP is present, only apply once to all known windows. +Add a new rule. Rules apply to windows that spawn after said rule has been added. +.br +If \fI\-\-one\-shot\fP is present it will apply once and automatically remove itself. .RE .sp -\fB\-\-remove \fI\fP\fP +\fB\-\-apply [\fI\fP | \fI\fP]\fP .RS 4 -Remove an existing rule with the given index or label. +Apply a rule to currently known windows. +.br +If no argument is given, all existing rules will apply. +.br +If an index or label is given, that particular rule will apply. +.br +Arguments can also be provided directly, just like in the \fB\-\-add\fP command. .RE .sp -\fB\-\-reapply [\fI\fP]\fP +\fB\-\-remove \fI\fP\fP .RS 4 -Apply an existing rule with the given index or label to all known windows. -.br -Will apply all rules if no index or label is specified. +Remove an existing rule with the given index or label. .RE .sp \fB\-\-list\fP @@ -816,7 +822,9 @@ Set window frame based on a self\-defined grid. "mouse_follows_focus": bool (optional), "layer": string, "native\-fullscreen": bool (optional), - "grid": string + "grid": string, + "one\-shot": bool, + "flags": string } .fam .fi diff --git a/doc/yabai.asciidoc b/doc/yabai.asciidoc index c75a3c6a..33214f52 100644 --- a/doc/yabai.asciidoc +++ b/doc/yabai.asciidoc @@ -498,8 +498,8 @@ WINDOW Rule ~~~~ -All registered rules that match the given filter will apply to a window in the order they were added. + -If multiple rules specify a value for the same argument, the latter rule will override that value as it was applied last. + +All rules that match the given filter will be applied in the order they were registered. + +If multiple rules specify a value for the same property, the latter rule will end up overriding that value. + The following properties require System Integrity Protection to be partially disabled: sticky, layer, opacity. General Syntax @@ -511,15 +511,18 @@ COMMAND ^^^^^^^ *--add [--one-shot] ['']*:: - Add a new rule. If '--one-shot' is present, only apply once to all known windows. + Add a new rule. Rules apply to windows that spawn after said rule has been added. + + If '--one-shot' is present it will apply once and automatically remove itself. + +*--apply ['' | '']*:: + Apply a rule to currently known windows. + + If no argument is given, all existing rules will apply. + + If an index or label is given, that particular rule will apply. + + Arguments can also be provided directly, just like in the *--add* command. *--remove ''*:: Remove an existing rule with the given index or label. -*--reapply ['']*:: - Apply an existing rule with the given index or label to all known windows. + - Will apply all rules if no index or label is specified. - *--list*:: Output list of registered rules. @@ -592,7 +595,9 @@ DATAFORMAT "mouse_follows_focus": bool (optional), "layer": string, "native-fullscreen": bool (optional), - "grid": string + "grid": string, + "one-shot": bool, + "flags": string } ---- diff --git a/src/event_loop.c b/src/event_loop.c index 7fcc77d7..5c080523 100644 --- a/src/event_loop.c +++ b/src/event_loop.c @@ -468,9 +468,20 @@ static EVENT_HANDLER(WINDOW_CREATED) struct application *application = window_manager_find_application(&g_window_manager, window_pid); if (!application) { CFRelease(context); return; } - struct window *window = window_manager_create_and_add_window(&g_space_manager, &g_window_manager, application, context, window_id); + struct window *window = window_manager_create_and_add_window(&g_space_manager, &g_window_manager, application, context, window_id, true); if (!window) return; + int rule_len = buf_len(g_window_manager.rules); + for (int i = 0; i < rule_len; ++i) { + if (rule_check_flag(&g_window_manager.rules[i], RULE_ONE_SHOT_REMOVE)) { + rule_destroy(&g_window_manager.rules[i]); + if (buf_del(g_window_manager.rules, i)) { + --i; + --rule_len; + } + } + } + if (window_manager_should_manage_window(window) && !window_manager_find_managed_window(&g_window_manager, window)) { uint64_t sid; diff --git a/src/message.c b/src/message.c index b6e772dc..e81a291f 100644 --- a/src/message.c +++ b/src/message.c @@ -175,7 +175,7 @@ extern bool g_verbose; /* --------------------------------DOMAIN RULE---------------------------------- */ #define COMMAND_RULE_ADD "--add" #define COMMAND_RULE_REM "--remove" -#define COMMAND_RULE_REAPPLY "--reapply" +#define COMMAND_RULE_APPLY "--apply" #define COMMAND_RULE_LS "--list" #define ARGUMENT_RULE_ONE_SHOT "--one-shot" @@ -185,7 +185,7 @@ extern bool g_verbose; #define ARGUMENT_RULE_KEY_SUBROLE "subrole" #define ARGUMENT_RULE_KEY_DISPLAY "display" #define ARGUMENT_RULE_KEY_SPACE "space" -#define ARGUMENT_RULE_KEY_ALPHA "opacity" +#define ARGUMENT_RULE_KEY_OPACITY "opacity" #define ARGUMENT_RULE_KEY_MANAGE "manage" #define ARGUMENT_RULE_KEY_STICKY "sticky" #define ARGUMENT_RULE_KEY_MFF "mouse_follows_focus" @@ -2208,229 +2208,245 @@ static void handle_domain_query(FILE *rsp, struct token domain, char *message) } } -static void handle_domain_rule(FILE *rsp, struct token domain, char *message) +static bool parse_rule(FILE *rsp, char **message, struct rule *rule, struct token token) { - struct token command = get_token(&message); - if (token_equals(command, COMMAND_RULE_ADD)) { - char *unsupported_exclusion = NULL; - bool did_parse = true; - bool has_filter = false; - bool one_shot = false; - struct rule rule = {}; - - struct token token = get_token(&message); - if (token_equals(token, ARGUMENT_RULE_ONE_SHOT)) { - one_shot = true; - token = get_token(&message); + char *unsupported_exclusion = NULL; + bool did_parse = true; + bool has_filter = false; + + while (token_is_valid(token)) { + char *key = NULL; + char *value = NULL; + bool exclusion = false; + get_key_value_pair(token.text, &key, &value, &exclusion); + + if (!key || !value) { + daemon_fail(rsp, "invalid key-value pair '%s'\n", token.text); + did_parse = false; + goto rnext; } - while (token_is_valid(token)) { - char *key = NULL; - char *value = NULL; - bool exclusion = false; - get_key_value_pair(token.text, &key, &value, &exclusion); - - if (!key || !value) { - daemon_fail(rsp, "invalid key-value pair '%s'\n", token.text); + if (string_equals(key, ARGUMENT_RULE_KEY_LABEL)) { + if (exclusion) unsupported_exclusion = key; + rule->label = string_copy(value); + } else if (string_equals(key, ARGUMENT_RULE_KEY_APP)) { + has_filter = true; + rule->app = string_copy(value); + if (exclusion) rule_set_flag(rule, RULE_APP_EXCLUDE); + if (regcomp(&rule->app_regex, value, REG_EXTENDED) == 0) { + rule_set_flag(rule, RULE_APP_VALID); + } else { + daemon_fail(rsp, "invalid regex pattern '%s' for key '%s'\n", value, key); did_parse = false; - goto rnext; } + } else if (string_equals(key, ARGUMENT_RULE_KEY_TITLE)) { + has_filter = true; + rule->title = string_copy(value); + if (exclusion) rule_set_flag(rule, RULE_TITLE_EXCLUDE); + if (regcomp(&rule->title_regex, value, REG_EXTENDED) == 0) { + rule_set_flag(rule, RULE_TITLE_VALID); + } else { + daemon_fail(rsp, "invalid regex pattern '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_ROLE)) { + has_filter = true; + rule->role = string_copy(value); + if (exclusion) rule_set_flag(rule, RULE_ROLE_EXCLUDE); + if (regcomp(&rule->role_regex, value, REG_EXTENDED) == 0) { + rule_set_flag(rule, RULE_ROLE_VALID); + } else { + daemon_fail(rsp, "invalid regex pattern '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_SUBROLE)) { + has_filter = true; + rule->subrole = string_copy(value); + if (exclusion) rule_set_flag(rule, RULE_SUBROLE_EXCLUDE); + if (regcomp(&rule->subrole_regex, value, REG_EXTENDED) == 0) { + rule_set_flag(rule, RULE_SUBROLE_VALID); + } else { + daemon_fail(rsp, "invalid regex pattern '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_DISPLAY)) { + if (exclusion) unsupported_exclusion = key; - if (string_equals(key, ARGUMENT_RULE_KEY_LABEL)) { - if (exclusion) unsupported_exclusion = key; - rule.label = string_copy(value); - } else if (string_equals(key, ARGUMENT_RULE_KEY_APP)) { - has_filter = true; - rule.app = string_copy(value); - rule.app_regex_exclude = exclusion; - rule.app_regex_valid = regcomp(&rule.app_regex, value, REG_EXTENDED) == 0; - if (!rule.app_regex_valid) { - daemon_fail(rsp, "invalid regex pattern '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_TITLE)) { - has_filter = true; - rule.title = string_copy(value); - rule.title_regex_exclude = exclusion; - rule.title_regex_valid = regcomp(&rule.title_regex, value, REG_EXTENDED) == 0; - if (!rule.title_regex_valid) { - daemon_fail(rsp, "invalid regex pattern '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_ROLE)) { - has_filter = true; - rule.role = string_copy(value); - rule.role_regex_exclude = exclusion; - rule.role_regex_valid = regcomp(&rule.role_regex, value, REG_EXTENDED) == 0; - if (!rule.role_regex_valid) { - daemon_fail(rsp, "invalid regex pattern '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_SUBROLE)) { - has_filter = true; - rule.subrole = string_copy(value); - rule.subrole_regex_exclude = exclusion; - rule.subrole_regex_valid = regcomp(&rule.subrole_regex, value, REG_EXTENDED) == 0; - if (!rule.subrole_regex_valid) { - daemon_fail(rsp, "invalid regex pattern '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_DISPLAY)) { - if (exclusion) unsupported_exclusion = key; - - if (value[0] == ARGUMENT_RULE_VALUE_SPACE) { - ++value; - rule.follow_space = true; - } - - struct selector selector = parse_display_selector(rsp, &value, display_manager_active_display_id(), false); - if (selector.did_parse && selector.did) { - rule.did = selector.did; - } else { - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_SPACE)) { - if (exclusion) unsupported_exclusion = key; + if (value[0] == ARGUMENT_RULE_VALUE_SPACE) { + ++value; + rule_set_flag(rule, RULE_FOLLOW_SPACE); + } - if (value[0] == ARGUMENT_RULE_VALUE_SPACE) { - ++value; - rule.follow_space = true; - } + struct selector selector = parse_display_selector(rsp, &value, display_manager_active_display_id(), false); + if (selector.did_parse && selector.did) { + rule->did = selector.did; + } else { + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_SPACE)) { + if (exclusion) unsupported_exclusion = key; - struct selector selector = parse_space_selector(rsp, &value, space_manager_active_space(), false); - if (selector.did_parse && selector.sid) { - rule.sid = selector.sid; - } else { - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_GRID)) { - if (exclusion) unsupported_exclusion = key; + if (value[0] == ARGUMENT_RULE_VALUE_SPACE) { + ++value; + rule_set_flag(rule, RULE_FOLLOW_SPACE); + } - if ((sscanf(value, ARGUMENT_RULE_VALUE_GRID, - &rule.grid[0], &rule.grid[1], - &rule.grid[2], &rule.grid[3], - &rule.grid[4], &rule.grid[5]) != 6)) { - daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_ALPHA)) { - if (exclusion) unsupported_exclusion = key; + struct selector selector = parse_space_selector(rsp, &value, space_manager_active_space(), false); + if (selector.did_parse && selector.sid) { + rule->sid = selector.sid; + } else { + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_GRID)) { + if (exclusion) unsupported_exclusion = key; - if ((sscanf(value, "%f", &rule.alpha) != 1) || (!in_range_ii(rule.alpha, 0.0f, 1.0f))) { - daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_MANAGE)) { - if (exclusion) unsupported_exclusion = key; + if ((sscanf(value, ARGUMENT_RULE_VALUE_GRID, + &rule->grid[0], &rule->grid[1], + &rule->grid[2], &rule->grid[3], + &rule->grid[4], &rule->grid[5]) != 6)) { + daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_OPACITY)) { + if (exclusion) unsupported_exclusion = key; - if (string_equals(value, ARGUMENT_COMMON_VAL_ON)) { - rule.manage = RULE_PROP_ON; - } else if (string_equals(value, ARGUMENT_COMMON_VAL_OFF)) { - rule.manage = RULE_PROP_OFF; - } else { - daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_STICKY)) { - if (exclusion) unsupported_exclusion = key; + if ((sscanf(value, "%f", &rule->opacity) == 1) && (in_range_ii(rule->opacity, 0.0f, 1.0f))) { + rule_set_flag(rule, RULE_OPACITY); + } else { + daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_MANAGE)) { + if (exclusion) unsupported_exclusion = key; - if (string_equals(value, ARGUMENT_COMMON_VAL_ON)) { - rule.sticky = RULE_PROP_ON; - } else if (string_equals(value, ARGUMENT_COMMON_VAL_OFF)) { - rule.sticky = RULE_PROP_OFF; - } else { - daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_MFF)) { - if (exclusion) unsupported_exclusion = key; + if (string_equals(value, ARGUMENT_COMMON_VAL_ON)) { + rule->manage = RULE_PROP_ON; + } else if (string_equals(value, ARGUMENT_COMMON_VAL_OFF)) { + rule->manage = RULE_PROP_OFF; + } else { + daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_STICKY)) { + if (exclusion) unsupported_exclusion = key; - if (string_equals(value, ARGUMENT_COMMON_VAL_ON)) { - rule.mff = RULE_PROP_ON; - } else if (string_equals(value, ARGUMENT_COMMON_VAL_OFF)) { - rule.mff = RULE_PROP_OFF; - } else { - daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_LAYER)) { - if (exclusion) unsupported_exclusion = key; + if (string_equals(value, ARGUMENT_COMMON_VAL_ON)) { + rule->sticky = RULE_PROP_ON; + } else if (string_equals(value, ARGUMENT_COMMON_VAL_OFF)) { + rule->sticky = RULE_PROP_OFF; + } else { + daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_MFF)) { + if (exclusion) unsupported_exclusion = key; - if (string_equals(value, ARGUMENT_WINDOW_LAYER_BELOW)) { - rule.layer = LAYER_BELOW; - } else if (string_equals(value, ARGUMENT_WINDOW_LAYER_NORMAL)) { - rule.layer = LAYER_NORMAL; - } else if (string_equals(value, ARGUMENT_WINDOW_LAYER_ABOVE)) { - rule.layer = LAYER_ABOVE; - } else if (string_equals(value, ARGUMENT_WINDOW_LAYER_AUTO)) { - rule.layer = LAYER_AUTO; - } else { - daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); - did_parse = false; - } - } else if (string_equals(key, ARGUMENT_RULE_KEY_FULLSCR)) { - if (exclusion) unsupported_exclusion = key; + if (string_equals(value, ARGUMENT_COMMON_VAL_ON)) { + rule->mff = RULE_PROP_ON; + } else if (string_equals(value, ARGUMENT_COMMON_VAL_OFF)) { + rule->mff = RULE_PROP_OFF; + } else { + daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_LAYER)) { + if (exclusion) unsupported_exclusion = key; + + if (string_equals(value, ARGUMENT_WINDOW_LAYER_BELOW)) { + rule->layer = LAYER_BELOW; + rule_set_flag(rule, RULE_LAYER); + } else if (string_equals(value, ARGUMENT_WINDOW_LAYER_NORMAL)) { + rule->layer = LAYER_NORMAL; + rule_set_flag(rule, RULE_LAYER); + } else if (string_equals(value, ARGUMENT_WINDOW_LAYER_ABOVE)) { + rule->layer = LAYER_ABOVE; + rule_set_flag(rule, RULE_LAYER); + } else if (string_equals(value, ARGUMENT_WINDOW_LAYER_AUTO)) { + rule->layer = LAYER_AUTO; + rule_set_flag(rule, RULE_LAYER); + } else { + daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); + did_parse = false; + } + } else if (string_equals(key, ARGUMENT_RULE_KEY_FULLSCR)) { + if (exclusion) unsupported_exclusion = key; - if (string_equals(value, ARGUMENT_COMMON_VAL_ON)) { - rule.fullscreen = RULE_PROP_ON; - } else if (string_equals(value, ARGUMENT_COMMON_VAL_OFF)) { - rule.fullscreen = RULE_PROP_OFF; - } else { - daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); - did_parse = false; - } + if (string_equals(value, ARGUMENT_COMMON_VAL_ON)) { + rule->fullscreen = RULE_PROP_ON; + } else if (string_equals(value, ARGUMENT_COMMON_VAL_OFF)) { + rule->fullscreen = RULE_PROP_OFF; } else { - daemon_fail(rsp, "unknown key '%s'\n", key); + daemon_fail(rsp, "invalid value '%s' for key '%s'\n", value, key); did_parse = false; } + } else { + daemon_fail(rsp, "unknown key '%s'\n", key); + did_parse = false; + } rnext: - token = get_token(&message); - } + token = get_token(message); + } - if (!has_filter) { - daemon_fail(rsp, "missing required key-value pair 'app[!]=..' or 'title[!]=..'\n"); - did_parse = false; - } + if (!has_filter) { + daemon_fail(rsp, "missing required key-value pair 'app[!]=..' or 'title[!]=..'\n"); + did_parse = false; + } - if (unsupported_exclusion) { - daemon_fail(rsp, "unsupported token '!' (exclusion) given for key '%s'\n", unsupported_exclusion); - did_parse = false; + if (unsupported_exclusion) { + daemon_fail(rsp, "unsupported token '!' (exclusion) given for key '%s'\n", unsupported_exclusion); + did_parse = false; + } + + return did_parse; +} + +static void handle_domain_rule(FILE *rsp, struct token domain, char *message) +{ + struct token command = get_token(&message); + if (token_equals(command, COMMAND_RULE_ADD)) { + struct rule rule = {}; + + struct token token = get_token(&message); + if (token_equals(token, ARGUMENT_RULE_ONE_SHOT)) { + rule_set_flag(&rule, RULE_ONE_SHOT); + token = get_token(&message); } - if (did_parse && !one_shot) { + if (parse_rule(rsp, &message, &rule, token)) { rule_add(&rule); - } else if (did_parse && one_shot) { - rule_apply(&rule); - rule_destroy(&rule); } else { rule_destroy(&rule); } - } else if (token_equals(command, COMMAND_RULE_REM)) { + } else if (token_equals(command, COMMAND_RULE_APPLY)) { struct token_value value = token_to_value(get_token(&message)); if (value.type == TOKEN_TYPE_INT) { - if (!rule_remove_by_index(value.int_value)) { + if (!rule_reapply_by_index(value.int_value)) { daemon_fail(rsp, "rule with index '%d' not found.\n", value.int_value); } } else if (value.type == TOKEN_TYPE_STRING) { - if (!rule_remove_by_label(value.string_value)) { - daemon_fail(rsp, "rule with label '%s' not found.\n", value.string_value); + if (!rule_reapply_by_label(value.string_value)) { + struct rule rule = {}; + if (parse_rule(rsp, &message, &rule, value.token)) { + rule_apply(&rule); + } + rule_destroy(&rule); } + } else if (value.type == TOKEN_TYPE_INVALID) { + rule_reapply_all(); } else { daemon_fail(rsp, "value '%.*s' is not a valid option for RULE_SEL\n", value.token.length, value.token.text); } - } else if (token_equals(command, COMMAND_RULE_REAPPLY)) { + } else if (token_equals(command, COMMAND_RULE_REM)) { struct token_value value = token_to_value(get_token(&message)); if (value.type == TOKEN_TYPE_INT) { - if (!rule_reapply_by_index(value.int_value)) { + if (!rule_remove_by_index(value.int_value)) { daemon_fail(rsp, "rule with index '%d' not found.\n", value.int_value); } } else if (value.type == TOKEN_TYPE_STRING) { - if (!rule_reapply_by_label(value.string_value)) { + if (!rule_remove_by_label(value.string_value)) { daemon_fail(rsp, "rule with label '%s' not found.\n", value.string_value); } - } else if (value.type == TOKEN_TYPE_INVALID) { - rule_reapply_all(); } else { daemon_fail(rsp, "value '%.*s' is not a valid option for RULE_SEL\n", value.token.length, value.token.text); } diff --git a/src/misc/helpers.h b/src/misc/helpers.h index 4ccc99d9..264edf28 100644 --- a/src/misc/helpers.h +++ b/src/misc/helpers.h @@ -16,10 +16,10 @@ static const char *bool_str[] = { "off", "on" }; static const char *layer_str[] = { - [0] = "unknown", - [LAYER_BELOW] = "below", + [LAYER_AUTO] = "auto", + [LAYER_BELOW] = "below", [LAYER_NORMAL] = "normal", - [LAYER_ABOVE] = "above" + [LAYER_ABOVE] = "above" }; static inline float ease_out_cubic(float t) diff --git a/src/rule.c b/src/rule.c index eead1617..f6d59478 100644 --- a/src/rule.c +++ b/src/rule.c @@ -20,7 +20,9 @@ void rule_serialize(FILE *rsp, struct rule *rule, int index) "\t\"mouse_follows_focus\":%s,\n" "\t\"layer\":\"%s\",\n" "\t\"native-fullscreen\":%s,\n" - "\t\"grid\":\"%d:%d:%d:%d:%d:%d\"\n" + "\t\"grid\":\"%d:%d:%d:%d:%d:%d\",\n" + "\t\"one-shot\":%s,\n" + "\t\"flags\":\"0x%08x\"\n" "}", index, rule->label ? rule->label : "", @@ -30,22 +32,26 @@ void rule_serialize(FILE *rsp, struct rule *rule, int index) rule->subrole ? rule->subrole : "", display_arrangement(rule->did), space_manager_mission_control_index(rule->sid), - json_bool(rule->follow_space), - rule->alpha, + json_bool(rule_check_flag(rule, RULE_FOLLOW_SPACE)), + rule->opacity, json_optional_bool(rule->manage), json_optional_bool(rule->sticky), json_optional_bool(rule->mff), - layer_str[rule->layer], + rule_check_flag(rule, RULE_LAYER) ? layer_str[rule->layer] : "", json_optional_bool(rule->fullscreen), rule->grid[0], rule->grid[1], rule->grid[2], rule->grid[3], - rule->grid[4], rule->grid[5]); + rule->grid[4], rule->grid[5], + json_bool(rule_check_flag(rule, RULE_ONE_SHOT)), + rule->flags); } void rule_reapply_all(void) { for (int i = 0; i < buf_len(g_window_manager.rules); ++i) { - rule_apply(&g_window_manager.rules[i]); + if (!rule_check_flag(&g_window_manager.rules[i], RULE_ONE_SHOT)) { + rule_apply(&g_window_manager.rules[i]); + } } } @@ -53,7 +59,9 @@ bool rule_reapply_by_index(int index) { for (int i = 0; i < buf_len(g_window_manager.rules); ++i) { if (i == index) { - rule_apply(&g_window_manager.rules[i]); + if (!rule_check_flag(&g_window_manager.rules[i], RULE_ONE_SHOT)) { + rule_apply(&g_window_manager.rules[i]); + } return true; } } @@ -65,7 +73,9 @@ bool rule_reapply_by_label(char *label) { for (int i = 0; i < buf_len(g_window_manager.rules); ++i) { if (string_equals(g_window_manager.rules[i].label, label)) { - rule_apply(&g_window_manager.rules[i]); + if (!rule_check_flag(&g_window_manager.rules[i], RULE_ONE_SHOT)) { + rule_apply(&g_window_manager.rules[i]); + } return true; } } @@ -102,7 +112,6 @@ void rule_add(struct rule *rule) { if (rule->label) rule_remove_by_label(rule->label); buf_push(g_window_manager.rules, *rule); - rule_apply(rule); } bool rule_remove_by_index(int index) @@ -133,10 +142,10 @@ bool rule_remove_by_label(char *label) void rule_destroy(struct rule *rule) { - if (rule->app_regex_valid) regfree(&rule->app_regex); - if (rule->title_regex_valid) regfree(&rule->title_regex); - if (rule->role_regex_valid) regfree(&rule->role_regex); - if (rule->subrole_regex_valid) regfree(&rule->subrole_regex); + if (rule_check_flag(rule, RULE_APP_VALID)) regfree(&rule->app_regex); + if (rule_check_flag(rule, RULE_TITLE_VALID)) regfree(&rule->title_regex); + if (rule_check_flag(rule, RULE_ROLE_VALID)) regfree(&rule->role_regex); + if (rule_check_flag(rule, RULE_SUBROLE_VALID)) regfree(&rule->subrole_regex); if (rule->label) free(rule->label); if (rule->app) free(rule->app); diff --git a/src/rule.h b/src/rule.h index 77bd0eec..e0748477 100644 --- a/src/rule.h +++ b/src/rule.h @@ -5,6 +5,27 @@ #define RULE_PROP_ON 1 #define RULE_PROP_OFF 2 +#define rule_check_flag(r, x) ((r)->flags & (x)) +#define rule_clear_flag(r, x) ((r)->flags &= ~(x)) +#define rule_set_flag(r, x) ((r)->flags |= (x)) + +enum rule_flags +{ + RULE_APP_VALID = 1 << 0, + RULE_TITLE_VALID = 1 << 1, + RULE_ROLE_VALID = 1 << 2, + RULE_SUBROLE_VALID = 1 << 3, + RULE_APP_EXCLUDE = 1 << 4, + RULE_TITLE_EXCLUDE = 1 << 5, + RULE_ROLE_EXCLUDE = 1 << 6, + RULE_SUBROLE_EXCLUDE = 1 << 7, + RULE_FOLLOW_SPACE = 1 << 8, + RULE_OPACITY = 1 << 9, + RULE_LAYER = 1 << 10, + RULE_ONE_SHOT = 1 << 11, + RULE_ONE_SHOT_REMOVE = 1 << 12 +}; + struct rule { char *label; @@ -16,24 +37,16 @@ struct rule regex_t title_regex; regex_t role_regex; regex_t subrole_regex; - bool app_regex_valid; - bool title_regex_valid; - bool role_regex_valid; - bool subrole_regex_valid; - bool app_regex_exclude; - bool title_regex_exclude; - bool role_regex_exclude; - bool subrole_regex_exclude; - bool follow_space; uint32_t did; uint64_t sid; - float alpha; + float opacity; int manage; int sticky; int mff; int layer; int fullscreen; unsigned grid[6]; + uint32_t flags; }; void rule_serialize(FILE *rsp, struct rule *rule, int index); diff --git a/src/window.c b/src/window.c index ea8631f2..f73d2524 100644 --- a/src/window.c +++ b/src/window.c @@ -113,7 +113,7 @@ static inline const char *window_layer(int level) if (level == g_layer_below_window_level) return layer_str[LAYER_BELOW]; if (level == g_layer_normal_window_level) return layer_str[LAYER_NORMAL]; if (level == g_layer_above_window_level) return layer_str[LAYER_ABOVE]; - return layer_str[0]; + return "unknown"; } void window_serialize(FILE *rsp, struct window *window) diff --git a/src/window_manager.c b/src/window_manager.c index 37eaae20..903d38a4 100644 --- a/src/window_manager.c +++ b/src/window_manager.c @@ -109,21 +109,21 @@ void window_manager_query_windows_for_displays(FILE *rsp) void window_manager_apply_manage_rule_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, struct rule *rule, char *window_title, char *window_role, char *window_subrole) { - int regex_match_app = rule->app_regex_exclude ? REGEX_MATCH_YES : REGEX_MATCH_NO; - if (regex_match(rule->app_regex_valid, &rule->app_regex, window->application->name) == regex_match_app) return; + int regex_match_app = rule_check_flag(rule, RULE_APP_EXCLUDE) ? REGEX_MATCH_YES : REGEX_MATCH_NO; + if (regex_match(rule_check_flag(rule, RULE_APP_VALID), &rule->app_regex, window->application->name) == regex_match_app) return; - int regex_match_title = rule->title_regex_exclude ? REGEX_MATCH_YES : REGEX_MATCH_NO; - if (regex_match(rule->title_regex_valid, &rule->title_regex, window_title) == regex_match_title) return; + int regex_match_title = rule_check_flag(rule, RULE_TITLE_EXCLUDE) ? REGEX_MATCH_YES : REGEX_MATCH_NO; + if (regex_match(rule_check_flag(rule, RULE_TITLE_VALID), &rule->title_regex, window_title) == regex_match_title) return; - int regex_match_role = rule->role_regex_exclude ? REGEX_MATCH_YES : REGEX_MATCH_NO; - if (regex_match(rule->role_regex_valid, &rule->role_regex, window_role) == regex_match_role) return; + int regex_match_role = rule_check_flag(rule, RULE_ROLE_EXCLUDE) ? REGEX_MATCH_YES : REGEX_MATCH_NO; + if (regex_match(rule_check_flag(rule, RULE_ROLE_VALID), &rule->role_regex, window_role) == regex_match_role) return; - int regex_match_subrole = rule->subrole_regex_exclude ? REGEX_MATCH_YES : REGEX_MATCH_NO; - if (regex_match(rule->subrole_regex_valid, &rule->subrole_regex, window_subrole) == regex_match_subrole) return; + int regex_match_subrole = rule_check_flag(rule, RULE_SUBROLE_EXCLUDE) ? REGEX_MATCH_YES : REGEX_MATCH_NO; + if (regex_match(rule_check_flag(rule, RULE_SUBROLE_VALID), &rule->subrole_regex, window_subrole) == regex_match_subrole) return; if (rule->manage == RULE_PROP_ON) { - if (!rule->role_regex_valid && !string_equals(window_role , "AXWindow")) return; - if (!rule->subrole_regex_valid && !string_equals(window_subrole, "AXStandardWindow")) return; + if (!rule_check_flag(rule, RULE_ROLE_VALID) && !string_equals(window_role , "AXWindow")) return; + if (!rule_check_flag(rule, RULE_SUBROLE_VALID) && !string_equals(window_subrole, "AXStandardWindow")) return; window_rule_set_flag(window, WINDOW_RULE_MANAGED); window_manager_make_window_floating(sm, wm, window, false, true); @@ -131,32 +131,36 @@ void window_manager_apply_manage_rule_to_window(struct space_manager *sm, struct window_rule_clear_flag(window, WINDOW_RULE_MANAGED); window_manager_make_window_floating(sm, wm, window, true, true); } + + if (rule_check_flag(rule, RULE_ONE_SHOT)) { + rule_set_flag(rule, RULE_ONE_SHOT_REMOVE); + } } void window_manager_apply_rule_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, struct rule *rule, char *window_title, char *window_role, char *window_subrole) { - int regex_match_app = rule->app_regex_exclude ? REGEX_MATCH_YES : REGEX_MATCH_NO; - if (regex_match(rule->app_regex_valid, &rule->app_regex, window->application->name) == regex_match_app) return; + int regex_match_app = rule_check_flag(rule, RULE_APP_EXCLUDE) ? REGEX_MATCH_YES : REGEX_MATCH_NO; + if (regex_match(rule_check_flag(rule, RULE_APP_VALID), &rule->app_regex, window->application->name) == regex_match_app) return; - int regex_match_title = rule->title_regex_exclude ? REGEX_MATCH_YES : REGEX_MATCH_NO; - if (regex_match(rule->title_regex_valid, &rule->title_regex, window_title) == regex_match_title) return; + int regex_match_title = rule_check_flag(rule, RULE_TITLE_EXCLUDE) ? REGEX_MATCH_YES : REGEX_MATCH_NO; + if (regex_match(rule_check_flag(rule, RULE_TITLE_VALID), &rule->title_regex, window_title) == regex_match_title) return; - int regex_match_role = rule->role_regex_exclude ? REGEX_MATCH_YES : REGEX_MATCH_NO; - if (regex_match(rule->role_regex_valid, &rule->role_regex, window_role) == regex_match_role) return; + int regex_match_role = rule_check_flag(rule, RULE_ROLE_EXCLUDE) ? REGEX_MATCH_YES : REGEX_MATCH_NO; + if (regex_match(rule_check_flag(rule, RULE_ROLE_VALID), &rule->role_regex, window_role) == regex_match_role) return; - int regex_match_subrole = rule->subrole_regex_exclude ? REGEX_MATCH_YES : REGEX_MATCH_NO; - if (regex_match(rule->subrole_regex_valid, &rule->subrole_regex, window_subrole) == regex_match_subrole) return; + int regex_match_subrole = rule_check_flag(rule, RULE_SUBROLE_EXCLUDE) ? REGEX_MATCH_YES : REGEX_MATCH_NO; + if (regex_match(rule_check_flag(rule, RULE_SUBROLE_VALID), &rule->subrole_regex, window_subrole) == regex_match_subrole) return; if (!window_rule_check_flag(window, WINDOW_RULE_MANAGED)) { - if (!rule->role_regex_valid && !string_equals(window_role , "AXWindow")) return; - if (!rule->subrole_regex_valid && !string_equals(window_subrole, "AXStandardWindow")) return; + if (!rule_check_flag(rule, RULE_ROLE_VALID) && !string_equals(window_role , "AXWindow")) return; + if (!rule_check_flag(rule, RULE_SUBROLE_VALID) && !string_equals(window_subrole, "AXStandardWindow")) return; } if (rule->sid || rule->did) { if (!window_is_fullscreen(window) && !space_is_fullscreen(window_space(window))) { uint64_t sid = rule->did ? display_space_id(rule->did) : rule->sid; window_manager_send_window_to_space(sm, wm, window, sid, true); - if (rule->follow_space || rule->fullscreen == RULE_PROP_ON) { + if (rule_check_flag(rule, RULE_FOLLOW_SPACE) || rule->fullscreen == RULE_PROP_ON) { space_manager_focus_space(sid); } } @@ -180,9 +184,9 @@ void window_manager_apply_rule_to_window(struct space_manager *sm, struct window window_manager_set_window_layer(window, rule->layer); } - if (in_range_ii(rule->alpha, 0.0f, 1.0f)) { - window->opacity = rule->alpha; - window_manager_set_opacity(wm, window, rule->alpha); + if (rule_check_flag(rule, RULE_OPACITY) && in_range_ii(rule->opacity, 0.0f, 1.0f)) { + window->opacity = rule->opacity; + window_manager_set_opacity(wm, window, rule->opacity); } if (rule->fullscreen == RULE_PROP_ON) { @@ -193,19 +197,27 @@ void window_manager_apply_rule_to_window(struct space_manager *sm, struct window if (rule->grid[0] != 0 && rule->grid[1] != 0) { window_manager_apply_grid(sm, wm, window, rule->grid[0], rule->grid[1], rule->grid[2], rule->grid[3], rule->grid[4], rule->grid[5]); } + + if (rule_check_flag(rule, RULE_ONE_SHOT)) { + rule_set_flag(rule, RULE_ONE_SHOT_REMOVE); + } } -void window_manager_apply_manage_rules_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, char *window_title, char *window_role, char *window_subrole) +void window_manager_apply_manage_rules_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, char *window_title, char *window_role, char *window_subrole, bool one_shot_rules) { for (int i = 0; i < buf_len(wm->rules); ++i) { - window_manager_apply_manage_rule_to_window(sm, wm, window, &wm->rules[i], window_title, window_role, window_subrole); + if (one_shot_rules || !rule_check_flag(&wm->rules[i], RULE_ONE_SHOT)) { + window_manager_apply_manage_rule_to_window(sm, wm, window, &wm->rules[i], window_title, window_role, window_subrole); + } } } -void window_manager_apply_rules_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, char *window_title, char *window_role, char *window_subrole) +void window_manager_apply_rules_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, char *window_title, char *window_role, char *window_subrole, bool one_shot_rules) { for (int i = 0; i < buf_len(wm->rules); ++i) { - window_manager_apply_rule_to_window(sm, wm, window, &wm->rules[i], window_title, window_role, window_subrole); + if (one_shot_rules || !rule_check_flag(&wm->rules[i], RULE_ONE_SHOT)) { + window_manager_apply_rule_to_window(sm, wm, window, &wm->rules[i], window_title, window_role, window_subrole); + } } } @@ -1391,7 +1403,7 @@ struct window **window_manager_find_application_windows(struct window_manager *w return window_list; } -struct window *window_manager_create_and_add_window(struct space_manager *sm, struct window_manager *wm, struct application *application, AXUIElementRef window_ref, uint32_t window_id) +struct window *window_manager_create_and_add_window(struct space_manager *sm, struct window_manager *wm, struct application *application, AXUIElementRef window_ref, uint32_t window_id, bool one_shot_rules) { struct window *window = window_create(application, window_ref, window_id); @@ -1440,11 +1452,11 @@ struct window *window_manager_create_and_add_window(struct space_manager *sm, st // no such rule matches this window, it will be ignored if it does not have a role of kAXWindowRole. // - window_manager_apply_manage_rules_to_window(sm, wm, window, window_title, window_role, window_subrole); + window_manager_apply_manage_rules_to_window(sm, wm, window, window_title, window_role, window_subrole, one_shot_rules); if (window_manager_is_window_eligible(window)) { window->is_eligible = true; - window_manager_apply_rules_to_window(sm, wm, window, window_title, window_role, window_subrole); + window_manager_apply_rules_to_window(sm, wm, window, window_title, window_role, window_subrole, one_shot_rules); window_manager_purify_window(wm, window); window_manager_set_window_opacity(wm, window, wm->normal_window_opacity); @@ -1514,10 +1526,21 @@ struct window **window_manager_add_application_windows(struct space_manager *sm, uint32_t window_id = ax_window_id(window_ref); if (!window_id || window_manager_find_window(wm, window_id)) continue; - struct window *window = window_manager_create_and_add_window(sm, wm, application, CFRetain(window_ref), window_id); + struct window *window = window_manager_create_and_add_window(sm, wm, application, CFRetain(window_ref), window_id, true); if (window) list[(*count)++] = window; } + int rule_len = buf_len(wm->rules); + for (int i = 0; i < rule_len; ++i) { + if (rule_check_flag(&wm->rules[i], RULE_ONE_SHOT_REMOVE)) { + rule_destroy(&wm->rules[i]); + if (buf_del(wm->rules, i)) { + --i; + --rule_len; + } + } + } + CFRelease(window_list); return list; } @@ -1579,7 +1602,7 @@ void window_manager_add_existing_application_windows(struct space_manager *sm, s } if (!window_manager_find_window(wm, window_id)) { - window_manager_create_and_add_window(sm, wm, application, CFRetain(window_ref), window_id); + window_manager_create_and_add_window(sm, wm, application, CFRetain(window_ref), window_id, false); } } diff --git a/src/window_manager.h b/src/window_manager.h index f9dc77cc..3453955d 100644 --- a/src/window_manager.h +++ b/src/window_manager.h @@ -100,8 +100,8 @@ void window_manager_query_windows_for_display(FILE *rsp, uint32_t did); void window_manager_query_windows_for_displays(FILE *rsp); void window_manager_apply_manage_rule_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, struct rule *rule, char *window_title, char *window_role, char *window_subrole); void window_manager_apply_rule_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, struct rule *rule, char *window_title, char *window_role, char *window_subrole); -void window_manager_apply_manage_rules_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, char *window_title, char *window_role, char *window_subrole); -void window_manager_apply_rules_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, char *window_title, char *window_role, char *window_subrole); +void window_manager_apply_manage_rules_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, char *window_title, char *window_role, char *window_subrole, bool one_shot_rules); +void window_manager_apply_rules_to_window(struct space_manager *sm, struct window_manager *wm, struct window *window, char *window_title, char *window_role, char *window_subrole, bool one_shot_rules); void window_manager_center_mouse(struct window_manager *wm, struct window *window); bool window_manager_is_window_eligible(struct window *window); bool window_manager_should_manage_window(struct window *window); @@ -175,7 +175,7 @@ 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); -struct window *window_manager_create_and_add_window(struct space_manager *sm, struct window_manager *wm, struct application *application, AXUIElementRef window_ref, uint32_t window_id); +struct window *window_manager_create_and_add_window(struct space_manager *sm, struct window_manager *wm, struct application *application, AXUIElementRef window_ref, uint32_t window_id, bool one_shot_rules); struct window **window_manager_add_application_windows(struct space_manager *sm, struct window_manager *wm, struct application *application, int *count); void window_manager_add_existing_application_windows(struct space_manager *sm, struct window_manager *wm, struct application *application, int refresh_index); enum window_op_error window_manager_apply_grid(struct space_manager *sm, struct window_manager *wm, struct window *window, unsigned r, unsigned c, unsigned x, unsigned y, unsigned w, unsigned h);