aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Ryan Dwyer <ryandwyer1@gmail.com>2018-05-09 14:23:20 +1000
committerLibravatar Ryan Dwyer <ryandwyer1@gmail.com>2018-05-11 09:38:53 +1000
commit3b0c26d149dfe5e05df338692db8255a01f0998d (patch)
treec39e377cb96297df8547311a0aa4cfe8fa85b517
parentMerge pull request #1948 from RyanDwyer/focus-parent-border (diff)
downloadsway-3b0c26d149dfe5e05df338692db8255a01f0998d.tar.gz
sway-3b0c26d149dfe5e05df338692db8255a01f0998d.tar.zst
sway-3b0c26d149dfe5e05df338692db8255a01f0998d.zip
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 <name>' 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
-rw-r--r--include/sway/criteria.h73
-rw-r--r--include/sway/tree/view.h21
-rw-r--r--sway/commands.c42
-rw-r--r--sway/commands/assign.c58
-rw-r--r--sway/commands/for_window.c35
-rw-r--r--sway/criteria.c685
-rw-r--r--sway/desktop/wl_shell.c4
-rw-r--r--sway/desktop/xdg_shell_v6.c4
-rw-r--r--sway/desktop/xwayland.c69
-rw-r--r--sway/tree/view.c97
10 files changed, 581 insertions, 507 deletions
diff --git a/include/sway/criteria.h b/include/sway/criteria.h
index ec256ddb..74da132c 100644
--- a/include/sway/criteria.h
+++ b/include/sway/criteria.h
@@ -1,42 +1,61 @@
1#ifndef _SWAY_CRITERIA_H 1#ifndef _SWAY_CRITERIA_H
2#define _SWAY_CRITERIA_H 2#define _SWAY_CRITERIA_H
3 3
4#include "tree/container.h" 4#include <pcre.h>
5#include "list.h" 5#include "list.h"
6#include "tree/view.h"
6 7
7/** 8enum criteria_type {
8 * Maps criteria (as a list of criteria tokens) to a command list. 9 CT_COMMAND = 1 << 0,
9 * 10 CT_ASSIGN_OUTPUT = 1 << 1,
10 * A list of tokens together represent a single criteria string (e.g. 11 CT_ASSIGN_WORKSPACE = 1 << 2,
11 * '[class="abc" title="xyz"]' becomes two criteria tokens). 12};
12 *
13 * for_window: Views matching all criteria will have the bound command list
14 * executed on them.
15 *
16 * Set via `for_window <criteria> <cmd list>`.
17 */
18struct criteria {
19 list_t *tokens; // struct crit_token, contains compiled regex.
20 char *crit_raw; // entire criteria string (for logging)
21 13
14struct criteria {
15 enum criteria_type type;
16 char *raw; // entire criteria string (for logging)
22 char *cmdlist; 17 char *cmdlist;
18 char *target; // workspace or output name for `assign` criteria
19
20 pcre *title;
21 pcre *app_id;
22 pcre *class;
23 pcre *instance;
24 pcre *con_mark;
25 uint32_t con_id; // internal ID
26 uint32_t id; // X11 window ID
27 pcre *window_role;
28 uint32_t window_type;
29 bool floating;
30 bool tiling;
31 char urgent; // 'l' for latest or 'o' for oldest
32 char *workspace;
23}; 33};
24 34
25int criteria_cmp(const void *item, const void *data); 35bool criteria_is_empty(struct criteria *criteria);
26void free_criteria(struct criteria *crit);
27 36
28// Pouplate list with crit_tokens extracted from criteria string, returns error 37void criteria_destroy(struct criteria *criteria);
29// string or NULL if successful.
30char *extract_crit_tokens(list_t *tokens, const char *criteria);
31 38
32// Returns list of criteria that match given container. These criteria have 39/**
33// been set with `for_window` commands and have an associated cmdlist. 40 * Generate a criteria struct from a raw criteria string such as
34list_t *criteria_for(struct sway_container *cont); 41 * [class="foo" instance="bar"] (brackets inclusive).
42 *
43 * The error argument is expected to be an address of a null pointer. If an
44 * error is encountered, the function will return NULL and the pointer will be
45 * changed to point to the error string. This string should be freed afterwards.
46 */
47struct criteria *criteria_parse(char *raw, char **error);
35 48
36// Returns a list of all containers that match the given list of tokens. 49/**
37list_t *container_for_crit_tokens(list_t *tokens); 50 * Compile a list of criterias matching the given view.
51 *
52 * Criteria types can be bitwise ORed.
53 */
54list_t *criteria_for_view(struct sway_view *view, enum criteria_type types);
38 55
39// Returns true if any criteria in the given list matches this container 56/**
40bool criteria_any(struct sway_container *cont, list_t *criteria); 57 * Compile a list of views matching the given criteria.
58 */
59list_t *criteria_get_views(struct criteria *criteria);
41 60
42#endif 61#endif
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h
index 4ecd8c44..144ad038 100644
--- a/include/sway/tree/view.h
+++ b/include/sway/tree/view.h
@@ -20,11 +20,15 @@ enum sway_view_prop {
20 VIEW_PROP_APP_ID, 20 VIEW_PROP_APP_ID,
21 VIEW_PROP_CLASS, 21 VIEW_PROP_CLASS,
22 VIEW_PROP_INSTANCE, 22 VIEW_PROP_INSTANCE,
23 VIEW_PROP_WINDOW_TYPE,
24 VIEW_PROP_WINDOW_ROLE,
25 VIEW_PROP_X11_WINDOW_ID,
23}; 26};
24 27
25struct sway_view_impl { 28struct sway_view_impl {
26 const char *(*get_prop)(struct sway_view *view, 29 const char *(*get_string_prop)(struct sway_view *view,
27 enum sway_view_prop prop); 30 enum sway_view_prop prop);
31 uint32_t (*get_int_prop)(struct sway_view *view, enum sway_view_prop prop);
28 void (*configure)(struct sway_view *view, double ox, double oy, int width, 32 void (*configure)(struct sway_view *view, double ox, double oy, int width,
29 int height); 33 int height);
30 void (*set_activated)(struct sway_view *view, bool activated); 34 void (*set_activated)(struct sway_view *view, bool activated);
@@ -52,6 +56,8 @@ struct sway_view {
52 enum sway_container_border border; 56 enum sway_container_border border;
53 int border_thickness; 57 int border_thickness;
54 58
59 list_t *executed_criteria;
60
55 union { 61 union {
56 struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6; 62 struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6;
57 struct wlr_xwayland_surface *wlr_xwayland_surface; 63 struct wlr_xwayland_surface *wlr_xwayland_surface;
@@ -91,6 +97,9 @@ struct sway_xwayland_view {
91 struct wl_listener request_maximize; 97 struct wl_listener request_maximize;
92 struct wl_listener request_configure; 98 struct wl_listener request_configure;
93 struct wl_listener request_fullscreen; 99 struct wl_listener request_fullscreen;
100 struct wl_listener set_title;
101 struct wl_listener set_class;
102 struct wl_listener set_window_type;
94 struct wl_listener map; 103 struct wl_listener map;
95 struct wl_listener unmap; 104 struct wl_listener unmap;
96 struct wl_listener destroy; 105 struct wl_listener destroy;
@@ -165,6 +174,10 @@ const char *view_get_class(struct sway_view *view);
165 174
166const char *view_get_instance(struct sway_view *view); 175const char *view_get_instance(struct sway_view *view);
167 176
177uint32_t view_get_x11_window_id(struct sway_view *view);
178
179uint32_t view_get_window_type(struct sway_view *view);
180
168const char *view_get_type(struct sway_view *view); 181const char *view_get_type(struct sway_view *view);
169 182
170void view_configure(struct sway_view *view, double ox, double oy, int width, 183void 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);
217 */ 230 */
218void view_update_title(struct sway_view *view, bool force); 231void view_update_title(struct sway_view *view, bool force);
219 232
233/**
234 * Run any criteria that match the view and haven't been run on this view
235 * before.
236 */
237void view_execute_criteria(struct sway_view *view);
238
220#endif 239#endif
diff --git a/sway/commands.c b/sway/commands.c
index 2e1cdc2c..811f6cfa 100644
--- a/sway/commands.c
+++ b/sway/commands.c
@@ -12,6 +12,7 @@
12#include "sway/security.h" 12#include "sway/security.h"
13#include "sway/input/input-manager.h" 13#include "sway/input/input-manager.h"
14#include "sway/input/seat.h" 14#include "sway/input/seat.h"
15#include "sway/tree/view.h"
15#include "stringop.h" 16#include "stringop.h"
16#include "log.h" 17#include "log.h"
17 18
@@ -283,7 +284,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {
283 char *head = exec; 284 char *head = exec;
284 char *cmdlist; 285 char *cmdlist;
285 char *cmd; 286 char *cmd;
286 list_t *containers = NULL; 287 list_t *views = NULL;
287 288
288 if (seat == NULL) { 289 if (seat == NULL) {
289 // passing a NULL seat means we just pick the default seat 290 // 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) {
300 // Extract criteria (valid for this command list only). 301 // Extract criteria (valid for this command list only).
301 bool has_criteria = false; 302 bool has_criteria = false;
302 if (*head == '[') { 303 if (*head == '[') {
303 has_criteria = true; 304 char *error = NULL;
304 ++head; 305 struct criteria *criteria = criteria_parse(head, &error);
305 char *criteria_string = argsep(&head, "]"); 306 if (!criteria) {
306 if (head) { 307 results = cmd_results_new(CMD_INVALID, head,
307 ++head; 308 "%s", error);
308 list_t *tokens = create_list(); 309 free(error);
309 char *error;
310
311 if ((error = extract_crit_tokens(tokens, criteria_string))) {
312 wlr_log(L_DEBUG, "criteria string parse error: %s", error);
313 results = cmd_results_new(CMD_INVALID, criteria_string,
314 "Can't parse criteria string: %s", error);
315 free(error);
316 free(tokens);
317 goto cleanup;
318 }
319 containers = container_for_crit_tokens(tokens);
320
321 free(tokens);
322 } else {
323 if (!results) {
324 results = cmd_results_new(CMD_INVALID, criteria_string, "Unmatched [");
325 }
326 goto cleanup; 310 goto cleanup;
327 } 311 }
312 views = criteria_get_views(criteria);
313 head += strlen(criteria->raw);
314 criteria_destroy(criteria);
315 has_criteria = true;
328 // Skip leading whitespace 316 // Skip leading whitespace
329 head += strspn(head, whitespace); 317 head += strspn(head, whitespace);
330 } 318 }
@@ -381,8 +369,9 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {
381 } 369 }
382 free_cmd_results(res); 370 free_cmd_results(res);
383 } else { 371 } else {
384 for (int i = 0; i < containers->length; ++i) { 372 for (int i = 0; i < views->length; ++i) {
385 config->handler_context.current_container = containers->items[i]; 373 struct sway_view *view = views->items[i];
374 config->handler_context.current_container = view->swayc;
386 struct cmd_results *res = handler->handle(argc-1, argv+1); 375 struct cmd_results *res = handler->handle(argc-1, argv+1);
387 if (res->status != CMD_SUCCESS) { 376 if (res->status != CMD_SUCCESS) {
388 free_argv(argc, argv); 377 free_argv(argc, argv);
@@ -400,6 +389,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) {
400 } while(head); 389 } while(head);
401cleanup: 390cleanup:
402 free(exec); 391 free(exec);
392 free(views);
403 if (!results) { 393 if (!results) {
404 results = cmd_results_new(CMD_SUCCESS, NULL, NULL); 394 results = cmd_results_new(CMD_SUCCESS, NULL, NULL);
405 } 395 }
diff --git a/sway/commands/assign.c b/sway/commands/assign.c
index eb7329aa..9d15e166 100644
--- a/sway/commands/assign.c
+++ b/sway/commands/assign.c
@@ -5,6 +5,7 @@
5#include "sway/criteria.h" 5#include "sway/criteria.h"
6#include "list.h" 6#include "list.h"
7#include "log.h" 7#include "log.h"
8#include "stringop.h"
8 9
9struct cmd_results *cmd_assign(int argc, char **argv) { 10struct cmd_results *cmd_assign(int argc, char **argv) {
10 struct cmd_results *error = NULL; 11 struct cmd_results *error = NULL;
@@ -12,46 +13,39 @@ struct cmd_results *cmd_assign(int argc, char **argv) {
12 return error; 13 return error;
13 } 14 }
14 15
15 char *criteria = *argv++; 16 // Create criteria
17 char *err_str = NULL;
18 struct criteria *criteria = criteria_parse(argv[0], &err_str);
19 if (!criteria) {
20 error = cmd_results_new(CMD_INVALID, "assign", err_str);
21 free(err_str);
22 return error;
23 }
24
25 ++argv;
26 int target_len = argc - 1;
16 27
17 if (strncmp(*argv, "→", strlen("→")) == 0) { 28 if (strncmp(*argv, "→", strlen("→")) == 0) {
18 if (argc < 3) { 29 if (argc < 3) {
19 return cmd_results_new(CMD_INVALID, "assign", "Missing workspace"); 30 return cmd_results_new(CMD_INVALID, "assign", "Missing workspace");
20 } 31 }
21 argv++; 32 ++argv;
33 --target_len;
22 } 34 }
23 35
24 char *movecmd = "move container to workspace "; 36 if (strcmp(*argv, "output") == 0) {
25 size_t arglen = strlen(movecmd) + strlen(*argv) + 1; 37 criteria->type = CT_ASSIGN_OUTPUT;
26 char *cmdlist = calloc(1, arglen); 38 ++argv;
27 if (!cmdlist) { 39 --target_len;
28 return cmd_results_new(CMD_FAILURE, "assign", "Unable to allocate command list"); 40 } else {
41 criteria->type = CT_ASSIGN_WORKSPACE;
29 } 42 }
30 snprintf(cmdlist, arglen, "%s%s", movecmd, *argv);
31 43
32 struct criteria *crit = malloc(sizeof(struct criteria)); 44 criteria->target = join_args(argv, target_len);
33 if (!crit) {
34 free(cmdlist);
35 return cmd_results_new(CMD_FAILURE, "assign", "Unable to allocate criteria");
36 }
37 crit->crit_raw = strdup(criteria);
38 crit->cmdlist = cmdlist;
39 crit->tokens = create_list();
40 char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw);
41 45
42 if (err_str) { 46 list_add(config->criteria, criteria);
43 error = cmd_results_new(CMD_INVALID, "assign", err_str); 47 wlr_log(L_DEBUG, "assign: '%s' -> '%s' added", criteria->raw,
44 free(err_str); 48 criteria->target);
45 free_criteria(crit); 49
46 } else if (crit->tokens->length == 0) { 50 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
47 error = cmd_results_new(CMD_INVALID, "assign", "Found no name/value pairs in criteria");
48 free_criteria(crit);
49 } else if (list_seq_find(config->criteria, criteria_cmp, crit) != -1) {
50 wlr_log(L_DEBUG, "assign: Duplicate, skipping.");
51 free_criteria(crit);
52 } else {
53 wlr_log(L_DEBUG, "assign: '%s' -> '%s' added", crit->crit_raw, crit->cmdlist);
54 list_add(config->criteria, crit);
55 }
56 return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL);
57} 51}
diff --git a/sway/commands/for_window.c b/sway/commands/for_window.c
index dd5461f0..8c425a1d 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) {
11 if ((error = checkarg(argc, "for_window", EXPECTED_AT_LEAST, 2))) { 11 if ((error = checkarg(argc, "for_window", EXPECTED_AT_LEAST, 2))) {
12 return error; 12 return error;
13 } 13 }
14 // add command to a criteria/command pair that is run against views when they appear.
15 char *criteria = argv[0], *cmdlist = join_args(argv + 1, argc - 1);
16 14
17 struct criteria *crit = calloc(sizeof(struct criteria), 1); 15 char *err_str = NULL;
18 if (!crit) { 16 struct criteria *criteria = criteria_parse(argv[0], &err_str);
19 return cmd_results_new(CMD_FAILURE, "for_window", "Unable to allocate criteria"); 17 if (!criteria) {
20 }
21 crit->crit_raw = strdup(criteria);
22 crit->cmdlist = cmdlist;
23 crit->tokens = create_list();
24 char *err_str = extract_crit_tokens(crit->tokens, crit->crit_raw);
25
26 if (err_str) {
27 error = cmd_results_new(CMD_INVALID, "for_window", err_str); 18 error = cmd_results_new(CMD_INVALID, "for_window", err_str);
28 free(err_str); 19 free(err_str);
29 free_criteria(crit); 20 return error;
30 } else if (crit->tokens->length == 0) {
31 error = cmd_results_new(CMD_INVALID, "for_window", "Found no name/value pairs in criteria");
32 free_criteria(crit);
33 } else if (list_seq_find(config->criteria, criteria_cmp, crit) != -1) {
34 wlr_log(L_DEBUG, "for_window: Duplicate, skipping.");
35 free_criteria(crit);
36 } else {
37 wlr_log(L_DEBUG, "for_window: '%s' -> '%s' added", crit->crit_raw, crit->cmdlist);
38 list_add(config->criteria, crit);
39 } 21 }
40 return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL); 22
23 criteria->type = CT_COMMAND;
24 criteria->cmdlist = join_args(argv + 1, argc - 1);
25
26 list_add(config->criteria, criteria);
27 wlr_log(L_DEBUG, "for_window: '%s' -> '%s' added", criteria->raw, criteria->cmdlist);
28
29 return cmd_results_new(CMD_SUCCESS, NULL, NULL);
41} 30}
diff --git a/sway/criteria.c b/sway/criteria.c
index 22e9a49b..6abe24af 100644
--- a/sway/criteria.c
+++ b/sway/criteria.c
@@ -11,435 +11,382 @@
11#include "list.h" 11#include "list.h"
12#include "log.h" 12#include "log.h"
13 13
14enum criteria_type { // *must* keep in sync with criteria_strings[] 14bool criteria_is_empty(struct criteria *criteria) {
15 CRIT_APP_ID, 15 return !criteria->title
16 CRIT_CLASS, 16 && !criteria->app_id
17 CRIT_CON_ID, 17 && !criteria->class
18 CRIT_CON_MARK, 18 && !criteria->instance
19 CRIT_FLOATING, 19 && !criteria->con_mark
20 CRIT_ID, 20 && !criteria->con_id
21 CRIT_INSTANCE, 21 && !criteria->id
22 CRIT_TILING, 22 && !criteria->window_role
23 CRIT_TITLE, 23 && !criteria->window_type
24 CRIT_URGENT, 24 && !criteria->floating
25 CRIT_WINDOW_ROLE, 25 && !criteria->tiling
26 CRIT_WINDOW_TYPE, 26 && !criteria->urgent
27 CRIT_WORKSPACE, 27 && !criteria->workspace;
28 CRIT_LAST 28}
29};
30
31static const char * const criteria_strings[CRIT_LAST] = {
32 [CRIT_APP_ID] = "app_id",
33 [CRIT_CLASS] = "class",
34 [CRIT_CON_ID] = "con_id",
35 [CRIT_CON_MARK] = "con_mark",
36 [CRIT_FLOATING] = "floating",
37 [CRIT_ID] = "id",
38 [CRIT_INSTANCE] = "instance",
39 [CRIT_TILING] = "tiling",
40 [CRIT_TITLE] = "title",
41 [CRIT_URGENT] = "urgent", // either "latest" or "oldest" ...
42 [CRIT_WINDOW_ROLE] = "window_role",
43 [CRIT_WINDOW_TYPE] = "window_type",
44 [CRIT_WORKSPACE] = "workspace"
45};
46 29
47/** 30void criteria_destroy(struct criteria *criteria) {
48 * A single criteria token (ie. value/regex pair), 31 pcre_free(criteria->title);
49 * e.g. 'class="some class regex"'. 32 pcre_free(criteria->app_id);
50 */ 33 pcre_free(criteria->class);
51struct crit_token { 34 pcre_free(criteria->instance);
52 enum criteria_type type; 35 pcre_free(criteria->con_mark);
53 pcre *regex; 36 pcre_free(criteria->window_role);
54 char *raw; 37 free(criteria->workspace);
55}; 38
39 free(criteria->raw);
40 free(criteria);
41}
56 42
57static void free_crit_token(struct crit_token *crit) { 43static int regex_cmp(const char *item, const pcre *regex) {
58 pcre_free(crit->regex); 44 return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0);
59 free(crit->raw);
60 free(crit);
61} 45}
62 46
63static void free_crit_tokens(list_t *crit_tokens) { 47static bool criteria_matches_view(struct criteria *criteria,
64 for (int i = 0; i < crit_tokens->length; i++) { 48 struct sway_view *view) {
65 free_crit_token(crit_tokens->items[i]); 49 if (criteria->title) {
50 const char *title = view_get_title(view);
51 if (!title || regex_cmp(title, criteria->title) != 0) {
52 return false;
53 }
66 } 54 }
67 list_free(crit_tokens);
68}
69 55
70// Extracts criteria string from its brackets. Returns new (duplicate) 56 if (criteria->app_id) {
71// substring. 57 const char *app_id = view_get_app_id(view);
72static char *criteria_from(const char *arg) { 58 if (!app_id || regex_cmp(app_id, criteria->app_id) != 0) {
73 char *criteria = NULL; 59 return false;
74 if (*arg == '[') { 60 }
75 criteria = strdup(arg + 1);
76 } else {
77 criteria = strdup(arg);
78 } 61 }
79 62
80 int last = strlen(criteria) - 1; 63 if (criteria->class) {
81 if (criteria[last] == ']') { 64 const char *class = view_get_class(view);
82 criteria[last] = '\0'; 65 if (!class || regex_cmp(class, criteria->class) != 0) {
66 return false;
67 }
83 } 68 }
84 return criteria;
85}
86 69
87// Return instances of c found in str. 70 if (criteria->instance) {
88static int countchr(char *str, char c) { 71 const char *instance = view_get_instance(view);
89 int found = 0; 72 if (!instance || regex_cmp(instance, criteria->instance) != 0) {
90 for (int i = 0; str[i]; i++) { 73 return false;
91 if (str[i] == c) {
92 ++found;
93 } 74 }
94 } 75 }
95 return found;
96}
97 76
98// criteria_str is e.g. '[class="some class regex" instance="instance name"]'. 77 if (criteria->con_mark) {
99// 78 // TODO
100// Will create array of pointers in buf, where first is duplicate of given 79 return false;
101// string (must be freed) and the rest are pointers to names and values in the 80 }
102// base string (every other, naturally). argc will be populated with the length 81
103// of buf. 82 if (criteria->con_id) { // Internal ID
104// 83 if (!view->swayc || view->swayc->id != criteria->con_id) {
105// Returns error string or NULL if successful. 84 return false;
106static char *crit_tokens(int *argc, char ***buf,
107 const char * const criteria_str) {
108 wlr_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str);
109 char *base = criteria_from(criteria_str);
110 char *head = base;
111 char *namep = head; // start of criteria name
112 char *valp = NULL; // start of value
113
114 // We're going to place EOS markers where we need to and fill up an array
115 // of pointers to the start of each token (either name or value).
116 int pairs = countchr(base, '=');
117 int max_tokens = pairs * 2 + 1; // this gives us at least enough slots
118
119 char **argv = *buf = calloc(max_tokens, sizeof(char*));
120 argv[0] = base; // this needs to be freed by caller
121 bool quoted = true;
122
123 *argc = 1; // uneven = name, even = value
124 while (*head && *argc < max_tokens) {
125 if (namep != head && *(head - 1) == '\\') {
126 // escaped character: don't try to parse this
127 } else if (*head == '=' && namep != head) {
128 if (*argc % 2 != 1) {
129 // we're not expecting a name
130 return strdup("Unable to parse criteria: "
131 "Found out of place equal sign");
132 } else {
133 // name ends here
134 char *end = head; // don't want to rewind the head
135 while (*(end - 1) == ' ') {
136 --end;
137 }
138 *end = '\0';
139 if (*(namep) == ' ') {
140 namep = strrchr(namep, ' ') + 1;
141 }
142 argv[*argc] = namep;
143 *argc += 1;
144 }
145 } else if (*head == '"') {
146 if (*argc % 2 != 0) {
147 // we're not expecting a value
148 return strdup("Unable to parse criteria: "
149 "Found quoted value where it was not expected");
150 } else if (!valp) { // value starts here
151 valp = head + 1;
152 quoted = true;
153 } else {
154 // value ends here
155 argv[*argc] = valp;
156 *argc += 1;
157 *head = '\0';
158 valp = NULL;
159 namep = head + 1;
160 }
161 } else if (*argc % 2 == 0 && *head != ' ') {
162 // parse unquoted values
163 if (!valp) {
164 quoted = false;
165 valp = head; // value starts here
166 }
167 } else if (valp && !quoted && *head == ' ') {
168 // value ends here
169 argv[*argc] = valp;
170 *argc += 1;
171 *head = '\0';
172 valp = NULL;
173 namep = head + 1;
174 } 85 }
175 head++;
176 } 86 }
177 87
178 // catch last unquoted value if needed 88 if (criteria->id) { // X11 window ID
179 if (valp && !quoted && !*head) { 89 uint32_t x11_window_id = view_get_x11_window_id(view);
180 argv[*argc] = valp; 90 if (!x11_window_id || x11_window_id != criteria->id) {
181 *argc += 1; 91 return false;
92 }
182 } 93 }
183 94
184 return NULL; 95 if (criteria->window_role) {
96 // TODO
97 }
98
99 if (criteria->window_type) {
100 uint32_t type = view_get_window_type(view);
101 if (!type || type != criteria->window_type) {
102 return false;
103 }
104 }
105
106 if (criteria->floating) {
107 // TODO
108 return false;
109 }
110
111 if (criteria->tiling) {
112 // TODO
113 }
114
115 if (criteria->urgent) {
116 // TODO
117 return false;
118 }
119
120 if (criteria->workspace) {
121 if (!view->swayc) {
122 return false;
123 }
124 struct sway_container *ws = container_parent(view->swayc, C_WORKSPACE);
125 if (!ws || strcmp(ws->name, criteria->workspace) != 0) {
126 return false;
127 }
128 }
129
130 return true;
185} 131}
186 132
187// Returns error string on failure or NULL otherwise. 133list_t *criteria_for_view(struct sway_view *view, enum criteria_type types) {
188static char *parse_criteria_name(enum criteria_type *type, char *name) { 134 list_t *criterias = config->criteria;
189 *type = CRIT_LAST; 135 list_t *matches = create_list();
190 for (int i = 0; i < CRIT_LAST; i++) { 136 for (int i = 0; i < criterias->length; ++i) {
191 if (strcmp(criteria_strings[i], name) == 0) { 137 struct criteria *criteria = criterias->items[i];
192 *type = (enum criteria_type) i; 138 if ((criteria->type & types) && criteria_matches_view(criteria, view)) {
193 break; 139 list_add(matches, criteria);
194 } 140 }
195 } 141 }
196 if (*type == CRIT_LAST) { 142 return matches;
197 const char *fmt = "Criteria type '%s' is invalid or unsupported."; 143}
198 int len = strlen(name) + strlen(fmt) - 1; 144
199 char *error = malloc(len); 145struct match_data {
200 snprintf(error, len, fmt, name); 146 struct criteria *criteria;
201 return error; 147 list_t *matches;
202 } else if (*type == CRIT_URGENT || *type == CRIT_WINDOW_ROLE || 148};
203 *type == CRIT_WINDOW_TYPE) { 149
204 // (we're just being helpful here) 150static void criteria_get_views_iterator(struct sway_container *container,
205 const char *fmt = "\"%s\" criteria currently unsupported, " 151 void *data) {
206 "no window will match this"; 152 struct match_data *match_data = data;
207 int len = strlen(fmt) + strlen(name) - 1; 153 if (container->type == C_VIEW) {
208 char *error = malloc(len); 154 if (criteria_matches_view(match_data->criteria, container->sway_view)) {
209 snprintf(error, len, fmt, name); 155 list_add(match_data->matches, container->sway_view);
210 return error; 156 }
211 } 157 }
212 return NULL;
213} 158}
214 159
160list_t *criteria_get_views(struct criteria *criteria) {
161 list_t *matches = create_list();
162 struct match_data data = {
163 .criteria = criteria,
164 .matches = matches,
165 };
166 container_for_each_descendant_dfs(&root_container,
167 criteria_get_views_iterator, &data);
168 return matches;
169}
170
171// The error pointer is used for parsing functions, and saves having to pass it
172// as an argument in several places.
173char *error = NULL;
174
215// Returns error string on failure or NULL otherwise. 175// Returns error string on failure or NULL otherwise.
216static char *generate_regex(pcre **regex, char *value) { 176static bool generate_regex(pcre **regex, char *value) {
217 const char *reg_err; 177 const char *reg_err;
218 int offset; 178 int offset;
219 179
220 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_err, &offset, NULL); 180 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_err, &offset, NULL);
221 181
222 if (!*regex) { 182 if (!*regex) {
223 const char *fmt = "Regex compilation (for '%s') failed: %s"; 183 const char *fmt = "Regex compilation for '%s' failed: %s";
224 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; 184 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3;
225 char *error = malloc(len); 185 error = malloc(len);
226 snprintf(error, len, fmt, value, reg_err); 186 snprintf(error, len, fmt, value, reg_err);
227 return error; 187 return false;
228 } 188 }
229 return NULL;
230}
231 189
232// Test whether the criterion corresponds to the currently focused window 190 return true;
233static bool crit_is_focused(const char *value) {
234 return !strcmp(value, "focused") || !strcmp(value, "__focused__");
235} 191}
236 192
237// Populate list with crit_tokens extracted from criteria string, returns error 193static bool parse_token(struct criteria *criteria, char *name, char *value) {
238// string or NULL if successful. 194 // Require value, unless token is floating or tiled
239char *extract_crit_tokens(list_t *tokens, const char * const criteria) { 195 if (!value && (strcmp(name, "title") == 0
240 int argc; 196 || strcmp(name, "app_id") == 0
241 char **argv = NULL, *error = NULL; 197 || strcmp(name, "class") == 0
242 if ((error = crit_tokens(&argc, &argv, criteria))) { 198 || strcmp(name, "instance") == 0
243 goto ect_cleanup; 199 || strcmp(name, "con_id") == 0
200 || strcmp(name, "con_mark") == 0
201 || strcmp(name, "window_role") == 0
202 || strcmp(name, "window_type") == 0
203 || strcmp(name, "id") == 0
204 || strcmp(name, "urgent") == 0
205 || strcmp(name, "workspace") == 0)) {
206 const char *fmt = "Token '%s' requires a value";
207 int len = strlen(fmt) + strlen(name) - 1;
208 error = malloc(len);
209 snprintf(error, len, fmt, name);
210 return false;
244 } 211 }
245 for (int i = 1; i + 1 < argc; i += 2) { 212
246 char* name = argv[i], *value = argv[i + 1]; 213 if (strcmp(name, "title") == 0) {
247 struct crit_token *token = calloc(1, sizeof(struct crit_token)); 214 generate_regex(&criteria->title, value);
248 token->raw = strdup(value); 215 } else if (strcmp(name, "app_id") == 0) {
249 216 generate_regex(&criteria->app_id, value);
250 if ((error = parse_criteria_name(&token->type, name))) { 217 } else if (strcmp(name, "class") == 0) {
251 free_crit_token(token); 218 generate_regex(&criteria->class, value);
252 goto ect_cleanup; 219 } else if (strcmp(name, "instance") == 0) {
253 } else if (token->type == CRIT_URGENT || crit_is_focused(value)) { 220 generate_regex(&criteria->instance, value);
254 wlr_log(L_DEBUG, "%s -> \"%s\"", name, value); 221 } else if (strcmp(name, "con_id") == 0) {
255 list_add(tokens, token); 222 char *endptr;
256 } else if((error = generate_regex(&token->regex, value))) { 223 criteria->con_id = strtoul(value, &endptr, 10);
257 free_crit_token(token); 224 if (*endptr != 0) {
258 goto ect_cleanup; 225 error = strdup("The value for 'con_id' should be numeric");
226 }
227 } else if (strcmp(name, "con_mark") == 0) {
228 generate_regex(&criteria->con_mark, value);
229 } else if (strcmp(name, "window_role") == 0) {
230 generate_regex(&criteria->window_role, value);
231 } else if (strcmp(name, "window_type") == 0) {
232 // TODO: This is a string but will be stored as an enum or integer
233 } else if (strcmp(name, "id") == 0) {
234 char *endptr;
235 criteria->id = strtoul(value, &endptr, 10);
236 if (*endptr != 0) {
237 error = strdup("The value for 'id' should be numeric");
238 }
239 } else if (strcmp(name, "floating") == 0) {
240 criteria->floating = true;
241 } else if (strcmp(name, "tiling") == 0) {
242 criteria->tiling = true;
243 } else if (strcmp(name, "urgent") == 0) {
244 if (strcmp(value, "latest") == 0) {
245 criteria->urgent = 'l';
246 } else if (strcmp(value, "oldest") == 0) {
247 criteria->urgent = 'o';
259 } else { 248 } else {
260 wlr_log(L_DEBUG, "%s -> /%s/", name, value); 249 error =
261 list_add(tokens, token); 250 strdup("The value for 'urgent' must be 'latest' or 'oldest'");
262 } 251 }
252 } else if (strcmp(name, "workspace") == 0) {
253 criteria->workspace = strdup(value);
254 } else {
255 const char *fmt = "Token '%s' is not recognized";
256 int len = strlen(fmt) + strlen(name) - 1;
257 error = malloc(len);
258 snprintf(error, len, fmt, name);
263 } 259 }
264ect_cleanup:
265 free(argv[0]); // base string
266 free(argv);
267 return error;
268}
269
270static int regex_cmp(const char *item, const pcre *regex) {
271 return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0);
272}
273 260
274// test a single view if it matches list of criteria tokens (all of them). 261 if (error) {
275static bool criteria_test(struct sway_container *cont, list_t *tokens) {
276 if (cont->type != C_CONTAINER && cont->type != C_VIEW) {
277 return false; 262 return false;
278 } 263 }
279 int matches = 0;
280 for (int i = 0; i < tokens->length; i++) {
281 struct crit_token *crit = tokens->items[i];
282 switch (crit->type) {
283 case CRIT_CLASS:
284 {
285 const char *class = view_get_class(cont->sway_view);
286 if (!class) {
287 break;
288 }
289 if (crit->regex && regex_cmp(class, crit->regex) == 0) {
290 matches++;
291 }
292 break;
293 }
294 case CRIT_CON_ID:
295 {
296 char *endptr;
297 size_t crit_id = strtoul(crit->raw, &endptr, 10);
298
299 if (*endptr == 0 && cont->id == crit_id) {
300 ++matches;
301 }
302 break;
303 }
304 case CRIT_CON_MARK:
305 // TODO
306 break;
307 case CRIT_FLOATING:
308 // TODO
309 break;
310 case CRIT_ID:
311 // TODO
312 break;
313 case CRIT_APP_ID:
314 {
315 const char *app_id = view_get_app_id(cont->sway_view);
316 if (!app_id) {
317 break;
318 }
319
320 if (crit->regex && regex_cmp(app_id, crit->regex) == 0) {
321 matches++;
322 }
323 break;
324 }
325 case CRIT_INSTANCE:
326 {
327 const char *instance = view_get_instance(cont->sway_view);
328 if (!instance) {
329 break;
330 }
331
332 if (crit->regex && regex_cmp(instance, crit->regex) == 0) {
333 matches++;
334 }
335 break;
336 }
337 case CRIT_TILING:
338 // TODO
339 break;
340 case CRIT_TITLE:
341 {
342 const char *title = view_get_title(cont->sway_view);
343 if (!title) {
344 break;
345 }
346 264
347 if (crit->regex && regex_cmp(title, crit->regex) == 0) { 265 return true;
348 matches++;
349 }
350 break;
351 }
352 case CRIT_URGENT:
353 // TODO "latest" or "oldest"
354 break;
355 case CRIT_WINDOW_ROLE:
356 // TODO
357 break;
358 case CRIT_WINDOW_TYPE:
359 // TODO
360 break;
361 case CRIT_WORKSPACE:
362 // TODO
363 break;
364 default:
365 sway_abort("Invalid criteria type (%i)", crit->type);
366 break;
367 }
368 }
369 return matches == tokens->length;
370} 266}
371 267
372int criteria_cmp(const void *a, const void *b) { 268static void skip_spaces(char **head) {
373 if (a == b) { 269 while (**head == ' ') {
374 return 0; 270 ++*head;
375 } else if (!a) {
376 return -1;
377 } else if (!b) {
378 return 1;
379 } 271 }
380 const struct criteria *crit_a = a, *crit_b = b;
381 int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist);
382 if (cmp != 0) {
383 return cmp;
384 }
385 return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw);
386} 272}
387 273
388void free_criteria(struct criteria *crit) { 274// Remove escaping slashes from value
389 if (crit->tokens) { 275static void unescape(char *value) {
390 free_crit_tokens(crit->tokens); 276 if (!strchr(value, '\\')) {
391 } 277 return;
392 if (crit->cmdlist) {
393 free(crit->cmdlist);
394 } 278 }
395 if (crit->crit_raw) { 279 char *copy = calloc(strlen(value) + 1, 1);
396 free(crit->crit_raw); 280 char *readhead = value;
397 } 281 char *writehead = copy;
398 free(crit); 282 while (*readhead) {
399} 283 if (*readhead == '\\' &&
400 284 (*(readhead + 1) == '"' || *(readhead + 1) == '\\')) {
401bool criteria_any(struct sway_container *cont, list_t *criteria) { 285 // skip the slash
402 for (int i = 0; i < criteria->length; i++) { 286 ++readhead;
403 struct criteria *bc = criteria->items[i];
404 if (criteria_test(cont, bc->tokens)) {
405 return true;
406 } 287 }
288 *writehead = *readhead;
289 ++writehead;
290 ++readhead;
407 } 291 }
408 return false; 292 strcpy(value, copy);
293 free(copy);
409} 294}
410 295
411list_t *criteria_for(struct sway_container *cont) { 296/**
412 list_t *criteria = config->criteria, *matches = create_list(); 297 * Parse a raw criteria string such as [class="foo" instance="bar"] into a
413 for (int i = 0; i < criteria->length; i++) { 298 * criteria struct.
414 struct criteria *bc = criteria->items[i]; 299 *
415 if (criteria_test(cont, bc->tokens)) { 300 * If errors are found, NULL will be returned and the error argument will be
416 list_add(matches, bc); 301 * populated with an error string.
302 */
303struct criteria *criteria_parse(char *raw, char **error_arg) {
304 free(error);
305 error = NULL;
306
307 char *head = raw;
308 skip_spaces(&head);
309 if (*head != '[') {
310 *error_arg = strdup("No criteria");
311 return NULL;
312 }
313 ++head;
314
315 struct criteria *criteria = calloc(sizeof(struct criteria), 1);
316 char *name = NULL, *value = NULL;
317 bool in_quotes = false;
318
319 while (*head && *head != ']') {
320 skip_spaces(&head);
321 // Parse token name
322 char *namestart = head;
323 while ((*head >= 'a' && *head <= 'z') || *head == '_') {
324 ++head;
417 } 325 }
326 name = calloc(head - namestart + 1, 1);
327 strncpy(name, namestart, head - namestart);
328 // Parse token value
329 skip_spaces(&head);
330 value = NULL;
331 if (*head == '=') {
332 ++head;
333 skip_spaces(&head);
334 if (*head == '"') {
335 in_quotes = true;
336 ++head;
337 }
338 char *valuestart = head;
339 if (in_quotes) {
340 while (*head && (*head != '"' || *(head - 1) == '\\')) {
341 ++head;
342 }
343 if (!*head) {
344 *error_arg = strdup("Quote mismatch in criteria");
345 goto cleanup;
346 }
347 } else {
348 while (*head && *head != ' ' && *head != ']') {
349 ++head;
350 }
351 }
352 value = calloc(head - valuestart + 1, 1);
353 strncpy(value, valuestart, head - valuestart);
354 if (in_quotes) {
355 ++head;
356 in_quotes = false;
357 }
358 unescape(value);
359 }
360 wlr_log(L_DEBUG, "Found pair: %s=%s", name, value);
361 if (!parse_token(criteria, name, value)) {
362 *error_arg = error;
363 goto cleanup;
364 }
365 skip_spaces(&head);
366 free(name);
367 free(value);
368 name = NULL;
369 value = NULL;
418 } 370 }
419 return matches; 371 if (*head != ']') {
420} 372 *error_arg = strdup("No closing brace found in criteria");
421 373 goto cleanup;
422struct list_tokens {
423 list_t *list;
424 list_t *tokens;
425};
426
427static void container_match_add(struct sway_container *container,
428 struct list_tokens *list_tokens) {
429 if (criteria_test(container, list_tokens->tokens)) {
430 list_add(list_tokens->list, container);
431 } 374 }
432}
433 375
434list_t *container_for_crit_tokens(list_t *tokens) { 376 if (criteria_is_empty(criteria)) {
435 struct list_tokens list_tokens = 377 *error_arg = strdup("Criteria is empty");
436 (struct list_tokens){create_list(), tokens}; 378 goto cleanup;
379 }
437 380
438 container_for_each_descendant_dfs(&root_container, 381 ++head;
439 (void (*)(struct sway_container *, void *))container_match_add, 382 int len = head - raw;
440 &list_tokens); 383 criteria->raw = calloc(len + 1, 1);
384 strncpy(criteria->raw, raw, len);
385 return criteria;
441 386
442 // TODO look in the scratchpad 387cleanup:
443 388 free(name);
444 return list_tokens.list; 389 free(value);
390 criteria_destroy(criteria);
391 return NULL;
445} 392}
diff --git a/sway/desktop/wl_shell.c b/sway/desktop/wl_shell.c
index 99e8947b..cb3774f7 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(
20 return (struct sway_wl_shell_view *)view; 20 return (struct sway_wl_shell_view *)view;
21} 21}
22 22
23static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { 23static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {
24 if (wl_shell_view_from_view(view) == NULL) { 24 if (wl_shell_view_from_view(view) == NULL) {
25 return NULL; 25 return NULL;
26 } 26 }
@@ -70,7 +70,7 @@ static void set_fullscreen(struct sway_view *view, bool fullscreen) {
70} 70}
71 71
72static const struct sway_view_impl view_impl = { 72static const struct sway_view_impl view_impl = {
73 .get_prop = get_prop, 73 .get_string_prop = get_string_prop,
74 .configure = configure, 74 .configure = configure,
75 .close = _close, 75 .close = _close,
76 .destroy = destroy, 76 .destroy = destroy,
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c
index 8ecb330d..f685ef71 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(
80 return (struct sway_xdg_shell_v6_view *)view; 80 return (struct sway_xdg_shell_v6_view *)view;
81} 81}
82 82
83static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { 83static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {
84 if (xdg_shell_v6_view_from_view(view) == NULL) { 84 if (xdg_shell_v6_view_from_view(view) == NULL) {
85 return NULL; 85 return NULL;
86 } 86 }
@@ -158,7 +158,7 @@ static void destroy(struct sway_view *view) {
158} 158}
159 159
160static const struct sway_view_impl view_impl = { 160static const struct sway_view_impl view_impl = {
161 .get_prop = get_prop, 161 .get_string_prop = get_string_prop,
162 .configure = configure, 162 .configure = configure,
163 .set_activated = set_activated, 163 .set_activated = set_activated,
164 .set_fullscreen = set_fullscreen, 164 .set_fullscreen = set_fullscreen,
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c
index 8f935760..554c070e 100644
--- a/sway/desktop/xwayland.c
+++ b/sway/desktop/xwayland.c
@@ -126,7 +126,7 @@ static struct sway_xwayland_view *xwayland_view_from_view(
126 return (struct sway_xwayland_view *)view; 126 return (struct sway_xwayland_view *)view;
127} 127}
128 128
129static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { 129static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) {
130 if (xwayland_view_from_view(view) == NULL) { 130 if (xwayland_view_from_view(view) == NULL) {
131 return NULL; 131 return NULL;
132 } 132 }
@@ -135,11 +135,27 @@ static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) {
135 return view->wlr_xwayland_surface->title; 135 return view->wlr_xwayland_surface->title;
136 case VIEW_PROP_CLASS: 136 case VIEW_PROP_CLASS:
137 return view->wlr_xwayland_surface->class; 137 return view->wlr_xwayland_surface->class;
138 case VIEW_PROP_INSTANCE:
139 return view->wlr_xwayland_surface->instance;
138 default: 140 default:
139 return NULL; 141 return NULL;
140 } 142 }
141} 143}
142 144
145static uint32_t get_int_prop(struct sway_view *view, enum sway_view_prop prop) {
146 if (xwayland_view_from_view(view) == NULL) {
147 return 0;
148 }
149 switch (prop) {
150 case VIEW_PROP_X11_WINDOW_ID:
151 return view->wlr_xwayland_surface->window_id;
152 case VIEW_PROP_WINDOW_TYPE:
153 return *view->wlr_xwayland_surface->window_type;
154 default:
155 return 0;
156 }
157}
158
143static void configure(struct sway_view *view, double ox, double oy, int width, 159static void configure(struct sway_view *view, double ox, double oy, int width,
144 int height) { 160 int height) {
145 struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view); 161 struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view);
@@ -200,13 +216,17 @@ static void destroy(struct sway_view *view) {
200 wl_list_remove(&xwayland_view->destroy.link); 216 wl_list_remove(&xwayland_view->destroy.link);
201 wl_list_remove(&xwayland_view->request_configure.link); 217 wl_list_remove(&xwayland_view->request_configure.link);
202 wl_list_remove(&xwayland_view->request_fullscreen.link); 218 wl_list_remove(&xwayland_view->request_fullscreen.link);
219 wl_list_remove(&xwayland_view->set_title.link);
220 wl_list_remove(&xwayland_view->set_class.link);
221 wl_list_remove(&xwayland_view->set_window_type.link);
203 wl_list_remove(&xwayland_view->map.link); 222 wl_list_remove(&xwayland_view->map.link);
204 wl_list_remove(&xwayland_view->unmap.link); 223 wl_list_remove(&xwayland_view->unmap.link);
205 free(xwayland_view); 224 free(xwayland_view);
206} 225}
207 226
208static const struct sway_view_impl view_impl = { 227static const struct sway_view_impl view_impl = {
209 .get_prop = get_prop, 228 .get_string_prop = get_string_prop,
229 .get_int_prop = get_int_prop,
210 .configure = configure, 230 .configure = configure,
211 .set_activated = set_activated, 231 .set_activated = set_activated,
212 .set_fullscreen = set_fullscreen, 232 .set_fullscreen = set_fullscreen,
@@ -223,7 +243,6 @@ static void handle_commit(struct wl_listener *listener, void *data) {
223 view_update_size(view, xwayland_view->pending_width, 243 view_update_size(view, xwayland_view->pending_width,
224 xwayland_view->pending_height); 244 xwayland_view->pending_height);
225 view_damage_from(view); 245 view_damage_from(view);
226 view_update_title(view, false);
227} 246}
228 247
229static void handle_unmap(struct wl_listener *listener, void *data) { 248static void handle_unmap(struct wl_listener *listener, void *data) {
@@ -285,6 +304,40 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data)
285 view_set_fullscreen(view, xsurface->fullscreen); 304 view_set_fullscreen(view, xsurface->fullscreen);
286} 305}
287 306
307static void handle_set_title(struct wl_listener *listener, void *data) {
308 struct sway_xwayland_view *xwayland_view =
309 wl_container_of(listener, xwayland_view, set_title);
310 struct sway_view *view = &xwayland_view->view;
311 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
312 if (!xsurface->mapped) {
313 return;
314 }
315 view_update_title(view, false);
316 view_execute_criteria(view);
317}
318
319static void handle_set_class(struct wl_listener *listener, void *data) {
320 struct sway_xwayland_view *xwayland_view =
321 wl_container_of(listener, xwayland_view, set_class);
322 struct sway_view *view = &xwayland_view->view;
323 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
324 if (!xsurface->mapped) {
325 return;
326 }
327 view_execute_criteria(view);
328}
329
330static void handle_set_window_type(struct wl_listener *listener, void *data) {
331 struct sway_xwayland_view *xwayland_view =
332 wl_container_of(listener, xwayland_view, set_window_type);
333 struct sway_view *view = &xwayland_view->view;
334 struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface;
335 if (!xsurface->mapped) {
336 return;
337 }
338 view_execute_criteria(view);
339}
340
288void handle_xwayland_surface(struct wl_listener *listener, void *data) { 341void handle_xwayland_surface(struct wl_listener *listener, void *data) {
289 struct sway_server *server = wl_container_of(listener, server, 342 struct sway_server *server = wl_container_of(listener, server,
290 xwayland_surface); 343 xwayland_surface);
@@ -323,6 +376,16 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) {
323 &xwayland_view->request_fullscreen); 376 &xwayland_view->request_fullscreen);
324 xwayland_view->request_fullscreen.notify = handle_request_fullscreen; 377 xwayland_view->request_fullscreen.notify = handle_request_fullscreen;
325 378
379 wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title);
380 xwayland_view->set_title.notify = handle_set_title;
381
382 wl_signal_add(&xsurface->events.set_class, &xwayland_view->set_class);
383 xwayland_view->set_class.notify = handle_set_class;
384
385 wl_signal_add(&xsurface->events.set_window_type,
386 &xwayland_view->set_window_type);
387 xwayland_view->set_window_type.notify = handle_set_window_type;
388
326 wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap); 389 wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap);
327 xwayland_view->unmap.notify = handle_unmap; 390 xwayland_view->unmap.notify = handle_unmap;
328 391
diff --git a/sway/tree/view.c b/sway/tree/view.c
index afd7eade..7d921e0e 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -3,6 +3,7 @@
3#include <wayland-server.h> 3#include <wayland-server.h>
4#include <wlr/render/wlr_renderer.h> 4#include <wlr/render/wlr_renderer.h>
5#include <wlr/types/wlr_output_layout.h> 5#include <wlr/types/wlr_output_layout.h>
6#include "list.h"
6#include "log.h" 7#include "log.h"
7#include "sway/criteria.h" 8#include "sway/criteria.h"
8#include "sway/commands.h" 9#include "sway/commands.h"
@@ -19,6 +20,7 @@ void view_init(struct sway_view *view, enum sway_view_type type,
19 const struct sway_view_impl *impl) { 20 const struct sway_view_impl *impl) {
20 view->type = type; 21 view->type = type;
21 view->impl = impl; 22 view->impl = impl;
23 view->executed_criteria = create_list();
22 wl_signal_init(&view->events.unmap); 24 wl_signal_init(&view->events.unmap);
23} 25}
24 26
@@ -31,6 +33,8 @@ void view_destroy(struct sway_view *view) {
31 view_unmap(view); 33 view_unmap(view);
32 } 34 }
33 35
36 list_free(view->executed_criteria);
37
34 container_destroy(view->swayc); 38 container_destroy(view->swayc);
35 39
36 if (view->impl->destroy) { 40 if (view->impl->destroy) {
@@ -41,33 +45,47 @@ void view_destroy(struct sway_view *view) {
41} 45}
42 46
43const char *view_get_title(struct sway_view *view) { 47const char *view_get_title(struct sway_view *view) {
44 if (view->impl->get_prop) { 48 if (view->impl->get_string_prop) {
45 return view->impl->get_prop(view, VIEW_PROP_TITLE); 49 return view->impl->get_string_prop(view, VIEW_PROP_TITLE);
46 } 50 }
47 return NULL; 51 return NULL;
48} 52}
49 53
50const char *view_get_app_id(struct sway_view *view) { 54const char *view_get_app_id(struct sway_view *view) {
51 if (view->impl->get_prop) { 55 if (view->impl->get_string_prop) {
52 return view->impl->get_prop(view, VIEW_PROP_APP_ID); 56 return view->impl->get_string_prop(view, VIEW_PROP_APP_ID);
53 } 57 }
54 return NULL; 58 return NULL;
55} 59}
56 60
57const char *view_get_class(struct sway_view *view) { 61const char *view_get_class(struct sway_view *view) {
58 if (view->impl->get_prop) { 62 if (view->impl->get_string_prop) {
59 return view->impl->get_prop(view, VIEW_PROP_CLASS); 63 return view->impl->get_string_prop(view, VIEW_PROP_CLASS);
60 } 64 }
61 return NULL; 65 return NULL;
62} 66}
63 67
64const char *view_get_instance(struct sway_view *view) { 68const char *view_get_instance(struct sway_view *view) {
65 if (view->impl->get_prop) { 69 if (view->impl->get_string_prop) {
66 return view->impl->get_prop(view, VIEW_PROP_INSTANCE); 70 return view->impl->get_string_prop(view, VIEW_PROP_INSTANCE);
67 } 71 }
68 return NULL; 72 return NULL;
69} 73}
70 74
75uint32_t view_get_x11_window_id(struct sway_view *view) {
76 if (view->impl->get_int_prop) {
77 return view->impl->get_int_prop(view, VIEW_PROP_X11_WINDOW_ID);
78 }
79 return 0;
80}
81
82uint32_t view_get_window_type(struct sway_view *view) {
83 if (view->impl->get_int_prop) {
84 return view->impl->get_int_prop(view, VIEW_PROP_WINDOW_TYPE);
85 }
86 return 0;
87}
88
71const char *view_get_type(struct sway_view *view) { 89const char *view_get_type(struct sway_view *view) {
72 switch(view->type) { 90 switch(view->type) {
73 case SWAY_VIEW_WL_SHELL: 91 case SWAY_VIEW_WL_SHELL:
@@ -282,19 +300,36 @@ static void view_handle_container_reparent(struct wl_listener *listener,
282 } 300 }
283} 301}
284 302
285static void view_execute_criteria(struct sway_view *view) { 303static bool view_has_executed_criteria(struct sway_view *view,
286 if (!sway_assert(view->swayc, "cannot run criteria for unmapped view")) { 304 struct criteria *criteria) {
305 for (int i = 0; i < view->executed_criteria->length; ++i) {
306 struct criteria *item = view->executed_criteria->items[i];
307 if (item == criteria) {
308 return true;
309 }
310 }
311 return false;
312}
313
314void view_execute_criteria(struct sway_view *view) {
315 if (!view->swayc) {
287 return; 316 return;
288 } 317 }
289 struct sway_seat *seat = input_manager_current_seat(input_manager); 318 struct sway_seat *seat = input_manager_current_seat(input_manager);
290 struct sway_container *prior_workspace = 319 struct sway_container *prior_workspace =
291 container_parent(view->swayc, C_WORKSPACE); 320 container_parent(view->swayc, C_WORKSPACE);
292 list_t *criteria = criteria_for(view->swayc); 321 list_t *criterias = criteria_for_view(view, CT_COMMAND);
293 for (int i = 0; i < criteria->length; i++) { 322 for (int i = 0; i < criterias->length; i++) {
294 struct criteria *crit = criteria->items[i]; 323 struct criteria *criteria = criterias->items[i];
295 wlr_log(L_DEBUG, "for_window '%s' matches new view %p, cmd: '%s'", 324 wlr_log(L_DEBUG, "Checking criteria %s", criteria->raw);
296 crit->crit_raw, view, crit->cmdlist); 325 if (view_has_executed_criteria(view, criteria)) {
297 struct cmd_results *res = execute_command(crit->cmdlist, NULL); 326 wlr_log(L_DEBUG, "Criteria already executed");
327 continue;
328 }
329 wlr_log(L_DEBUG, "for_window '%s' matches view %p, cmd: '%s'",
330 criteria->raw, view, criteria->cmdlist);
331 list_add(view->executed_criteria, criteria);
332 struct cmd_results *res = execute_command(criteria->cmdlist, NULL);
298 if (res->status != CMD_SUCCESS) { 333 if (res->status != CMD_SUCCESS) {
299 wlr_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error); 334 wlr_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error);
300 } 335 }
@@ -303,7 +338,7 @@ static void view_execute_criteria(struct sway_view *view) {
303 // so always refocus in-between command lists 338 // so always refocus in-between command lists
304 seat_set_focus(seat, view->swayc); 339 seat_set_focus(seat, view->swayc);
305 } 340 }
306 list_free(criteria); 341 list_free(criterias);
307 seat_set_focus(seat, seat_get_focus_inactive(seat, prior_workspace)); 342 seat_set_focus(seat, seat_get_focus_inactive(seat, prior_workspace));
308} 343}
309 344
@@ -313,9 +348,26 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
313 } 348 }
314 349
315 struct sway_seat *seat = input_manager_current_seat(input_manager); 350 struct sway_seat *seat = input_manager_current_seat(input_manager);
316 struct sway_container *focus = seat_get_focus_inactive(seat, 351 struct sway_container *focus = seat_get_focus(seat);
317 &root_container); 352 struct sway_container *cont = NULL;
318 struct sway_container *cont = container_view_create(focus, view); 353
354 // Check if there's any `assign` criteria for the view
355 list_t *criterias = criteria_for_view(view,
356 CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT);
357 if (criterias->length) {
358 struct criteria *criteria = criterias->items[0];
359 if (criteria->type == CT_ASSIGN_WORKSPACE) {
360 struct sway_container *workspace = workspace_by_name(criteria->target);
361 if (!workspace) {
362 workspace = workspace_create(NULL, criteria->target);
363 }
364 focus = seat_get_focus_inactive(seat, workspace);
365 } else {
366 // TODO: CT_ASSIGN_OUTPUT
367 }
368 }
369 free(criterias);
370 cont = container_view_create(focus, view);
319 371
320 view->surface = wlr_surface; 372 view->surface = wlr_surface;
321 view->swayc = cont; 373 view->swayc = cont;
@@ -333,10 +385,11 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) {
333 arrange_children_of(cont->parent); 385 arrange_children_of(cont->parent);
334 input_manager_set_focus(input_manager, cont); 386 input_manager_set_focus(input_manager, cont);
335 387
388 view_update_title(view, false);
389 view_execute_criteria(view);
390
336 container_damage_whole(cont); 391 container_damage_whole(cont);
337 view_handle_container_reparent(&view->container_reparent, NULL); 392 view_handle_container_reparent(&view->container_reparent, NULL);
338
339 view_execute_criteria(view);
340} 393}
341 394
342void view_unmap(struct sway_view *view) { 395void view_unmap(struct sway_view *view) {