From 3b0c26d149dfe5e05df338692db8255a01f0998d Mon Sep 17 00:00:00 2001 From: Ryan Dwyer Date: Wed, 9 May 2018 14:23:20 +1000 Subject: [PATCH 1/6] Overhaul criteria implementation The criteria struct now uses properties for each token type rather than the list_t list of tokens. The reason for this is that different token types have different data types: pcre, string and number to name a few. This solution should be more flexible moving forward. A bonus of this is that criteria is now easier to understand when looking at the struct definition. The criteria parser has been rewritten because the previous one didn't support valueless pairs (eg. [class="foo" floating]). Criteria now has types. Types at the moment are CT_COMMAND, CT_ASSIGN_WORKSPACE and CT_ASSIGN_OUTPUT. i3 uses types as well. Previously the assign command was creating a criteria with 'move to workspace ' as its command, but this caused the window to appear briefly on the focused workspace before being moved to the assigned workspace. It now creates the view directly in the assigned workspace. Each view will only execute a given criteria once. This is achieved by storing a list of executed criteria in the view. This is the same strategy used by i3. Escaping now works properly. Previously you could do things like [class="Fire\"fox"] and the stored value would be 'Fire\"fox', but it should be (and now is) 'Fire"fox'. The public functions in criteria.c are now all prefixed with criteria_. Xwayland views now listen to the set_title, set_class and set_window_type events and criteria will be run when these happen. XDG shell has none of these events so it continues to update the title in handle_commit. Each view type's get_prop function has been split into get_string_prop and get_int_prop because some properties like the X11 window ID and window type are numeric. The following new criteria tokens are now supported: * id (X11 window ID) * instance * tiling * workspace --- include/sway/criteria.h | 77 ++-- include/sway/tree/view.h | 21 +- sway/commands.c | 42 +- sway/commands/assign.c | 60 ++- sway/commands/for_window.c | 35 +- sway/criteria.c | 749 +++++++++++++++++------------------- sway/desktop/wl_shell.c | 4 +- sway/desktop/xdg_shell_v6.c | 4 +- sway/desktop/xwayland.c | 69 +++- sway/tree/view.c | 97 +++-- 10 files changed, 616 insertions(+), 542 deletions(-) diff --git a/include/sway/criteria.h b/include/sway/criteria.h index ec256ddbd..74da132c6 100644 --- a/include/sway/criteria.h +++ b/include/sway/criteria.h @@ -1,42 +1,61 @@ #ifndef _SWAY_CRITERIA_H #define _SWAY_CRITERIA_H -#include "tree/container.h" +#include #include "list.h" +#include "tree/view.h" -/** - * Maps criteria (as a list of criteria tokens) to a command list. - * - * A list of tokens together represent a single criteria string (e.g. - * '[class="abc" title="xyz"]' becomes two criteria tokens). - * - * for_window: Views matching all criteria will have the bound command list - * executed on them. - * - * Set via `for_window `. - */ -struct criteria { - list_t *tokens; // struct crit_token, contains compiled regex. - char *crit_raw; // entire criteria string (for logging) - - char *cmdlist; +enum criteria_type { + CT_COMMAND = 1 << 0, + CT_ASSIGN_OUTPUT = 1 << 1, + CT_ASSIGN_WORKSPACE = 1 << 2, }; -int criteria_cmp(const void *item, const void *data); -void free_criteria(struct criteria *crit); +struct criteria { + enum criteria_type type; + char *raw; // entire criteria string (for logging) + char *cmdlist; + char *target; // workspace or output name for `assign` criteria -// Pouplate list with crit_tokens extracted from criteria string, returns error -// string or NULL if successful. -char *extract_crit_tokens(list_t *tokens, const char *criteria); + pcre *title; + pcre *app_id; + pcre *class; + pcre *instance; + pcre *con_mark; + uint32_t con_id; // internal ID + uint32_t id; // X11 window ID + pcre *window_role; + uint32_t window_type; + bool floating; + bool tiling; + char urgent; // 'l' for latest or 'o' for oldest + char *workspace; +}; -// Returns list of criteria that match given container. These criteria have -// been set with `for_window` commands and have an associated cmdlist. -list_t *criteria_for(struct sway_container *cont); +bool criteria_is_empty(struct criteria *criteria); -// Returns a list of all containers that match the given list of tokens. -list_t *container_for_crit_tokens(list_t *tokens); +void criteria_destroy(struct criteria *criteria); -// Returns true if any criteria in the given list matches this container -bool criteria_any(struct sway_container *cont, list_t *criteria); +/** + * Generate a criteria struct from a raw criteria string such as + * [class="foo" instance="bar"] (brackets inclusive). + * + * The error argument is expected to be an address of a null pointer. If an + * error is encountered, the function will return NULL and the pointer will be + * changed to point to the error string. This string should be freed afterwards. + */ +struct criteria *criteria_parse(char *raw, char **error); + +/** + * Compile a list of criterias matching the given view. + * + * Criteria types can be bitwise ORed. + */ +list_t *criteria_for_view(struct sway_view *view, enum criteria_type types); + +/** + * Compile a list of views matching the given criteria. + */ +list_t *criteria_get_views(struct criteria *criteria); #endif diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 4ecd8c44d..144ad038b 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -20,11 +20,15 @@ enum sway_view_prop { VIEW_PROP_APP_ID, VIEW_PROP_CLASS, VIEW_PROP_INSTANCE, + VIEW_PROP_WINDOW_TYPE, + VIEW_PROP_WINDOW_ROLE, + VIEW_PROP_X11_WINDOW_ID, }; struct sway_view_impl { - const char *(*get_prop)(struct sway_view *view, + const char *(*get_string_prop)(struct sway_view *view, enum sway_view_prop prop); + uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop); void (*configure)(struct sway_view *view, double ox, double oy, int width, int height); void (*set_activated)(struct sway_view *view, bool activated); @@ -52,6 +56,8 @@ struct sway_view { enum sway_container_border border; int border_thickness; + list_t *executed_criteria; + union { struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6; struct wlr_xwayland_surface *wlr_xwayland_surface; @@ -91,6 +97,9 @@ struct sway_xwayland_view { struct wl_listener request_maximize; struct wl_listener request_configure; struct wl_listener request_fullscreen; + struct wl_listener set_title; + struct wl_listener set_class; + struct wl_listener set_window_type; struct wl_listener map; struct wl_listener unmap; struct wl_listener destroy; @@ -165,6 +174,10 @@ const char *view_get_class(struct sway_view *view); const char *view_get_instance(struct sway_view *view); +uint32_t view_get_x11_window_id(struct sway_view *view); + +uint32_t view_get_window_type(struct sway_view *view); + const char *view_get_type(struct sway_view *view); void view_configure(struct sway_view *view, double ox, double oy, int width, @@ -217,4 +230,10 @@ void view_child_destroy(struct sway_view_child *child); */ void view_update_title(struct sway_view *view, bool force); +/** + * Run any criteria that match the view and haven't been run on this view + * before. + */ +void view_execute_criteria(struct sway_view *view); + #endif diff --git a/sway/commands.c b/sway/commands.c index 2e1cdc2ce..811f6cfa5 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -12,6 +12,7 @@ #include "sway/security.h" #include "sway/input/input-manager.h" #include "sway/input/seat.h" +#include "sway/tree/view.h" #include "stringop.h" #include "log.h" @@ -283,7 +284,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { char *head = exec; char *cmdlist; char *cmd; - list_t *containers = NULL; + list_t *views = NULL; if (seat == NULL) { // passing a NULL seat means we just pick the default seat @@ -300,31 +301,18 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { // Extract criteria (valid for this command list only). bool has_criteria = false; if (*head == '[') { - has_criteria = true; - ++head; - char *criteria_string = argsep(&head, "]"); - if (head) { - ++head; - list_t *tokens = create_list(); - char *error; - - if ((error = extract_crit_tokens(tokens, criteria_string))) { - wlr_log(L_DEBUG, "criteria string parse error: %s", error); - results = cmd_results_new(CMD_INVALID, criteria_string, - "Can't parse criteria string: %s", error); - free(error); - free(tokens); - goto cleanup; - } - containers = container_for_crit_tokens(tokens); - - free(tokens); - } else { - if (!results) { - results = cmd_results_new(CMD_INVALID, criteria_string, "Unmatched ["); - } + char *error = NULL; + struct criteria *criteria = criteria_parse(head, &error); + if (!criteria) { + results = cmd_results_new(CMD_INVALID, head, + "%s", error); + free(error); goto cleanup; } + views = criteria_get_views(criteria); + head += strlen(criteria->raw); + criteria_destroy(criteria); + has_criteria = true; // Skip leading whitespace head += strspn(head, whitespace); } @@ -381,8 +369,9 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { } free_cmd_results(res); } else { - for (int i = 0; i < containers->length; ++i) { - config->handler_context.current_container = containers->items[i]; + for (int i = 0; i < views->length; ++i) { + struct sway_view *view = views->items[i]; + config->handler_context.current_container = view->swayc; struct cmd_results *res = handler->handle(argc-1, argv+1); if (res->status != CMD_SUCCESS) { free_argv(argc, argv); @@ -400,6 +389,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { } while(head); cleanup: free(exec); + free(views); if (!results) { results = cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/assign.c b/sway/commands/assign.c index eb7329aa4..9d15e166e 100644 --- a/sway/commands/assign.c +++ b/sway/commands/assign.c @@ -5,6 +5,7 @@ #include "sway/criteria.h" #include "list.h" #include "log.h" +#include "stringop.h" struct cmd_results *cmd_assign(int argc, char **argv) { struct cmd_results *error = NULL; @@ -12,46 +13,39 @@ struct cmd_results *cmd_assign(int argc, char **argv) { return error; } - char *criteria = *argv++; + // Create criteria + char *err_str = NULL; + struct criteria *criteria = criteria_parse(argv[0], &err_str); + if (!criteria) { + error = cmd_results_new(CMD_INVALID, "assign", err_str); + free(err_str); + return error; + } + + ++argv; + int target_len = argc - 1; if (strncmp(*argv, "→", strlen("→")) == 0) { if (argc < 3) { return cmd_results_new(CMD_INVALID, "assign", "Missing workspace"); } - argv++; + ++argv; + --target_len; } - char *movecmd = "move container to workspace "; - size_t arglen = strlen(movecmd) + strlen(*argv) + 1; - char *cmdlist = calloc(1, arglen); - if (!cmdlist) { - return cmd_results_new(CMD_FAILURE, "assign", "Unable to allocate command list"); - } - snprintf(cmdlist, arglen, "%s%s", movecmd, *argv); - - struct criteria *crit = malloc(sizeof(struct criteria)); - if (!crit) { - free(cmdlist); - return cmd_results_new(CMD_FAILURE, "assign", "Unable to allocate criteria"); - } - crit->crit_raw = strdup(criteria); - crit->cmdlist = cmdlist; - crit->tokens = create_list(); - char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw); - - if (err_str) { - error = cmd_results_new(CMD_INVALID, "assign", err_str); - free(err_str); - free_criteria(crit); - } else if (crit->tokens->length == 0) { - error = cmd_results_new(CMD_INVALID, "assign", "Found no name/value pairs in criteria"); - free_criteria(crit); - } else if (list_seq_find(config->criteria, criteria_cmp, crit) != -1) { - wlr_log(L_DEBUG, "assign: Duplicate, skipping."); - free_criteria(crit); + if (strcmp(*argv, "output") == 0) { + criteria->type = CT_ASSIGN_OUTPUT; + ++argv; + --target_len; } else { - wlr_log(L_DEBUG, "assign: '%s' -> '%s' added", crit->crit_raw, crit->cmdlist); - list_add(config->criteria, crit); + criteria->type = CT_ASSIGN_WORKSPACE; } - return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL); + + criteria->target = join_args(argv, target_len); + + list_add(config->criteria, criteria); + wlr_log(L_DEBUG, "assign: '%s' -> '%s' added", criteria->raw, + criteria->target); + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/for_window.c b/sway/commands/for_window.c index dd5461f03..8c425a1dd 100644 --- a/sway/commands/for_window.c +++ b/sway/commands/for_window.c @@ -11,31 +11,20 @@ struct cmd_results *cmd_for_window(int argc, char **argv) { if ((error = checkarg(argc, "for_window", EXPECTED_AT_LEAST, 2))) { return error; } - // add command to a criteria/command pair that is run against views when they appear. - char *criteria = argv[0], *cmdlist = join_args(argv + 1, argc - 1); - struct criteria *crit = calloc(sizeof(struct criteria), 1); - if (!crit) { - return cmd_results_new(CMD_FAILURE, "for_window", "Unable to allocate criteria"); - } - crit->crit_raw = strdup(criteria); - crit->cmdlist = cmdlist; - crit->tokens = create_list(); - char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw); - - if (err_str) { + char *err_str = NULL; + struct criteria *criteria = criteria_parse(argv[0], &err_str); + if (!criteria) { error = cmd_results_new(CMD_INVALID, "for_window", err_str); free(err_str); - free_criteria(crit); - } else if (crit->tokens->length == 0) { - error = cmd_results_new(CMD_INVALID, "for_window", "Found no name/value pairs in criteria"); - free_criteria(crit); - } else if (list_seq_find(config->criteria, criteria_cmp, crit) != -1) { - wlr_log(L_DEBUG, "for_window: Duplicate, skipping."); - free_criteria(crit); - } else { - wlr_log(L_DEBUG, "for_window: '%s' -> '%s' added", crit->crit_raw, crit->cmdlist); - list_add(config->criteria, crit); + return error; } - return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL); + + criteria->type = CT_COMMAND; + criteria->cmdlist = join_args(argv + 1, argc - 1); + + list_add(config->criteria, criteria); + wlr_log(L_DEBUG, "for_window: '%s' -> '%s' added", criteria->raw, criteria->cmdlist); + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/criteria.c b/sway/criteria.c index 22e9a49b6..6abe24aff 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -11,435 +11,382 @@ #include "list.h" #include "log.h" -enum criteria_type { // *must* keep in sync with criteria_strings[] - CRIT_APP_ID, - CRIT_CLASS, - CRIT_CON_ID, - CRIT_CON_MARK, - CRIT_FLOATING, - CRIT_ID, - CRIT_INSTANCE, - CRIT_TILING, - CRIT_TITLE, - CRIT_URGENT, - CRIT_WINDOW_ROLE, - CRIT_WINDOW_TYPE, - CRIT_WORKSPACE, - CRIT_LAST -}; - -static const char * const criteria_strings[CRIT_LAST] = { - [CRIT_APP_ID] = "app_id", - [CRIT_CLASS] = "class", - [CRIT_CON_ID] = "con_id", - [CRIT_CON_MARK] = "con_mark", - [CRIT_FLOATING] = "floating", - [CRIT_ID] = "id", - [CRIT_INSTANCE] = "instance", - [CRIT_TILING] = "tiling", - [CRIT_TITLE] = "title", - [CRIT_URGENT] = "urgent", // either "latest" or "oldest" ... - [CRIT_WINDOW_ROLE] = "window_role", - [CRIT_WINDOW_TYPE] = "window_type", - [CRIT_WORKSPACE] = "workspace" -}; - -/** - * A single criteria token (ie. value/regex pair), - * e.g. 'class="some class regex"'. - */ -struct crit_token { - enum criteria_type type; - pcre *regex; - char *raw; -}; - -static void free_crit_token(struct crit_token *crit) { - pcre_free(crit->regex); - free(crit->raw); - free(crit); +bool criteria_is_empty(struct criteria *criteria) { + return !criteria->title + && !criteria->app_id + && !criteria->class + && !criteria->instance + && !criteria->con_mark + && !criteria->con_id + && !criteria->id + && !criteria->window_role + && !criteria->window_type + && !criteria->floating + && !criteria->tiling + && !criteria->urgent + && !criteria->workspace; } -static void free_crit_tokens(list_t *crit_tokens) { - for (int i = 0; i < crit_tokens->length; i++) { - free_crit_token(crit_tokens->items[i]); - } - list_free(crit_tokens); -} +void criteria_destroy(struct criteria *criteria) { + pcre_free(criteria->title); + pcre_free(criteria->app_id); + pcre_free(criteria->class); + pcre_free(criteria->instance); + pcre_free(criteria->con_mark); + pcre_free(criteria->window_role); + free(criteria->workspace); -// Extracts criteria string from its brackets. Returns new (duplicate) -// substring. -static char *criteria_from(const char *arg) { - char *criteria = NULL; - if (*arg == '[') { - criteria = strdup(arg + 1); - } else { - criteria = strdup(arg); - } - - int last = strlen(criteria) - 1; - if (criteria[last] == ']') { - criteria[last] = '\0'; - } - return criteria; -} - -// Return instances of c found in str. -static int countchr(char *str, char c) { - int found = 0; - for (int i = 0; str[i]; i++) { - if (str[i] == c) { - ++found; - } - } - return found; -} - -// criteria_str is e.g. '[class="some class regex" instance="instance name"]'. -// -// Will create array of pointers in buf, where first is duplicate of given -// string (must be freed) and the rest are pointers to names and values in the -// base string (every other, naturally). argc will be populated with the length -// of buf. -// -// Returns error string or NULL if successful. -static char *crit_tokens(int *argc, char ***buf, - const char * const criteria_str) { - wlr_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str); - char *base = criteria_from(criteria_str); - char *head = base; - char *namep = head; // start of criteria name - char *valp = NULL; // start of value - - // We're going to place EOS markers where we need to and fill up an array - // of pointers to the start of each token (either name or value). - int pairs = countchr(base, '='); - int max_tokens = pairs * 2 + 1; // this gives us at least enough slots - - char **argv = *buf = calloc(max_tokens, sizeof(char*)); - argv[0] = base; // this needs to be freed by caller - bool quoted = true; - - *argc = 1; // uneven = name, even = value - while (*head && *argc < max_tokens) { - if (namep != head && *(head - 1) == '\\') { - // escaped character: don't try to parse this - } else if (*head == '=' && namep != head) { - if (*argc % 2 != 1) { - // we're not expecting a name - return strdup("Unable to parse criteria: " - "Found out of place equal sign"); - } else { - // name ends here - char *end = head; // don't want to rewind the head - while (*(end - 1) == ' ') { - --end; - } - *end = '\0'; - if (*(namep) == ' ') { - namep = strrchr(namep, ' ') + 1; - } - argv[*argc] = namep; - *argc += 1; - } - } else if (*head == '"') { - if (*argc % 2 != 0) { - // we're not expecting a value - return strdup("Unable to parse criteria: " - "Found quoted value where it was not expected"); - } else if (!valp) { // value starts here - valp = head + 1; - quoted = true; - } else { - // value ends here - argv[*argc] = valp; - *argc += 1; - *head = '\0'; - valp = NULL; - namep = head + 1; - } - } else if (*argc % 2 == 0 && *head != ' ') { - // parse unquoted values - if (!valp) { - quoted = false; - valp = head; // value starts here - } - } else if (valp && !quoted && *head == ' ') { - // value ends here - argv[*argc] = valp; - *argc += 1; - *head = '\0'; - valp = NULL; - namep = head + 1; - } - head++; - } - - // catch last unquoted value if needed - if (valp && !quoted && !*head) { - argv[*argc] = valp; - *argc += 1; - } - - return NULL; -} - -// Returns error string on failure or NULL otherwise. -static char *parse_criteria_name(enum criteria_type *type, char *name) { - *type = CRIT_LAST; - for (int i = 0; i < CRIT_LAST; i++) { - if (strcmp(criteria_strings[i], name) == 0) { - *type = (enum criteria_type) i; - break; - } - } - if (*type == CRIT_LAST) { - const char *fmt = "Criteria type '%s' is invalid or unsupported."; - int len = strlen(name) + strlen(fmt) - 1; - char *error = malloc(len); - snprintf(error, len, fmt, name); - return error; - } else if (*type == CRIT_URGENT || *type == CRIT_WINDOW_ROLE || - *type == CRIT_WINDOW_TYPE) { - // (we're just being helpful here) - const char *fmt = "\"%s\" criteria currently unsupported, " - "no window will match this"; - int len = strlen(fmt) + strlen(name) - 1; - char *error = malloc(len); - snprintf(error, len, fmt, name); - return error; - } - return NULL; -} - -// Returns error string on failure or NULL otherwise. -static char *generate_regex(pcre **regex, char *value) { - const char *reg_err; - int offset; - - *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); - - if (!*regex) { - const char *fmt = "Regex compilation (for '%s') failed: %s"; - int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; - char *error = malloc(len); - snprintf(error, len, fmt, value, reg_err); - return error; - } - return NULL; -} - -// Test whether the criterion corresponds to the currently focused window -static bool crit_is_focused(const char *value) { - return !strcmp(value, "focused") || !strcmp(value, "__focused__"); -} - -// Populate list with crit_tokens extracted from criteria string, returns error -// string or NULL if successful. -char *extract_crit_tokens(list_t *tokens, const char * const criteria) { - int argc; - char **argv = NULL, *error = NULL; - if ((error = crit_tokens(&argc, &argv, criteria))) { - goto ect_cleanup; - } - for (int i = 1; i + 1 < argc; i += 2) { - char* name = argv[i], *value = argv[i + 1]; - struct crit_token *token = calloc(1, sizeof(struct crit_token)); - token->raw = strdup(value); - - if ((error = parse_criteria_name(&token->type, name))) { - free_crit_token(token); - goto ect_cleanup; - } else if (token->type == CRIT_URGENT || crit_is_focused(value)) { - wlr_log(L_DEBUG, "%s -> \"%s\"", name, value); - list_add(tokens, token); - } else if((error = generate_regex(&token->regex, value))) { - free_crit_token(token); - goto ect_cleanup; - } else { - wlr_log(L_DEBUG, "%s -> /%s/", name, value); - list_add(tokens, token); - } - } -ect_cleanup: - free(argv[0]); // base string - free(argv); - return error; + free(criteria->raw); + free(criteria); } static int regex_cmp(const char *item, const pcre *regex) { return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); } -// test a single view if it matches list of criteria tokens (all of them). -static bool criteria_test(struct sway_container *cont, list_t *tokens) { - if (cont->type != C_CONTAINER && cont->type != C_VIEW) { +static bool criteria_matches_view(struct criteria *criteria, + struct sway_view *view) { + if (criteria->title) { + const char *title = view_get_title(view); + if (!title || regex_cmp(title, criteria->title) != 0) { + return false; + } + } + + if (criteria->app_id) { + const char *app_id = view_get_app_id(view); + if (!app_id || regex_cmp(app_id, criteria->app_id) != 0) { + return false; + } + } + + if (criteria->class) { + const char *class = view_get_class(view); + if (!class || regex_cmp(class, criteria->class) != 0) { + return false; + } + } + + if (criteria->instance) { + const char *instance = view_get_instance(view); + if (!instance || regex_cmp(instance, criteria->instance) != 0) { + return false; + } + } + + if (criteria->con_mark) { + // TODO return false; } - int matches = 0; - for (int i = 0; i < tokens->length; i++) { - struct crit_token *crit = tokens->items[i]; - switch (crit->type) { - case CRIT_CLASS: - { - const char *class = view_get_class(cont->sway_view); - if (!class) { - break; - } - if (crit->regex && regex_cmp(class, crit->regex) == 0) { - matches++; - } - break; - } - case CRIT_CON_ID: - { - char *endptr; - size_t crit_id = strtoul(crit->raw, &endptr, 10); - if (*endptr == 0 && cont->id == crit_id) { - ++matches; - } - break; - } - case CRIT_CON_MARK: - // TODO - break; - case CRIT_FLOATING: - // TODO - break; - case CRIT_ID: - // TODO - break; - case CRIT_APP_ID: - { - const char *app_id = view_get_app_id(cont->sway_view); - if (!app_id) { - break; - } - - if (crit->regex && regex_cmp(app_id, crit->regex) == 0) { - matches++; - } - break; - } - case CRIT_INSTANCE: - { - const char *instance = view_get_instance(cont->sway_view); - if (!instance) { - break; - } - - if (crit->regex && regex_cmp(instance, crit->regex) == 0) { - matches++; - } - break; - } - case CRIT_TILING: - // TODO - break; - case CRIT_TITLE: - { - const char *title = view_get_title(cont->sway_view); - if (!title) { - break; - } - - if (crit->regex && regex_cmp(title, crit->regex) == 0) { - matches++; - } - break; - } - case CRIT_URGENT: - // TODO "latest" or "oldest" - break; - case CRIT_WINDOW_ROLE: - // TODO - break; - case CRIT_WINDOW_TYPE: - // TODO - break; - case CRIT_WORKSPACE: - // TODO - break; - default: - sway_abort("Invalid criteria type (%i)", crit->type); - break; + if (criteria->con_id) { // Internal ID + if (!view->swayc || view->swayc->id != criteria->con_id) { + return false; } } - return matches == tokens->length; -} -int criteria_cmp(const void *a, const void *b) { - if (a == b) { - return 0; - } else if (!a) { - return -1; - } else if (!b) { - return 1; - } - const struct criteria *crit_a = a, *crit_b = b; - int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist); - if (cmp != 0) { - return cmp; - } - return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw); -} - -void free_criteria(struct criteria *crit) { - if (crit->tokens) { - free_crit_tokens(crit->tokens); - } - if (crit->cmdlist) { - free(crit->cmdlist); - } - if (crit->crit_raw) { - free(crit->crit_raw); - } - free(crit); -} - -bool criteria_any(struct sway_container *cont, list_t *criteria) { - for (int i = 0; i < criteria->length; i++) { - struct criteria *bc = criteria->items[i]; - if (criteria_test(cont, bc->tokens)) { - return true; + if (criteria->id) { // X11 window ID + uint32_t x11_window_id = view_get_x11_window_id(view); + if (!x11_window_id || x11_window_id != criteria->id) { + return false; } } - return false; + + if (criteria->window_role) { + // TODO + } + + if (criteria->window_type) { + uint32_t type = view_get_window_type(view); + if (!type || type != criteria->window_type) { + return false; + } + } + + if (criteria->floating) { + // TODO + return false; + } + + if (criteria->tiling) { + // TODO + } + + if (criteria->urgent) { + // TODO + return false; + } + + if (criteria->workspace) { + if (!view->swayc) { + return false; + } + struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE); + if (!ws || strcmp(ws->name, criteria->workspace) != 0) { + return false; + } + } + + return true; } -list_t *criteria_for(struct sway_container *cont) { - list_t *criteria = config->criteria, *matches = create_list(); - for (int i = 0; i < criteria->length; i++) { - struct criteria *bc = criteria->items[i]; - if (criteria_test(cont, bc->tokens)) { - list_add(matches, bc); +list_t *criteria_for_view(struct sway_view *view, enum criteria_type types) { + list_t *criterias = config->criteria; + list_t *matches = create_list(); + for (int i = 0; i < criterias->length; ++i) { + struct criteria *criteria = criterias->items[i]; + if ((criteria->type & types) && criteria_matches_view(criteria, view)) { + list_add(matches, criteria); } } return matches; } -struct list_tokens { - list_t *list; - list_t *tokens; +struct match_data { + struct criteria *criteria; + list_t *matches; }; -static void container_match_add(struct sway_container *container, - struct list_tokens *list_tokens) { - if (criteria_test(container, list_tokens->tokens)) { - list_add(list_tokens->list, container); +static void criteria_get_views_iterator(struct sway_container *container, + void *data) { + struct match_data *match_data = data; + if (container->type == C_VIEW) { + if (criteria_matches_view(match_data->criteria, container->sway_view)) { + list_add(match_data->matches, container->sway_view); + } } } -list_t *container_for_crit_tokens(list_t *tokens) { - struct list_tokens list_tokens = - (struct list_tokens){create_list(), tokens}; - +list_t *criteria_get_views(struct criteria *criteria) { + list_t *matches = create_list(); + struct match_data data = { + .criteria = criteria, + .matches = matches, + }; container_for_each_descendant_dfs(&root_container, - (void (*)(struct sway_container *, void *))container_match_add, - &list_tokens); - - // TODO look in the scratchpad - - return list_tokens.list; + criteria_get_views_iterator, &data); + return matches; +} + +// The error pointer is used for parsing functions, and saves having to pass it +// as an argument in several places. +char *error = NULL; + +// Returns error string on failure or NULL otherwise. +static bool generate_regex(pcre **regex, char *value) { + const char *reg_err; + int offset; + + *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); + + if (!*regex) { + const char *fmt = "Regex compilation for '%s' failed: %s"; + int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; + error = malloc(len); + snprintf(error, len, fmt, value, reg_err); + return false; + } + + return true; +} + +static bool parse_token(struct criteria *criteria, char *name, char *value) { + // Require value, unless token is floating or tiled + if (!value && (strcmp(name, "title") == 0 + || strcmp(name, "app_id") == 0 + || strcmp(name, "class") == 0 + || strcmp(name, "instance") == 0 + || strcmp(name, "con_id") == 0 + || strcmp(name, "con_mark") == 0 + || strcmp(name, "window_role") == 0 + || strcmp(name, "window_type") == 0 + || strcmp(name, "id") == 0 + || strcmp(name, "urgent") == 0 + || strcmp(name, "workspace") == 0)) { + const char *fmt = "Token '%s' requires a value"; + int len = strlen(fmt) + strlen(name) - 1; + error = malloc(len); + snprintf(error, len, fmt, name); + return false; + } + + if (strcmp(name, "title") == 0) { + generate_regex(&criteria->title, value); + } else if (strcmp(name, "app_id") == 0) { + generate_regex(&criteria->app_id, value); + } else if (strcmp(name, "class") == 0) { + generate_regex(&criteria->class, value); + } else if (strcmp(name, "instance") == 0) { + generate_regex(&criteria->instance, value); + } else if (strcmp(name, "con_id") == 0) { + char *endptr; + criteria->con_id = strtoul(value, &endptr, 10); + if (*endptr != 0) { + error = strdup("The value for 'con_id' should be numeric"); + } + } else if (strcmp(name, "con_mark") == 0) { + generate_regex(&criteria->con_mark, value); + } else if (strcmp(name, "window_role") == 0) { + generate_regex(&criteria->window_role, value); + } else if (strcmp(name, "window_type") == 0) { + // TODO: This is a string but will be stored as an enum or integer + } else if (strcmp(name, "id") == 0) { + char *endptr; + criteria->id = strtoul(value, &endptr, 10); + if (*endptr != 0) { + error = strdup("The value for 'id' should be numeric"); + } + } else if (strcmp(name, "floating") == 0) { + criteria->floating = true; + } else if (strcmp(name, "tiling") == 0) { + criteria->tiling = true; + } else if (strcmp(name, "urgent") == 0) { + if (strcmp(value, "latest") == 0) { + criteria->urgent = 'l'; + } else if (strcmp(value, "oldest") == 0) { + criteria->urgent = 'o'; + } else { + error = + strdup("The value for 'urgent' must be 'latest' or 'oldest'"); + } + } else if (strcmp(name, "workspace") == 0) { + criteria->workspace = strdup(value); + } else { + const char *fmt = "Token '%s' is not recognized"; + int len = strlen(fmt) + strlen(name) - 1; + error = malloc(len); + snprintf(error, len, fmt, name); + } + + if (error) { + return false; + } + + return true; +} + +static void skip_spaces(char **head) { + while (**head == ' ') { + ++*head; + } +} + +// Remove escaping slashes from value +static void unescape(char *value) { + if (!strchr(value, '\\')) { + return; + } + char *copy = calloc(strlen(value) + 1, 1); + char *readhead = value; + char *writehead = copy; + while (*readhead) { + if (*readhead == '\\' && + (*(readhead + 1) == '"' || *(readhead + 1) == '\\')) { + // skip the slash + ++readhead; + } + *writehead = *readhead; + ++writehead; + ++readhead; + } + strcpy(value, copy); + free(copy); +} + +/** + * Parse a raw criteria string such as [class="foo" instance="bar"] into a + * criteria struct. + * + * If errors are found, NULL will be returned and the error argument will be + * populated with an error string. + */ +struct criteria *criteria_parse(char *raw, char **error_arg) { + free(error); + error = NULL; + + char *head = raw; + skip_spaces(&head); + if (*head != '[') { + *error_arg = strdup("No criteria"); + return NULL; + } + ++head; + + struct criteria *criteria = calloc(sizeof(struct criteria), 1); + char *name = NULL, *value = NULL; + bool in_quotes = false; + + while (*head && *head != ']') { + skip_spaces(&head); + // Parse token name + char *namestart = head; + while ((*head >= 'a' && *head <= 'z') || *head == '_') { + ++head; + } + name = calloc(head - namestart + 1, 1); + strncpy(name, namestart, head - namestart); + // Parse token value + skip_spaces(&head); + value = NULL; + if (*head == '=') { + ++head; + skip_spaces(&head); + if (*head == '"') { + in_quotes = true; + ++head; + } + char *valuestart = head; + if (in_quotes) { + while (*head && (*head != '"' || *(head - 1) == '\\')) { + ++head; + } + if (!*head) { + *error_arg = strdup("Quote mismatch in criteria"); + goto cleanup; + } + } else { + while (*head && *head != ' ' && *head != ']') { + ++head; + } + } + value = calloc(head - valuestart + 1, 1); + strncpy(value, valuestart, head - valuestart); + if (in_quotes) { + ++head; + in_quotes = false; + } + unescape(value); + } + wlr_log(L_DEBUG, "Found pair: %s=%s", name, value); + if (!parse_token(criteria, name, value)) { + *error_arg = error; + goto cleanup; + } + skip_spaces(&head); + free(name); + free(value); + name = NULL; + value = NULL; + } + if (*head != ']') { + *error_arg = strdup("No closing brace found in criteria"); + goto cleanup; + } + + if (criteria_is_empty(criteria)) { + *error_arg = strdup("Criteria is empty"); + goto cleanup; + } + + ++head; + int len = head - raw; + criteria->raw = calloc(len + 1, 1); + strncpy(criteria->raw, raw, len); + return criteria; + +cleanup: + free(name); + free(value); + criteria_destroy(criteria); + return NULL; } diff --git a/sway/desktop/wl_shell.c b/sway/desktop/wl_shell.c index 99e8947bc..cb3774f78 100644 --- a/sway/desktop/wl_shell.c +++ b/sway/desktop/wl_shell.c @@ -20,7 +20,7 @@ static struct sway_wl_shell_view *wl_shell_view_from_view( return (struct sway_wl_shell_view *)view; } -static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { +static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { if (wl_shell_view_from_view(view) == NULL) { return NULL; } @@ -70,7 +70,7 @@ static void set_fullscreen(struct sway_view *view, bool fullscreen) { } static const struct sway_view_impl view_impl = { - .get_prop = get_prop, + .get_string_prop = get_string_prop, .configure = configure, .close = _close, .destroy = destroy, diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c index 8ecb330da..f685ef717 100644 --- a/sway/desktop/xdg_shell_v6.c +++ b/sway/desktop/xdg_shell_v6.c @@ -80,7 +80,7 @@ static struct sway_xdg_shell_v6_view *xdg_shell_v6_view_from_view( return (struct sway_xdg_shell_v6_view *)view; } -static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { +static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { if (xdg_shell_v6_view_from_view(view) == NULL) { return NULL; } @@ -158,7 +158,7 @@ static void destroy(struct sway_view *view) { } static const struct sway_view_impl view_impl = { - .get_prop = get_prop, + .get_string_prop = get_string_prop, .configure = configure, .set_activated = set_activated, .set_fullscreen = set_fullscreen, diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c index 8f9357602..554c070e8 100644 --- a/sway/desktop/xwayland.c +++ b/sway/desktop/xwayland.c @@ -126,7 +126,7 @@ static struct sway_xwayland_view *xwayland_view_from_view( return (struct sway_xwayland_view *)view; } -static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { +static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { if (xwayland_view_from_view(view) == NULL) { return NULL; } @@ -135,11 +135,27 @@ static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { return view->wlr_xwayland_surface->title; case VIEW_PROP_CLASS: return view->wlr_xwayland_surface->class; + case VIEW_PROP_INSTANCE: + return view->wlr_xwayland_surface->instance; default: return NULL; } } +static uint32_t get_int_prop(struct sway_view *view, enum sway_view_prop prop) { + if (xwayland_view_from_view(view) == NULL) { + return 0; + } + switch (prop) { + case VIEW_PROP_X11_WINDOW_ID: + return view->wlr_xwayland_surface->window_id; + case VIEW_PROP_WINDOW_TYPE: + return *view->wlr_xwayland_surface->window_type; + default: + return 0; + } +} + static void configure(struct sway_view *view, double ox, double oy, int width, int height) { struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view); @@ -200,13 +216,17 @@ static void destroy(struct sway_view *view) { wl_list_remove(&xwayland_view->destroy.link); wl_list_remove(&xwayland_view->request_configure.link); wl_list_remove(&xwayland_view->request_fullscreen.link); + wl_list_remove(&xwayland_view->set_title.link); + wl_list_remove(&xwayland_view->set_class.link); + wl_list_remove(&xwayland_view->set_window_type.link); wl_list_remove(&xwayland_view->map.link); wl_list_remove(&xwayland_view->unmap.link); free(xwayland_view); } static const struct sway_view_impl view_impl = { - .get_prop = get_prop, + .get_string_prop = get_string_prop, + .get_int_prop = get_int_prop, .configure = configure, .set_activated = set_activated, .set_fullscreen = set_fullscreen, @@ -223,7 +243,6 @@ static void handle_commit(struct wl_listener *listener, void *data) { view_update_size(view, xwayland_view->pending_width, xwayland_view->pending_height); view_damage_from(view); - view_update_title(view, false); } static void handle_unmap(struct wl_listener *listener, void *data) { @@ -285,6 +304,40 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data) view_set_fullscreen(view, xsurface->fullscreen); } +static void handle_set_title(struct wl_listener *listener, void *data) { + struct sway_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, set_title); + struct sway_view *view = &xwayland_view->view; + struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; + if (!xsurface->mapped) { + return; + } + view_update_title(view, false); + view_execute_criteria(view); +} + +static void handle_set_class(struct wl_listener *listener, void *data) { + struct sway_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, set_class); + struct sway_view *view = &xwayland_view->view; + struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; + if (!xsurface->mapped) { + return; + } + view_execute_criteria(view); +} + +static void handle_set_window_type(struct wl_listener *listener, void *data) { + struct sway_xwayland_view *xwayland_view = + wl_container_of(listener, xwayland_view, set_window_type); + struct sway_view *view = &xwayland_view->view; + struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; + if (!xsurface->mapped) { + return; + } + view_execute_criteria(view); +} + void handle_xwayland_surface(struct wl_listener *listener, void *data) { struct sway_server *server = wl_container_of(listener, server, xwayland_surface); @@ -323,6 +376,16 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) { &xwayland_view->request_fullscreen); xwayland_view->request_fullscreen.notify = handle_request_fullscreen; + wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title); + xwayland_view->set_title.notify = handle_set_title; + + wl_signal_add(&xsurface->events.set_class, &xwayland_view->set_class); + xwayland_view->set_class.notify = handle_set_class; + + wl_signal_add(&xsurface->events.set_window_type, + &xwayland_view->set_window_type); + xwayland_view->set_window_type.notify = handle_set_window_type; + wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap); xwayland_view->unmap.notify = handle_unmap; diff --git a/sway/tree/view.c b/sway/tree/view.c index afd7eadea..7d921e0ec 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -3,6 +3,7 @@ #include #include #include +#include "list.h" #include "log.h" #include "sway/criteria.h" #include "sway/commands.h" @@ -19,6 +20,7 @@ void view_init(struct sway_view *view, enum sway_view_type type, const struct sway_view_impl *impl) { view->type = type; view->impl = impl; + view->executed_criteria = create_list(); wl_signal_init(&view->events.unmap); } @@ -31,6 +33,8 @@ void view_destroy(struct sway_view *view) { view_unmap(view); } + list_free(view->executed_criteria); + container_destroy(view->swayc); if (view->impl->destroy) { @@ -41,33 +45,47 @@ void view_destroy(struct sway_view *view) { } const char *view_get_title(struct sway_view *view) { - if (view->impl->get_prop) { - return view->impl->get_prop(view, VIEW_PROP_TITLE); + if (view->impl->get_string_prop) { + return view->impl->get_string_prop(view, VIEW_PROP_TITLE); } return NULL; } const char *view_get_app_id(struct sway_view *view) { - if (view->impl->get_prop) { - return view->impl->get_prop(view, VIEW_PROP_APP_ID); + if (view->impl->get_string_prop) { + return view->impl->get_string_prop(view, VIEW_PROP_APP_ID); } return NULL; } const char *view_get_class(struct sway_view *view) { - if (view->impl->get_prop) { - return view->impl->get_prop(view, VIEW_PROP_CLASS); + if (view->impl->get_string_prop) { + return view->impl->get_string_prop(view, VIEW_PROP_CLASS); } return NULL; } const char *view_get_instance(struct sway_view *view) { - if (view->impl->get_prop) { - return view->impl->get_prop(view, VIEW_PROP_INSTANCE); + if (view->impl->get_string_prop) { + return view->impl->get_string_prop(view, VIEW_PROP_INSTANCE); } return NULL; } +uint32_t view_get_x11_window_id(struct sway_view *view) { + if (view->impl->get_int_prop) { + return view->impl->get_int_prop(view, VIEW_PROP_X11_WINDOW_ID); + } + return 0; +} + +uint32_t view_get_window_type(struct sway_view *view) { + if (view->impl->get_int_prop) { + return view->impl->get_int_prop(view, VIEW_PROP_WINDOW_TYPE); + } + return 0; +} + const char *view_get_type(struct sway_view *view) { switch(view->type) { case SWAY_VIEW_WL_SHELL: @@ -282,19 +300,36 @@ static void view_handle_container_reparent(struct wl_listener *listener, } } -static void view_execute_criteria(struct sway_view *view) { - if (!sway_assert(view->swayc, "cannot run criteria for unmapped view")) { +static bool view_has_executed_criteria(struct sway_view *view, + struct criteria *criteria) { + for (int i = 0; i < view->executed_criteria->length; ++i) { + struct criteria *item = view->executed_criteria->items[i]; + if (item == criteria) { + return true; + } + } + return false; +} + +void view_execute_criteria(struct sway_view *view) { + if (!view->swayc) { return; } struct sway_seat *seat = input_manager_current_seat(input_manager); struct sway_container *prior_workspace = container_parent(view->swayc, C_WORKSPACE); - list_t *criteria = criteria_for(view->swayc); - for (int i = 0; i < criteria->length; i++) { - struct criteria *crit = criteria->items[i]; - wlr_log(L_DEBUG, "for_window '%s' matches new view %p, cmd: '%s'", - crit->crit_raw, view, crit->cmdlist); - struct cmd_results *res = execute_command(crit->cmdlist, NULL); + list_t *criterias = criteria_for_view(view, CT_COMMAND); + for (int i = 0; i < criterias->length; i++) { + struct criteria *criteria = criterias->items[i]; + wlr_log(L_DEBUG, "Checking criteria %s", criteria->raw); + if (view_has_executed_criteria(view, criteria)) { + wlr_log(L_DEBUG, "Criteria already executed"); + continue; + } + wlr_log(L_DEBUG, "for_window '%s' matches view %p, cmd: '%s'", + criteria->raw, view, criteria->cmdlist); + list_add(view->executed_criteria, criteria); + struct cmd_results *res = execute_command(criteria->cmdlist, NULL); if (res->status != CMD_SUCCESS) { wlr_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error); } @@ -303,7 +338,7 @@ static void view_execute_criteria(struct sway_view *view) { // so always refocus in-between command lists seat_set_focus(seat, view->swayc); } - list_free(criteria); + list_free(criterias); seat_set_focus(seat, seat_get_focus_inactive(seat, prior_workspace)); } @@ -313,9 +348,26 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) { } struct sway_seat *seat = input_manager_current_seat(input_manager); - struct sway_container *focus = seat_get_focus_inactive(seat, - &root_container); - struct sway_container *cont = container_view_create(focus, view); + struct sway_container *focus = seat_get_focus(seat); + struct sway_container *cont = NULL; + + // Check if there's any `assign` criteria for the view + list_t *criterias = criteria_for_view(view, + CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT); + if (criterias->length) { + struct criteria *criteria = criterias->items[0]; + if (criteria->type == CT_ASSIGN_WORKSPACE) { + struct sway_container *workspace = workspace_by_name(criteria->target); + if (!workspace) { + workspace = workspace_create(NULL, criteria->target); + } + focus = seat_get_focus_inactive(seat, workspace); + } else { + // TODO: CT_ASSIGN_OUTPUT + } + } + free(criterias); + cont = container_view_create(focus, view); view->surface = wlr_surface; view->swayc = cont; @@ -333,10 +385,11 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) { arrange_children_of(cont->parent); input_manager_set_focus(input_manager, cont); + view_update_title(view, false); + view_execute_criteria(view); + container_damage_whole(cont); view_handle_container_reparent(&view->container_reparent, NULL); - - view_execute_criteria(view); } void view_unmap(struct sway_view *view) { From 0bf0a4fa4049cbabeb797536e549640ec5235454 Mon Sep 17 00:00:00 2001 From: Ryan Dwyer Date: Fri, 11 May 2018 10:42:24 +1000 Subject: [PATCH 2/6] Don't unescape \\ in criteria --- sway/criteria.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sway/criteria.c b/sway/criteria.c index 6abe24aff..7da790e6d 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -280,8 +280,7 @@ static void unescape(char *value) { char *readhead = value; char *writehead = copy; while (*readhead) { - if (*readhead == '\\' && - (*(readhead + 1) == '"' || *(readhead + 1) == '\\')) { + if (*readhead == '\\' && *(readhead + 1) == '"') { // skip the slash ++readhead; } From 8595fc5a6f81f00ab62bfea127f8fad5c0b249de Mon Sep 17 00:00:00 2001 From: Ryan Dwyer Date: Fri, 11 May 2018 10:43:10 +1000 Subject: [PATCH 3/6] Update criteria documentation --- sway/sway.5.txt | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/sway/sway.5.txt b/sway/sway.5.txt index 039753493..704bb6998 100644 --- a/sway/sway.5.txt +++ b/sway/sway.5.txt @@ -485,11 +485,16 @@ Mark all Firefox windows with "Browser": Currently supported attributes: -**class**:: - Compare value against the window class. Can be a regular expression. If value - is _focused_, then the window class must be the same as that of the currently +**app_id**:: + Compare value against the app id. Can be a regular expression. If value is + __focused__, then the app id must be the same as that of the currently focused window. +**class**:: + Compare value against the window class. Can be a regular expression. If + value is __focused__, then the window class must be the same as that of the + currently focused window. + **con_id**:: Compare against the internal container ID, which you can find via IPC. @@ -500,20 +505,38 @@ Currently supported attributes: Matches against floating windows. **id**:: - Compare value against the app id. Can be a regular expression. + Compare value against the X11 window id. Must be numeric. -**title**:: - Compare against the window title. Can be a regular expression. If value is - _focused_ then the window title must be the same as that of the currently - focused window. +**instance**:: + Compare value against the window instance. Can be a regular expression. If + value is __focused__, then the window instance must be the same as that of + the currently focused window. **tiling**:: Matches against tiling windows. +**title**:: + Compare against the window title. Can be a regular expression. If value is + __focused__, then the window title must be the same as that of the currently + focused window. + +**urgent**:: + Compares the urgent state of the window. Can be "latest" or "oldest". + +**window_role**:: + Compare against the window role (WM_WINDOW_ROLE). Can be a regular + expression. If value is __focused__, then the window role must be the same + as that of the currently focused window. + +**window_type**:: + Compare against the window type (_NET_WM_WINDOW_TYPE). Possible values are + normal, dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, + tooltip and notification. + **workspace**:: - Compare against the workspace name for this view. Can be a regular expression. - If the value is _focused_, then all the views on the currently focused workspace - matches. + Compare against the workspace name for this view. Can be a regular + expression. If the value is __focused__, then all the views on the currently + focused workspace matches. See Also -------- From 0a79983f9430263cf508535c7ce1aa27967b7ae8 Mon Sep 17 00:00:00 2001 From: Geoff Greer Date: Thu, 10 May 2018 23:35:37 -0700 Subject: [PATCH 4/6] Allow setting border widths for normal borders using default_border. In Sway 0.15, `default_border normal 1` would set 1px wide borders. This recreates that behavior. --- sway/commands/default_border.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sway/commands/default_border.c b/sway/commands/default_border.c index fcd2c075b..2e356d3db 100644 --- a/sway/commands/default_border.c +++ b/sway/commands/default_border.c @@ -15,12 +15,12 @@ struct cmd_results *cmd_default_border(int argc, char **argv) { config->border = B_NORMAL; } else if (strcmp(argv[0], "pixel") == 0) { config->border = B_PIXEL; - if (argc == 2) { - config->border_thickness = atoi(argv[1]); - } } else { return cmd_results_new(CMD_INVALID, "default_border", - "Expected 'default_border ' or 'default_border pixel '"); + "Expected 'default_border ' or 'default_border '"); + } + if (argc == 2) { + config->border_thickness = atoi(argv[1]); } return cmd_results_new(CMD_SUCCESS, NULL, NULL); From 87fa84df131bd30251a789360e73b6fe8162d71f Mon Sep 17 00:00:00 2001 From: Geoff Greer Date: Thu, 10 May 2018 23:44:35 -0700 Subject: [PATCH 5/6] cmd_move_container: Focus a window on the source workspace. In Sway 0.15, moving a window to another workspace would cause a window on the source workspace to be focused. This restores that behavior, allowing you to quickly move a lot of windows to another workspace. --- sway/commands/move.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sway/commands/move.c b/sway/commands/move.c index a5273ba43..890b1a8c0 100644 --- a/sway/commands/move.c +++ b/sway/commands/move.c @@ -90,12 +90,14 @@ static struct cmd_results *cmd_move_container(struct sway_container *current, } free(ws_name); struct sway_container *old_parent = current->parent; - struct sway_container *focus = seat_get_focus_inactive( + struct sway_container *destination = seat_get_focus_inactive( config->handler_context.seat, ws); - container_move_to(current, focus); - seat_set_focus(config->handler_context.seat, old_parent); + container_move_to(current, destination); + struct sway_container *focus = seat_get_focus_inactive( + config->handler_context.seat, old_parent); + seat_set_focus(config->handler_context.seat, focus); container_reap_empty(old_parent); - container_reap_empty(focus->parent); + container_reap_empty(destination->parent); return cmd_results_new(CMD_SUCCESS, NULL, NULL); } else if (strcasecmp(argv[1], "to") == 0 && strcasecmp(argv[2], "output") == 0) { From 94e42f985778ce402b4c47bf8c38ee4233b6204d Mon Sep 17 00:00:00 2001 From: Ryan Dwyer Date: Fri, 11 May 2018 14:58:24 +1000 Subject: [PATCH 6/6] Implement __focused__ criteria --- include/sway/tree/view.h | 2 + sway/criteria.c | 204 +++++++++++++++++++++++++++++++-------- sway/tree/view.c | 7 ++ 3 files changed, 171 insertions(+), 42 deletions(-) diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 144ad038b..65cadea12 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -176,6 +176,8 @@ const char *view_get_instance(struct sway_view *view); uint32_t view_get_x11_window_id(struct sway_view *view); +const char *view_get_window_role(struct sway_view *view); + uint32_t view_get_window_type(struct sway_view *view); const char *view_get_type(struct sway_view *view); diff --git a/sway/criteria.c b/sway/criteria.c index 7da790e6d..294b29228 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -190,19 +190,128 @@ static bool generate_regex(pcre **regex, char *value) { return true; } +enum criteria_token { + T_APP_ID, + T_CLASS, + T_CON_ID, + T_CON_MARK, + T_FLOATING, + T_ID, + T_INSTANCE, + T_TILING, + T_TITLE, + T_URGENT, + T_WINDOW_ROLE, + T_WINDOW_TYPE, + T_WORKSPACE, + + T_INVALID, +}; + +static enum criteria_token token_from_name(char *name) { + if (strcmp(name, "app_id") == 0) { + return T_APP_ID; + } else if (strcmp(name, "class") == 0) { + return T_CLASS; + } else if (strcmp(name, "con_id") == 0) { + return T_CON_ID; + } else if (strcmp(name, "con_mark") == 0) { + return T_CON_MARK; + } else if (strcmp(name, "id") == 0) { + return T_ID; + } else if (strcmp(name, "instance") == 0) { + return T_INSTANCE; + } else if (strcmp(name, "title") == 0) { + return T_TITLE; + } else if (strcmp(name, "urgent") == 0) { + return T_URGENT; + } else if (strcmp(name, "window_role") == 0) { + return T_WINDOW_ROLE; + } else if (strcmp(name, "window_type") == 0) { + return T_WINDOW_TYPE; + } else if (strcmp(name, "workspace") == 0) { + return T_WORKSPACE; + } + return T_INVALID; +} + +/** + * Get a property of the focused view. + * + * Note that we are taking the focused view at the time of criteria parsing, not + * at the time of execution. This is because __focused__ only makes sense when + * using criteria via IPC. Using __focused__ in config is not useful because + * criteria is only executed once per view. + */ +static char *get_focused_prop(enum criteria_token token) { + struct sway_seat *seat = input_manager_current_seat(input_manager); + struct sway_container *focus = seat_get_focus(seat); + + if (!focus || focus->type != C_VIEW) { + return NULL; + } + struct sway_view *view = focus->sway_view; + const char *value = NULL; + + switch (token) { + case T_APP_ID: + value = view_get_app_id(view); + break; + case T_CLASS: + value = view_get_class(view); + break; + case T_INSTANCE: + value = view_get_instance(view); + break; + case T_TITLE: + value = view_get_class(view); + break; + case T_WINDOW_ROLE: + value = view_get_class(view); + break; + case T_WORKSPACE: + { + struct sway_container *ws = container_parent(focus, C_WORKSPACE); + if (ws) { + value = ws->name; + } + } + break; + case T_CON_ID: // These do not support __focused__ + case T_CON_MARK: + case T_FLOATING: + case T_ID: + case T_TILING: + case T_URGENT: + case T_WINDOW_TYPE: + case T_INVALID: + break; + } + if (value) { + return strdup(value); + } + return NULL; +} + static bool parse_token(struct criteria *criteria, char *name, char *value) { + enum criteria_token token = token_from_name(name); + if (token == T_INVALID) { + const char *fmt = "Token '%s' is not recognized"; + int len = strlen(fmt) + strlen(name) - 1; + error = malloc(len); + snprintf(error, len, fmt, name); + return false; + } + + char *effective_value = NULL; + if (value && strcmp(value, "__focused__") == 0) { + effective_value = get_focused_prop(token); + } else if (value) { + effective_value = strdup(value); + } + // Require value, unless token is floating or tiled - if (!value && (strcmp(name, "title") == 0 - || strcmp(name, "app_id") == 0 - || strcmp(name, "class") == 0 - || strcmp(name, "instance") == 0 - || strcmp(name, "con_id") == 0 - || strcmp(name, "con_mark") == 0 - || strcmp(name, "window_role") == 0 - || strcmp(name, "window_type") == 0 - || strcmp(name, "id") == 0 - || strcmp(name, "urgent") == 0 - || strcmp(name, "workspace") == 0)) { + if (!effective_value && token != T_FLOATING && token != T_TILING) { const char *fmt = "Token '%s' requires a value"; int len = strlen(fmt) + strlen(name) - 1; error = malloc(len); @@ -210,53 +319,64 @@ static bool parse_token(struct criteria *criteria, char *name, char *value) { return false; } - if (strcmp(name, "title") == 0) { - generate_regex(&criteria->title, value); - } else if (strcmp(name, "app_id") == 0) { - generate_regex(&criteria->app_id, value); - } else if (strcmp(name, "class") == 0) { - generate_regex(&criteria->class, value); - } else if (strcmp(name, "instance") == 0) { - generate_regex(&criteria->instance, value); - } else if (strcmp(name, "con_id") == 0) { - char *endptr; - criteria->con_id = strtoul(value, &endptr, 10); + char *endptr = NULL; + switch (token) { + case T_TITLE: + generate_regex(&criteria->title, effective_value); + break; + case T_APP_ID: + generate_regex(&criteria->app_id, effective_value); + break; + case T_CLASS: + generate_regex(&criteria->class, effective_value); + break; + case T_INSTANCE: + generate_regex(&criteria->instance, effective_value); + break; + case T_CON_ID: + criteria->con_id = strtoul(effective_value, &endptr, 10); if (*endptr != 0) { error = strdup("The value for 'con_id' should be numeric"); } - } else if (strcmp(name, "con_mark") == 0) { - generate_regex(&criteria->con_mark, value); - } else if (strcmp(name, "window_role") == 0) { - generate_regex(&criteria->window_role, value); - } else if (strcmp(name, "window_type") == 0) { + break; + case T_CON_MARK: + generate_regex(&criteria->con_mark, effective_value); + break; + case T_WINDOW_ROLE: + generate_regex(&criteria->window_role, effective_value); + break; + case T_WINDOW_TYPE: // TODO: This is a string but will be stored as an enum or integer - } else if (strcmp(name, "id") == 0) { - char *endptr; - criteria->id = strtoul(value, &endptr, 10); + break; + case T_ID: + criteria->id = strtoul(effective_value, &endptr, 10); if (*endptr != 0) { error = strdup("The value for 'id' should be numeric"); } - } else if (strcmp(name, "floating") == 0) { + break; + case T_FLOATING: criteria->floating = true; - } else if (strcmp(name, "tiling") == 0) { + break; + case T_TILING: criteria->tiling = true; - } else if (strcmp(name, "urgent") == 0) { - if (strcmp(value, "latest") == 0) { + break; + case T_URGENT: + if (strcmp(effective_value, "latest") == 0) { criteria->urgent = 'l'; - } else if (strcmp(value, "oldest") == 0) { + } else if (strcmp(effective_value, "oldest") == 0) { criteria->urgent = 'o'; } else { error = strdup("The value for 'urgent' must be 'latest' or 'oldest'"); } - } else if (strcmp(name, "workspace") == 0) { - criteria->workspace = strdup(value); - } else { - const char *fmt = "Token '%s' is not recognized"; - int len = strlen(fmt) + strlen(name) - 1; - error = malloc(len); - snprintf(error, len, fmt, name); + break; + case T_WORKSPACE: + criteria->workspace = strdup(effective_value); + break; + case T_INVALID: + break; } + free(effective_value); if (error) { return false; diff --git a/sway/tree/view.c b/sway/tree/view.c index 7d921e0ec..ca84d82ec 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -79,6 +79,13 @@ uint32_t view_get_x11_window_id(struct sway_view *view) { return 0; } +const char *view_get_window_role(struct sway_view *view) { + if (view->impl->get_string_prop) { + return view->impl->get_string_prop(view, VIEW_PROP_WINDOW_ROLE); + } + return NULL; +} + uint32_t view_get_window_type(struct sway_view *view) { if (view->impl->get_int_prop) { return view->impl->get_int_prop(view, VIEW_PROP_WINDOW_TYPE);