diff options
-rw-r--r-- | include/sway/criteria.h | 73 | ||||
-rw-r--r-- | include/sway/server.h | 1 | ||||
-rw-r--r-- | include/sway/tree/view.h | 38 | ||||
-rw-r--r-- | sway/commands.c | 42 | ||||
-rw-r--r-- | sway/commands/assign.c | 58 | ||||
-rw-r--r-- | sway/commands/for_window.c | 35 | ||||
-rw-r--r-- | sway/commands/fullscreen.c | 1 | ||||
-rw-r--r-- | sway/criteria.c | 794 | ||||
-rw-r--r-- | sway/desktop/output.c | 1 | ||||
-rw-r--r-- | sway/desktop/wl_shell.c | 165 | ||||
-rw-r--r-- | sway/desktop/xdg_shell.c | 4 | ||||
-rw-r--r-- | sway/desktop/xdg_shell_v6.c | 4 | ||||
-rw-r--r-- | sway/desktop/xwayland.c | 69 | ||||
-rw-r--r-- | sway/meson.build | 1 | ||||
-rw-r--r-- | sway/server.c | 10 | ||||
-rw-r--r-- | sway/tree/container.c | 6 | ||||
-rw-r--r-- | sway/tree/view.c | 107 |
17 files changed, 707 insertions, 702 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 | /** | 8 | enum 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 | */ | ||
18 | struct criteria { | ||
19 | list_t *tokens; // struct crit_token, contains compiled regex. | ||
20 | char *crit_raw; // entire criteria string (for logging) | ||
21 | 13 | ||
14 | struct 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 | ||
25 | int criteria_cmp(const void *item, const void *data); | 35 | bool criteria_is_empty(struct criteria *criteria); |
26 | void free_criteria(struct criteria *crit); | ||
27 | 36 | ||
28 | // Pouplate list with crit_tokens extracted from criteria string, returns error | 37 | void criteria_destroy(struct criteria *criteria); |
29 | // string or NULL if successful. | ||
30 | char *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 |
34 | list_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 | */ | ||
47 | struct criteria *criteria_parse(char *raw, char **error); | ||
35 | 48 | ||
36 | // Returns a list of all containers that match the given list of tokens. | 49 | /** |
37 | list_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 | */ | ||
54 | list_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 | /** |
40 | bool criteria_any(struct sway_container *cont, list_t *criteria); | 57 | * Compile a list of views matching the given criteria. |
58 | */ | ||
59 | list_t *criteria_get_views(struct criteria *criteria); | ||
41 | 60 | ||
42 | #endif | 61 | #endif |
diff --git a/include/sway/server.h b/include/sway/server.h index d04ea896..c95ee0f3 100644 --- a/include/sway/server.h +++ b/include/sway/server.h | |||
@@ -58,6 +58,5 @@ void handle_layer_shell_surface(struct wl_listener *listener, void *data); | |||
58 | void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data); | 58 | void handle_xdg_shell_v6_surface(struct wl_listener *listener, void *data); |
59 | void handle_xdg_shell_surface(struct wl_listener *listener, void *data); | 59 | void handle_xdg_shell_surface(struct wl_listener *listener, void *data); |
60 | void handle_xwayland_surface(struct wl_listener *listener, void *data); | 60 | void handle_xwayland_surface(struct wl_listener *listener, void *data); |
61 | void handle_wl_shell_surface(struct wl_listener *listener, void *data); | ||
62 | 61 | ||
63 | #endif | 62 | #endif |
diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index e163e3fe..f12386dc 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h | |||
@@ -10,7 +10,6 @@ | |||
10 | struct sway_container; | 10 | struct sway_container; |
11 | 11 | ||
12 | enum sway_view_type { | 12 | enum sway_view_type { |
13 | SWAY_VIEW_WL_SHELL, | ||
14 | SWAY_VIEW_XDG_SHELL_V6, | 13 | SWAY_VIEW_XDG_SHELL_V6, |
15 | SWAY_VIEW_XDG_SHELL, | 14 | SWAY_VIEW_XDG_SHELL, |
16 | SWAY_VIEW_XWAYLAND, | 15 | SWAY_VIEW_XWAYLAND, |
@@ -21,11 +20,15 @@ enum sway_view_prop { | |||
21 | VIEW_PROP_APP_ID, | 20 | VIEW_PROP_APP_ID, |
22 | VIEW_PROP_CLASS, | 21 | VIEW_PROP_CLASS, |
23 | VIEW_PROP_INSTANCE, | 22 | VIEW_PROP_INSTANCE, |
23 | VIEW_PROP_WINDOW_TYPE, | ||
24 | VIEW_PROP_WINDOW_ROLE, | ||
25 | VIEW_PROP_X11_WINDOW_ID, | ||
24 | }; | 26 | }; |
25 | 27 | ||
26 | struct sway_view_impl { | 28 | struct sway_view_impl { |
27 | const char *(*get_prop)(struct sway_view *view, | 29 | const char *(*get_string_prop)(struct sway_view *view, |
28 | 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); | ||
29 | 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, |
30 | int height); | 33 | int height); |
31 | void (*set_activated)(struct sway_view *view, bool activated); | 34 | void (*set_activated)(struct sway_view *view, bool activated); |
@@ -57,6 +60,8 @@ struct sway_view { | |||
57 | bool border_left; | 60 | bool border_left; |
58 | bool border_right; | 61 | bool border_right; |
59 | 62 | ||
63 | list_t *executed_criteria; | ||
64 | |||
60 | union { | 65 | union { |
61 | struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6; | 66 | struct wlr_xdg_surface_v6 *wlr_xdg_surface_v6; |
62 | struct wlr_xdg_surface *wlr_xdg_surface; | 67 | struct wlr_xdg_surface *wlr_xdg_surface; |
@@ -113,6 +118,9 @@ struct sway_xwayland_view { | |||
113 | struct wl_listener request_maximize; | 118 | struct wl_listener request_maximize; |
114 | struct wl_listener request_configure; | 119 | struct wl_listener request_configure; |
115 | struct wl_listener request_fullscreen; | 120 | struct wl_listener request_fullscreen; |
121 | struct wl_listener set_title; | ||
122 | struct wl_listener set_class; | ||
123 | struct wl_listener set_window_type; | ||
116 | struct wl_listener map; | 124 | struct wl_listener map; |
117 | struct wl_listener unmap; | 125 | struct wl_listener unmap; |
118 | struct wl_listener destroy; | 126 | struct wl_listener destroy; |
@@ -134,20 +142,6 @@ struct sway_xwayland_unmanaged { | |||
134 | struct wl_listener destroy; | 142 | struct wl_listener destroy; |
135 | }; | 143 | }; |
136 | 144 | ||
137 | struct sway_wl_shell_view { | ||
138 | struct sway_view view; | ||
139 | |||
140 | struct wl_listener commit; | ||
141 | struct wl_listener request_move; | ||
142 | struct wl_listener request_resize; | ||
143 | struct wl_listener request_maximize; | ||
144 | struct wl_listener request_fullscreen; | ||
145 | struct wl_listener set_state; | ||
146 | struct wl_listener destroy; | ||
147 | |||
148 | int pending_width, pending_height; | ||
149 | }; | ||
150 | |||
151 | struct sway_view_child; | 145 | struct sway_view_child; |
152 | 146 | ||
153 | struct sway_view_child_impl { | 147 | struct sway_view_child_impl { |
@@ -195,6 +189,12 @@ const char *view_get_class(struct sway_view *view); | |||
195 | 189 | ||
196 | const char *view_get_instance(struct sway_view *view); | 190 | const char *view_get_instance(struct sway_view *view); |
197 | 191 | ||
192 | uint32_t view_get_x11_window_id(struct sway_view *view); | ||
193 | |||
194 | const char *view_get_window_role(struct sway_view *view); | ||
195 | |||
196 | uint32_t view_get_window_type(struct sway_view *view); | ||
197 | |||
198 | const char *view_get_type(struct sway_view *view); | 198 | const char *view_get_type(struct sway_view *view); |
199 | 199 | ||
200 | void view_configure(struct sway_view *view, double ox, double oy, int width, | 200 | void view_configure(struct sway_view *view, double ox, double oy, int width, |
@@ -247,4 +247,10 @@ void view_child_destroy(struct sway_view_child *child); | |||
247 | */ | 247 | */ |
248 | void view_update_title(struct sway_view *view, bool force); | 248 | void view_update_title(struct sway_view *view, bool force); |
249 | 249 | ||
250 | /** | ||
251 | * Run any criteria that match the view and haven't been run on this view | ||
252 | * before. | ||
253 | */ | ||
254 | void view_execute_criteria(struct sway_view *view); | ||
255 | |||
250 | #endif | 256 | #endif |
diff --git a/sway/commands.c b/sway/commands.c index 37ead367..60c64776 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 | ||
@@ -284,7 +285,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { | |||
284 | char *head = exec; | 285 | char *head = exec; |
285 | char *cmdlist; | 286 | char *cmdlist; |
286 | char *cmd; | 287 | char *cmd; |
287 | list_t *containers = NULL; | 288 | list_t *views = NULL; |
288 | 289 | ||
289 | if (seat == NULL) { | 290 | if (seat == NULL) { |
290 | // passing a NULL seat means we just pick the default seat | 291 | // passing a NULL seat means we just pick the default seat |
@@ -301,31 +302,18 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { | |||
301 | // Extract criteria (valid for this command list only). | 302 | // Extract criteria (valid for this command list only). |
302 | bool has_criteria = false; | 303 | bool has_criteria = false; |
303 | if (*head == '[') { | 304 | if (*head == '[') { |
304 | has_criteria = true; | 305 | char *error = NULL; |
305 | ++head; | 306 | struct criteria *criteria = criteria_parse(head, &error); |
306 | char *criteria_string = argsep(&head, "]"); | 307 | if (!criteria) { |
307 | if (head) { | 308 | results = cmd_results_new(CMD_INVALID, head, |
308 | ++head; | 309 | "%s", error); |
309 | list_t *tokens = create_list(); | 310 | free(error); |
310 | char *error; | ||
311 | |||
312 | if ((error = extract_crit_tokens(tokens, criteria_string))) { | ||
313 | wlr_log(L_DEBUG, "criteria string parse error: %s", error); | ||
314 | results = cmd_results_new(CMD_INVALID, criteria_string, | ||
315 | "Can't parse criteria string: %s", error); | ||
316 | free(error); | ||
317 | free(tokens); | ||
318 | goto cleanup; | ||
319 | } | ||
320 | containers = container_for_crit_tokens(tokens); | ||
321 | |||
322 | free(tokens); | ||
323 | } else { | ||
324 | if (!results) { | ||
325 | results = cmd_results_new(CMD_INVALID, criteria_string, "Unmatched ["); | ||
326 | } | ||
327 | goto cleanup; | 311 | goto cleanup; |
328 | } | 312 | } |
313 | views = criteria_get_views(criteria); | ||
314 | head += strlen(criteria->raw); | ||
315 | criteria_destroy(criteria); | ||
316 | has_criteria = true; | ||
329 | // Skip leading whitespace | 317 | // Skip leading whitespace |
330 | head += strspn(head, whitespace); | 318 | head += strspn(head, whitespace); |
331 | } | 319 | } |
@@ -382,8 +370,9 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { | |||
382 | } | 370 | } |
383 | free_cmd_results(res); | 371 | free_cmd_results(res); |
384 | } else { | 372 | } else { |
385 | for (int i = 0; i < containers->length; ++i) { | 373 | for (int i = 0; i < views->length; ++i) { |
386 | config->handler_context.current_container = containers->items[i]; | 374 | struct sway_view *view = views->items[i]; |
375 | config->handler_context.current_container = view->swayc; | ||
387 | struct cmd_results *res = handler->handle(argc-1, argv+1); | 376 | struct cmd_results *res = handler->handle(argc-1, argv+1); |
388 | if (res->status != CMD_SUCCESS) { | 377 | if (res->status != CMD_SUCCESS) { |
389 | free_argv(argc, argv); | 378 | free_argv(argc, argv); |
@@ -401,6 +390,7 @@ struct cmd_results *execute_command(char *_exec, struct sway_seat *seat) { | |||
401 | } while(head); | 390 | } while(head); |
402 | cleanup: | 391 | cleanup: |
403 | free(exec); | 392 | free(exec); |
393 | free(views); | ||
404 | if (!results) { | 394 | if (!results) { |
405 | results = cmd_results_new(CMD_SUCCESS, NULL, NULL); | 395 | results = cmd_results_new(CMD_SUCCESS, NULL, NULL); |
406 | } | 396 | } |
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 | ||
9 | struct cmd_results *cmd_assign(int argc, char **argv) { | 10 | struct 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/commands/fullscreen.c b/sway/commands/fullscreen.c index 8692e92d..ec9ec276 100644 --- a/sway/commands/fullscreen.c +++ b/sway/commands/fullscreen.c | |||
@@ -1,4 +1,3 @@ | |||
1 | #include <wlr/types/wlr_wl_shell.h> | ||
2 | #include "log.h" | 1 | #include "log.h" |
3 | #include "sway/commands.h" | 2 | #include "sway/commands.h" |
4 | #include "sway/config.h" | 3 | #include "sway/config.h" |
diff --git a/sway/criteria.c b/sway/criteria.c index 22e9a49b..248260ec 100644 --- a/sway/criteria.c +++ b/sway/criteria.c | |||
@@ -11,435 +11,501 @@ | |||
11 | #include "list.h" | 11 | #include "list.h" |
12 | #include "log.h" | 12 | #include "log.h" |
13 | 13 | ||
14 | enum criteria_type { // *must* keep in sync with criteria_strings[] | 14 | bool 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 | |||
31 | static 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 | /** | 30 | void 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); |
51 | struct 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 | ||
57 | static void free_crit_token(struct crit_token *crit) { | 43 | static 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 | ||
63 | static void free_crit_tokens(list_t *crit_tokens) { | 47 | static 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); |
72 | static 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) { |
88 | static 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; |
106 | static 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. | 133 | list_t *criteria_for_view(struct sway_view *view, enum criteria_type types) { |
188 | static 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); | 145 | struct 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) | 150 | static 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 | ||
160 | list_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. | ||
173 | char *error = NULL; | ||
174 | |||
215 | // Returns error string on failure or NULL otherwise. | 175 | // Returns error string on failure or NULL otherwise. |
216 | static char *generate_regex(pcre **regex, char *value) { | 176 | static 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, ®_err, &offset, NULL); | 180 | *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_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; |
233 | static 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 | 193 | enum criteria_token { |
238 | // string or NULL if successful. | 194 | T_APP_ID, |
239 | char *extract_crit_tokens(list_t *tokens, const char * const criteria) { | 195 | T_CLASS, |
240 | int argc; | 196 | T_CON_ID, |
241 | char **argv = NULL, *error = NULL; | 197 | T_CON_MARK, |
242 | if ((error = crit_tokens(&argc, &argv, criteria))) { | 198 | T_FLOATING, |
243 | goto ect_cleanup; | 199 | T_ID, |
244 | } | 200 | T_INSTANCE, |
245 | for (int i = 1; i + 1 < argc; i += 2) { | 201 | T_TILING, |
246 | char* name = argv[i], *value = argv[i + 1]; | 202 | T_TITLE, |
247 | struct crit_token *token = calloc(1, sizeof(struct crit_token)); | 203 | T_URGENT, |
248 | token->raw = strdup(value); | 204 | T_WINDOW_ROLE, |
249 | 205 | T_WINDOW_TYPE, | |
250 | if ((error = parse_criteria_name(&token->type, name))) { | 206 | T_WORKSPACE, |
251 | free_crit_token(token); | 207 | |
252 | goto ect_cleanup; | 208 | T_INVALID, |
253 | } else if (token->type == CRIT_URGENT || crit_is_focused(value)) { | 209 | }; |
254 | wlr_log(L_DEBUG, "%s -> \"%s\"", name, value); | 210 | |
255 | list_add(tokens, token); | 211 | static enum criteria_token token_from_name(char *name) { |
256 | } else if((error = generate_regex(&token->regex, value))) { | 212 | if (strcmp(name, "app_id") == 0) { |
257 | free_crit_token(token); | 213 | return T_APP_ID; |
258 | goto ect_cleanup; | 214 | } else if (strcmp(name, "class") == 0) { |
259 | } else { | 215 | return T_CLASS; |
260 | wlr_log(L_DEBUG, "%s -> /%s/", name, value); | 216 | } else if (strcmp(name, "con_id") == 0) { |
261 | list_add(tokens, token); | 217 | return T_CON_ID; |
262 | } | 218 | } else if (strcmp(name, "con_mark") == 0) { |
219 | return T_CON_MARK; | ||
220 | } else if (strcmp(name, "id") == 0) { | ||
221 | return T_ID; | ||
222 | } else if (strcmp(name, "instance") == 0) { | ||
223 | return T_INSTANCE; | ||
224 | } else if (strcmp(name, "title") == 0) { | ||
225 | return T_TITLE; | ||
226 | } else if (strcmp(name, "urgent") == 0) { | ||
227 | return T_URGENT; | ||
228 | } else if (strcmp(name, "window_role") == 0) { | ||
229 | return T_WINDOW_ROLE; | ||
230 | } else if (strcmp(name, "window_type") == 0) { | ||
231 | return T_WINDOW_TYPE; | ||
232 | } else if (strcmp(name, "workspace") == 0) { | ||
233 | return T_WORKSPACE; | ||
263 | } | 234 | } |
264 | ect_cleanup: | 235 | return T_INVALID; |
265 | free(argv[0]); // base string | ||
266 | free(argv); | ||
267 | return error; | ||
268 | } | 236 | } |
269 | 237 | ||
270 | static int regex_cmp(const char *item, const pcre *regex) { | 238 | /** |
271 | return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); | 239 | * Get a property of the focused view. |
272 | } | 240 | * |
241 | * Note that we are taking the focused view at the time of criteria parsing, not | ||
242 | * at the time of execution. This is because __focused__ only makes sense when | ||
243 | * using criteria via IPC. Using __focused__ in config is not useful because | ||
244 | * criteria is only executed once per view. | ||
245 | */ | ||
246 | static char *get_focused_prop(enum criteria_token token) { | ||
247 | struct sway_seat *seat = input_manager_current_seat(input_manager); | ||
248 | struct sway_container *focus = seat_get_focus(seat); | ||
273 | 249 | ||
274 | // test a single view if it matches list of criteria tokens (all of them). | 250 | if (!focus || focus->type != C_VIEW) { |
275 | static bool criteria_test(struct sway_container *cont, list_t *tokens) { | 251 | return NULL; |
276 | if (cont->type != C_CONTAINER && cont->type != C_VIEW) { | ||
277 | return false; | ||
278 | } | 252 | } |
279 | int matches = 0; | 253 | struct sway_view *view = focus->sway_view; |
280 | for (int i = 0; i < tokens->length; i++) { | 254 | const char *value = NULL; |
281 | struct crit_token *crit = tokens->items[i]; | 255 | |
282 | switch (crit->type) { | 256 | switch (token) { |
283 | case CRIT_CLASS: | 257 | case T_APP_ID: |
284 | { | 258 | value = view_get_app_id(view); |
285 | const char *class = view_get_class(cont->sway_view); | 259 | break; |
286 | if (!class) { | 260 | case T_CLASS: |
287 | break; | 261 | value = view_get_class(view); |
288 | } | 262 | break; |
289 | if (crit->regex && regex_cmp(class, crit->regex) == 0) { | 263 | case T_INSTANCE: |
290 | matches++; | 264 | value = view_get_instance(view); |
291 | } | 265 | break; |
292 | break; | 266 | case T_TITLE: |
293 | } | 267 | value = view_get_class(view); |
294 | case CRIT_CON_ID: | 268 | break; |
295 | { | 269 | case T_WINDOW_ROLE: |
296 | char *endptr; | 270 | value = view_get_class(view); |
297 | size_t crit_id = strtoul(crit->raw, &endptr, 10); | 271 | break; |
298 | 272 | case T_WORKSPACE: | |
299 | if (*endptr == 0 && cont->id == crit_id) { | 273 | { |
300 | ++matches; | 274 | struct sway_container *ws = container_parent(focus, C_WORKSPACE); |
301 | } | 275 | if (ws) { |
302 | break; | 276 | value = ws->name; |
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 | } | 277 | } |
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 | |||
347 | if (crit->regex && regex_cmp(title, crit->regex) == 0) { | ||
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 | } | 278 | } |
279 | break; | ||
280 | case T_CON_ID: // These do not support __focused__ | ||
281 | case T_CON_MARK: | ||
282 | case T_FLOATING: | ||
283 | case T_ID: | ||
284 | case T_TILING: | ||
285 | case T_URGENT: | ||
286 | case T_WINDOW_TYPE: | ||
287 | case T_INVALID: | ||
288 | break; | ||
289 | } | ||
290 | if (value) { | ||
291 | return strdup(value); | ||
368 | } | 292 | } |
369 | return matches == tokens->length; | 293 | return NULL; |
370 | } | 294 | } |
371 | 295 | ||
372 | int criteria_cmp(const void *a, const void *b) { | 296 | static bool parse_token(struct criteria *criteria, char *name, char *value) { |
373 | if (a == b) { | 297 | enum criteria_token token = token_from_name(name); |
374 | return 0; | 298 | if (token == T_INVALID) { |
375 | } else if (!a) { | 299 | const char *fmt = "Token '%s' is not recognized"; |
376 | return -1; | 300 | int len = strlen(fmt) + strlen(name) - 1; |
377 | } else if (!b) { | 301 | error = malloc(len); |
378 | return 1; | 302 | snprintf(error, len, fmt, name); |
303 | return false; | ||
379 | } | 304 | } |
380 | const struct criteria *crit_a = a, *crit_b = b; | 305 | |
381 | int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist); | 306 | char *effective_value = NULL; |
382 | if (cmp != 0) { | 307 | if (value && strcmp(value, "__focused__") == 0) { |
383 | return cmp; | 308 | effective_value = get_focused_prop(token); |
309 | } else if (value) { | ||
310 | effective_value = strdup(value); | ||
384 | } | 311 | } |
385 | return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw); | ||
386 | } | ||
387 | 312 | ||
388 | void free_criteria(struct criteria *crit) { | 313 | // Require value, unless token is floating or tiled |
389 | if (crit->tokens) { | 314 | if (!effective_value && token != T_FLOATING && token != T_TILING) { |
390 | free_crit_tokens(crit->tokens); | 315 | const char *fmt = "Token '%s' requires a value"; |
316 | int len = strlen(fmt) + strlen(name) - 1; | ||
317 | error = malloc(len); | ||
318 | snprintf(error, len, fmt, name); | ||
319 | return false; | ||
391 | } | 320 | } |
392 | if (crit->cmdlist) { | 321 | |
393 | free(crit->cmdlist); | 322 | char *endptr = NULL; |
323 | switch (token) { | ||
324 | case T_TITLE: | ||
325 | generate_regex(&criteria->title, effective_value); | ||
326 | break; | ||
327 | case T_APP_ID: | ||
328 | generate_regex(&criteria->app_id, effective_value); | ||
329 | break; | ||
330 | case T_CLASS: | ||
331 | generate_regex(&criteria->class, effective_value); | ||
332 | break; | ||
333 | case T_INSTANCE: | ||
334 | generate_regex(&criteria->instance, effective_value); | ||
335 | break; | ||
336 | case T_CON_ID: | ||
337 | criteria->con_id = strtoul(effective_value, &endptr, 10); | ||
338 | if (*endptr != 0) { | ||
339 | error = strdup("The value for 'con_id' should be numeric"); | ||
340 | } | ||
341 | break; | ||
342 | case T_CON_MARK: | ||
343 | generate_regex(&criteria->con_mark, effective_value); | ||
344 | break; | ||
345 | case T_WINDOW_ROLE: | ||
346 | generate_regex(&criteria->window_role, effective_value); | ||
347 | break; | ||
348 | case T_WINDOW_TYPE: | ||
349 | // TODO: This is a string but will be stored as an enum or integer | ||
350 | break; | ||
351 | case T_ID: | ||
352 | criteria->id = strtoul(effective_value, &endptr, 10); | ||
353 | if (*endptr != 0) { | ||
354 | error = strdup("The value for 'id' should be numeric"); | ||
355 | } | ||
356 | break; | ||
357 | case T_FLOATING: | ||
358 | criteria->floating = true; | ||
359 | break; | ||
360 | case T_TILING: | ||
361 | criteria->tiling = true; | ||
362 | break; | ||
363 | case T_URGENT: | ||
364 | if (strcmp(effective_value, "latest") == 0) { | ||
365 | criteria->urgent = 'l'; | ||
366 | } else if (strcmp(effective_value, "oldest") == 0) { | ||
367 | criteria->urgent = 'o'; | ||
368 | } else { | ||
369 | error = | ||
370 | strdup("The value for 'urgent' must be 'latest' or 'oldest'"); | ||
371 | } | ||
372 | break; | ||
373 | case T_WORKSPACE: | ||
374 | criteria->workspace = strdup(effective_value); | ||
375 | break; | ||
376 | case T_INVALID: | ||
377 | break; | ||
394 | } | 378 | } |
395 | if (crit->crit_raw) { | 379 | free(effective_value); |
396 | free(crit->crit_raw); | 380 | |
381 | if (error) { | ||
382 | return false; | ||
397 | } | 383 | } |
398 | free(crit); | 384 | |
385 | return true; | ||
399 | } | 386 | } |
400 | 387 | ||
401 | bool criteria_any(struct sway_container *cont, list_t *criteria) { | 388 | static void skip_spaces(char **head) { |
402 | for (int i = 0; i < criteria->length; i++) { | 389 | while (**head == ' ') { |
403 | struct criteria *bc = criteria->items[i]; | 390 | ++*head; |
404 | if (criteria_test(cont, bc->tokens)) { | ||
405 | return true; | ||
406 | } | ||
407 | } | 391 | } |
408 | return false; | ||
409 | } | 392 | } |
410 | 393 | ||
411 | list_t *criteria_for(struct sway_container *cont) { | 394 | // Remove escaping slashes from value |
412 | list_t *criteria = config->criteria, *matches = create_list(); | 395 | static void unescape(char *value) { |
413 | for (int i = 0; i < criteria->length; i++) { | 396 | if (!strchr(value, '\\')) { |
414 | struct criteria *bc = criteria->items[i]; | 397 | return; |
415 | if (criteria_test(cont, bc->tokens)) { | 398 | } |
416 | list_add(matches, bc); | 399 | char *copy = calloc(strlen(value) + 1, 1); |
400 | char *readhead = value; | ||
401 | char *writehead = copy; | ||
402 | while (*readhead) { | ||
403 | if (*readhead == '\\' && *(readhead + 1) == '"') { | ||
404 | // skip the slash | ||
405 | ++readhead; | ||
417 | } | 406 | } |
407 | *writehead = *readhead; | ||
408 | ++writehead; | ||
409 | ++readhead; | ||
418 | } | 410 | } |
419 | return matches; | 411 | strcpy(value, copy); |
412 | free(copy); | ||
420 | } | 413 | } |
421 | 414 | ||
422 | struct list_tokens { | 415 | /** |
423 | list_t *list; | 416 | * Parse a raw criteria string such as [class="foo" instance="bar"] into a |
424 | list_t *tokens; | 417 | * criteria struct. |
425 | }; | 418 | * |
426 | 419 | * If errors are found, NULL will be returned and the error argument will be | |
427 | static void container_match_add(struct sway_container *container, | 420 | * populated with an error string. It is up to the caller to free the error. |
428 | struct list_tokens *list_tokens) { | 421 | */ |
429 | if (criteria_test(container, list_tokens->tokens)) { | 422 | struct criteria *criteria_parse(char *raw, char **error_arg) { |
430 | list_add(list_tokens->list, container); | 423 | *error_arg = NULL; |
424 | error = NULL; | ||
425 | |||
426 | char *head = raw; | ||
427 | skip_spaces(&head); | ||
428 | if (*head != '[') { | ||
429 | *error_arg = strdup("No criteria"); | ||
430 | return NULL; | ||
431 | } | ||
432 | ++head; | ||
433 | |||
434 | struct criteria *criteria = calloc(sizeof(struct criteria), 1); | ||
435 | char *name = NULL, *value = NULL; | ||
436 | bool in_quotes = false; | ||
437 | |||
438 | while (*head && *head != ']') { | ||
439 | skip_spaces(&head); | ||
440 | // Parse token name | ||
441 | char *namestart = head; | ||
442 | while ((*head >= 'a' && *head <= 'z') || *head == '_') { | ||
443 | ++head; | ||
444 | } | ||
445 | name = calloc(head - namestart + 1, 1); | ||
446 | strncpy(name, namestart, head - namestart); | ||
447 | // Parse token value | ||
448 | skip_spaces(&head); | ||
449 | value = NULL; | ||
450 | if (*head == '=') { | ||
451 | ++head; | ||
452 | skip_spaces(&head); | ||
453 | if (*head == '"') { | ||
454 | in_quotes = true; | ||
455 | ++head; | ||
456 | } | ||
457 | char *valuestart = head; | ||
458 | if (in_quotes) { | ||
459 | while (*head && (*head != '"' || *(head - 1) == '\\')) { | ||
460 | ++head; | ||
461 | } | ||
462 | if (!*head) { | ||
463 | *error_arg = strdup("Quote mismatch in criteria"); | ||
464 | goto cleanup; | ||
465 | } | ||
466 | } else { | ||
467 | while (*head && *head != ' ' && *head != ']') { | ||
468 | ++head; | ||
469 | } | ||
470 | } | ||
471 | value = calloc(head - valuestart + 1, 1); | ||
472 | strncpy(value, valuestart, head - valuestart); | ||
473 | if (in_quotes) { | ||
474 | ++head; | ||
475 | in_quotes = false; | ||
476 | } | ||
477 | unescape(value); | ||
478 | } | ||
479 | wlr_log(L_DEBUG, "Found pair: %s=%s", name, value); | ||
480 | if (!parse_token(criteria, name, value)) { | ||
481 | *error_arg = error; | ||
482 | goto cleanup; | ||
483 | } | ||
484 | skip_spaces(&head); | ||
485 | free(name); | ||
486 | free(value); | ||
487 | name = NULL; | ||
488 | value = NULL; | ||
489 | } | ||
490 | if (*head != ']') { | ||
491 | *error_arg = strdup("No closing brace found in criteria"); | ||
492 | goto cleanup; | ||
431 | } | 493 | } |
432 | } | ||
433 | 494 | ||
434 | list_t *container_for_crit_tokens(list_t *tokens) { | 495 | if (criteria_is_empty(criteria)) { |
435 | struct list_tokens list_tokens = | 496 | *error_arg = strdup("Criteria is empty"); |
436 | (struct list_tokens){create_list(), tokens}; | 497 | goto cleanup; |
498 | } | ||
437 | 499 | ||
438 | container_for_each_descendant_dfs(&root_container, | 500 | ++head; |
439 | (void (*)(struct sway_container *, void *))container_match_add, | 501 | int len = head - raw; |
440 | &list_tokens); | 502 | criteria->raw = calloc(len + 1, 1); |
503 | strncpy(criteria->raw, raw, len); | ||
504 | return criteria; | ||
441 | 505 | ||
442 | // TODO look in the scratchpad | 506 | cleanup: |
443 | 507 | free(name); | |
444 | return list_tokens.list; | 508 | free(value); |
509 | criteria_destroy(criteria); | ||
510 | return NULL; | ||
445 | } | 511 | } |
diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 577279ac..0350e437 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c | |||
@@ -11,7 +11,6 @@ | |||
11 | #include <wlr/types/wlr_output_layout.h> | 11 | #include <wlr/types/wlr_output_layout.h> |
12 | #include <wlr/types/wlr_output.h> | 12 | #include <wlr/types/wlr_output.h> |
13 | #include <wlr/types/wlr_surface.h> | 13 | #include <wlr/types/wlr_surface.h> |
14 | #include <wlr/types/wlr_wl_shell.h> | ||
15 | #include <wlr/util/region.h> | 14 | #include <wlr/util/region.h> |
16 | #include "log.h" | 15 | #include "log.h" |
17 | #include "sway/config.h" | 16 | #include "sway/config.h" |
diff --git a/sway/desktop/wl_shell.c b/sway/desktop/wl_shell.c deleted file mode 100644 index 99e8947b..00000000 --- a/sway/desktop/wl_shell.c +++ /dev/null | |||
@@ -1,165 +0,0 @@ | |||
1 | #define _POSIX_C_SOURCE 199309L | ||
2 | #include <stdbool.h> | ||
3 | #include <stdlib.h> | ||
4 | #include <wayland-server.h> | ||
5 | #include <wlr/types/wlr_wl_shell.h> | ||
6 | #include "sway/tree/container.h" | ||
7 | #include "sway/tree/layout.h" | ||
8 | #include "sway/server.h" | ||
9 | #include "sway/tree/view.h" | ||
10 | #include "sway/input/seat.h" | ||
11 | #include "sway/input/input-manager.h" | ||
12 | #include "log.h" | ||
13 | |||
14 | static struct sway_wl_shell_view *wl_shell_view_from_view( | ||
15 | struct sway_view *view) { | ||
16 | if (!sway_assert(view->type == SWAY_VIEW_WL_SHELL, | ||
17 | "Expected wl_shell view")) { | ||
18 | return NULL; | ||
19 | } | ||
20 | return (struct sway_wl_shell_view *)view; | ||
21 | } | ||
22 | |||
23 | static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { | ||
24 | if (wl_shell_view_from_view(view) == NULL) { | ||
25 | return NULL; | ||
26 | } | ||
27 | switch (prop) { | ||
28 | case VIEW_PROP_TITLE: | ||
29 | return view->wlr_wl_shell_surface->title; | ||
30 | case VIEW_PROP_CLASS: | ||
31 | return view->wlr_wl_shell_surface->class; | ||
32 | default: | ||
33 | return NULL; | ||
34 | } | ||
35 | } | ||
36 | |||
37 | static void configure(struct sway_view *view, double ox, double oy, int width, | ||
38 | int height) { | ||
39 | struct sway_wl_shell_view *wl_shell_view = wl_shell_view_from_view(view); | ||
40 | if (wl_shell_view == NULL) { | ||
41 | return; | ||
42 | } | ||
43 | wl_shell_view->pending_width = width; | ||
44 | wl_shell_view->pending_height = height; | ||
45 | wlr_wl_shell_surface_configure(view->wlr_wl_shell_surface, 0, width, height); | ||
46 | } | ||
47 | |||
48 | static void _close(struct sway_view *view) { | ||
49 | if (wl_shell_view_from_view(view) == NULL) { | ||
50 | return; | ||
51 | } | ||
52 | |||
53 | wl_client_destroy(view->wlr_wl_shell_surface->client); | ||
54 | } | ||
55 | |||
56 | static void destroy(struct sway_view *view) { | ||
57 | struct sway_wl_shell_view *wl_shell_view = wl_shell_view_from_view(view); | ||
58 | if (wl_shell_view == NULL) { | ||
59 | return; | ||
60 | } | ||
61 | wl_list_remove(&wl_shell_view->commit.link); | ||
62 | wl_list_remove(&wl_shell_view->destroy.link); | ||
63 | wl_list_remove(&wl_shell_view->request_fullscreen.link); | ||
64 | wl_list_remove(&wl_shell_view->set_state.link); | ||
65 | free(wl_shell_view); | ||
66 | } | ||
67 | |||
68 | static void set_fullscreen(struct sway_view *view, bool fullscreen) { | ||
69 | // TODO | ||
70 | } | ||
71 | |||
72 | static const struct sway_view_impl view_impl = { | ||
73 | .get_prop = get_prop, | ||
74 | .configure = configure, | ||
75 | .close = _close, | ||
76 | .destroy = destroy, | ||
77 | .set_fullscreen = set_fullscreen, | ||
78 | }; | ||
79 | |||
80 | static void handle_commit(struct wl_listener *listener, void *data) { | ||
81 | struct sway_wl_shell_view *wl_shell_view = | ||
82 | wl_container_of(listener, wl_shell_view, commit); | ||
83 | struct sway_view *view = &wl_shell_view->view; | ||
84 | // NOTE: We intentionally discard the view's desired width here | ||
85 | // TODO: Let floating views do whatever | ||
86 | view_update_size(view, wl_shell_view->pending_width, | ||
87 | wl_shell_view->pending_height); | ||
88 | view_damage_from(view); | ||
89 | } | ||
90 | |||
91 | static void handle_destroy(struct wl_listener *listener, void *data) { | ||
92 | struct sway_wl_shell_view *wl_shell_view = | ||
93 | wl_container_of(listener, wl_shell_view, destroy); | ||
94 | view_destroy(&wl_shell_view->view); | ||
95 | } | ||
96 | |||
97 | static void handle_request_fullscreen(struct wl_listener *listener, void *data) { | ||
98 | struct sway_wl_shell_view *wl_shell_view = | ||
99 | wl_container_of(listener, wl_shell_view, request_fullscreen); | ||
100 | view_set_fullscreen(&wl_shell_view->view, true); | ||
101 | } | ||
102 | |||
103 | static void handle_set_state(struct wl_listener *listener, void *data) { | ||
104 | struct sway_wl_shell_view *wl_shell_view = | ||
105 | wl_container_of(listener, wl_shell_view, set_state); | ||
106 | struct sway_view *view = &wl_shell_view->view; | ||
107 | struct wlr_wl_shell_surface *surface = view->wlr_wl_shell_surface; | ||
108 | if (view->is_fullscreen && | ||
109 | surface->state != WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN) { | ||
110 | view_set_fullscreen(view, false); | ||
111 | } | ||
112 | } | ||
113 | |||
114 | void handle_wl_shell_surface(struct wl_listener *listener, void *data) { | ||
115 | struct sway_server *server = wl_container_of(listener, server, | ||
116 | wl_shell_surface); | ||
117 | struct wlr_wl_shell_surface *shell_surface = data; | ||
118 | |||
119 | if (shell_surface->state == WLR_WL_SHELL_SURFACE_STATE_POPUP) { | ||
120 | // popups don't get views | ||
121 | wlr_log(L_DEBUG, "New wl_shell popup"); | ||
122 | return; | ||
123 | } | ||
124 | |||
125 | // TODO: make transient windows floating | ||
126 | |||
127 | wlr_log(L_DEBUG, "New wl_shell toplevel title='%s' app_id='%s'", | ||
128 | shell_surface->title, shell_surface->class); | ||
129 | wlr_wl_shell_surface_ping(shell_surface); | ||
130 | |||
131 | struct sway_wl_shell_view *wl_shell_view = | ||
132 | calloc(1, sizeof(struct sway_wl_shell_view)); | ||
133 | if (!sway_assert(wl_shell_view, "Failed to allocate view")) { | ||
134 | return; | ||
135 | } | ||
136 | |||
137 | view_init(&wl_shell_view->view, SWAY_VIEW_WL_SHELL, &view_impl); | ||
138 | wl_shell_view->view.wlr_wl_shell_surface = shell_surface; | ||
139 | |||
140 | // TODO: | ||
141 | // - Wire up listeners | ||
142 | // - Look up pid and open on appropriate workspace | ||
143 | // - Set new view to maximized so it behaves nicely | ||
144 | // - Criteria | ||
145 | |||
146 | wl_shell_view->commit.notify = handle_commit; | ||
147 | wl_signal_add(&shell_surface->surface->events.commit, | ||
148 | &wl_shell_view->commit); | ||
149 | |||
150 | wl_shell_view->destroy.notify = handle_destroy; | ||
151 | wl_signal_add(&shell_surface->events.destroy, &wl_shell_view->destroy); | ||
152 | |||
153 | wl_shell_view->request_fullscreen.notify = handle_request_fullscreen; | ||
154 | wl_signal_add(&shell_surface->events.request_fullscreen, | ||
155 | &wl_shell_view->request_fullscreen); | ||
156 | |||
157 | wl_shell_view->set_state.notify = handle_set_state; | ||
158 | wl_signal_add(&shell_surface->events.set_state, &wl_shell_view->set_state); | ||
159 | |||
160 | view_map(&wl_shell_view->view, shell_surface->surface); | ||
161 | |||
162 | if (shell_surface->state == WLR_WL_SHELL_SURFACE_STATE_FULLSCREEN) { | ||
163 | view_set_fullscreen(&wl_shell_view->view, true); | ||
164 | } | ||
165 | } | ||
diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 48b659f2..9a0d282b 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c | |||
@@ -72,7 +72,7 @@ static struct sway_xdg_shell_view *xdg_shell_view_from_view( | |||
72 | return (struct sway_xdg_shell_view *)view; | 72 | return (struct sway_xdg_shell_view *)view; |
73 | } | 73 | } |
74 | 74 | ||
75 | static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { | 75 | static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { |
76 | if (xdg_shell_view_from_view(view) == NULL) { | 76 | if (xdg_shell_view_from_view(view) == NULL) { |
77 | return NULL; | 77 | return NULL; |
78 | } | 78 | } |
@@ -150,7 +150,7 @@ static void destroy(struct sway_view *view) { | |||
150 | } | 150 | } |
151 | 151 | ||
152 | static const struct sway_view_impl view_impl = { | 152 | static const struct sway_view_impl view_impl = { |
153 | .get_prop = get_prop, | 153 | .get_string_prop = get_string_prop, |
154 | .configure = configure, | 154 | .configure = configure, |
155 | .set_activated = set_activated, | 155 | .set_activated = set_activated, |
156 | .set_fullscreen = set_fullscreen, | 156 | .set_fullscreen = set_fullscreen, |
diff --git a/sway/desktop/xdg_shell_v6.c b/sway/desktop/xdg_shell_v6.c index e9051b6c..d098c797 100644 --- a/sway/desktop/xdg_shell_v6.c +++ b/sway/desktop/xdg_shell_v6.c | |||
@@ -72,7 +72,7 @@ static struct sway_xdg_shell_v6_view *xdg_shell_v6_view_from_view( | |||
72 | return (struct sway_xdg_shell_v6_view *)view; | 72 | return (struct sway_xdg_shell_v6_view *)view; |
73 | } | 73 | } |
74 | 74 | ||
75 | static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { | 75 | static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { |
76 | if (xdg_shell_v6_view_from_view(view) == NULL) { | 76 | if (xdg_shell_v6_view_from_view(view) == NULL) { |
77 | return NULL; | 77 | return NULL; |
78 | } | 78 | } |
@@ -150,7 +150,7 @@ static void destroy(struct sway_view *view) { | |||
150 | } | 150 | } |
151 | 151 | ||
152 | static const struct sway_view_impl view_impl = { | 152 | static const struct sway_view_impl view_impl = { |
153 | .get_prop = get_prop, | 153 | .get_string_prop = get_string_prop, |
154 | .configure = configure, | 154 | .configure = configure, |
155 | .set_activated = set_activated, | 155 | .set_activated = set_activated, |
156 | .set_fullscreen = set_fullscreen, | 156 | .set_fullscreen = set_fullscreen, |
diff --git a/sway/desktop/xwayland.c b/sway/desktop/xwayland.c index 1d3c857d..6a99a66a 100644 --- a/sway/desktop/xwayland.c +++ b/sway/desktop/xwayland.c | |||
@@ -122,7 +122,7 @@ static struct sway_xwayland_view *xwayland_view_from_view( | |||
122 | return (struct sway_xwayland_view *)view; | 122 | return (struct sway_xwayland_view *)view; |
123 | } | 123 | } |
124 | 124 | ||
125 | static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { | 125 | static const char *get_string_prop(struct sway_view *view, enum sway_view_prop prop) { |
126 | if (xwayland_view_from_view(view) == NULL) { | 126 | if (xwayland_view_from_view(view) == NULL) { |
127 | return NULL; | 127 | return NULL; |
128 | } | 128 | } |
@@ -131,11 +131,27 @@ static const char *get_prop(struct sway_view *view, enum sway_view_prop prop) { | |||
131 | return view->wlr_xwayland_surface->title; | 131 | return view->wlr_xwayland_surface->title; |
132 | case VIEW_PROP_CLASS: | 132 | case VIEW_PROP_CLASS: |
133 | return view->wlr_xwayland_surface->class; | 133 | return view->wlr_xwayland_surface->class; |
134 | case VIEW_PROP_INSTANCE: | ||
135 | return view->wlr_xwayland_surface->instance; | ||
134 | default: | 136 | default: |
135 | return NULL; | 137 | return NULL; |
136 | } | 138 | } |
137 | } | 139 | } |
138 | 140 | ||
141 | static uint32_t get_int_prop(struct sway_view *view, enum sway_view_prop prop) { | ||
142 | if (xwayland_view_from_view(view) == NULL) { | ||
143 | return 0; | ||
144 | } | ||
145 | switch (prop) { | ||
146 | case VIEW_PROP_X11_WINDOW_ID: | ||
147 | return view->wlr_xwayland_surface->window_id; | ||
148 | case VIEW_PROP_WINDOW_TYPE: | ||
149 | return *view->wlr_xwayland_surface->window_type; | ||
150 | default: | ||
151 | return 0; | ||
152 | } | ||
153 | } | ||
154 | |||
139 | static void configure(struct sway_view *view, double ox, double oy, int width, | 155 | static void configure(struct sway_view *view, double ox, double oy, int width, |
140 | int height) { | 156 | int height) { |
141 | struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view); | 157 | struct sway_xwayland_view *xwayland_view = xwayland_view_from_view(view); |
@@ -196,13 +212,17 @@ static void destroy(struct sway_view *view) { | |||
196 | wl_list_remove(&xwayland_view->destroy.link); | 212 | wl_list_remove(&xwayland_view->destroy.link); |
197 | wl_list_remove(&xwayland_view->request_configure.link); | 213 | wl_list_remove(&xwayland_view->request_configure.link); |
198 | wl_list_remove(&xwayland_view->request_fullscreen.link); | 214 | wl_list_remove(&xwayland_view->request_fullscreen.link); |
215 | wl_list_remove(&xwayland_view->set_title.link); | ||
216 | wl_list_remove(&xwayland_view->set_class.link); | ||
217 | wl_list_remove(&xwayland_view->set_window_type.link); | ||
199 | wl_list_remove(&xwayland_view->map.link); | 218 | wl_list_remove(&xwayland_view->map.link); |
200 | wl_list_remove(&xwayland_view->unmap.link); | 219 | wl_list_remove(&xwayland_view->unmap.link); |
201 | free(xwayland_view); | 220 | free(xwayland_view); |
202 | } | 221 | } |
203 | 222 | ||
204 | static const struct sway_view_impl view_impl = { | 223 | static const struct sway_view_impl view_impl = { |
205 | .get_prop = get_prop, | 224 | .get_string_prop = get_string_prop, |
225 | .get_int_prop = get_int_prop, | ||
206 | .configure = configure, | 226 | .configure = configure, |
207 | .set_activated = set_activated, | 227 | .set_activated = set_activated, |
208 | .set_fullscreen = set_fullscreen, | 228 | .set_fullscreen = set_fullscreen, |
@@ -219,7 +239,6 @@ static void handle_commit(struct wl_listener *listener, void *data) { | |||
219 | view_update_size(view, xwayland_view->pending_width, | 239 | view_update_size(view, xwayland_view->pending_width, |
220 | xwayland_view->pending_height); | 240 | xwayland_view->pending_height); |
221 | view_damage_from(view); | 241 | view_damage_from(view); |
222 | view_update_title(view, false); | ||
223 | } | 242 | } |
224 | 243 | ||
225 | static void handle_unmap(struct wl_listener *listener, void *data) { | 244 | static void handle_unmap(struct wl_listener *listener, void *data) { |
@@ -281,6 +300,40 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data) | |||
281 | view_set_fullscreen(view, xsurface->fullscreen); | 300 | view_set_fullscreen(view, xsurface->fullscreen); |
282 | } | 301 | } |
283 | 302 | ||
303 | static void handle_set_title(struct wl_listener *listener, void *data) { | ||
304 | struct sway_xwayland_view *xwayland_view = | ||
305 | wl_container_of(listener, xwayland_view, set_title); | ||
306 | struct sway_view *view = &xwayland_view->view; | ||
307 | struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; | ||
308 | if (!xsurface->mapped) { | ||
309 | return; | ||
310 | } | ||
311 | view_update_title(view, false); | ||
312 | view_execute_criteria(view); | ||
313 | } | ||
314 | |||
315 | static void handle_set_class(struct wl_listener *listener, void *data) { | ||
316 | struct sway_xwayland_view *xwayland_view = | ||
317 | wl_container_of(listener, xwayland_view, set_class); | ||
318 | struct sway_view *view = &xwayland_view->view; | ||
319 | struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; | ||
320 | if (!xsurface->mapped) { | ||
321 | return; | ||
322 | } | ||
323 | view_execute_criteria(view); | ||
324 | } | ||
325 | |||
326 | static void handle_set_window_type(struct wl_listener *listener, void *data) { | ||
327 | struct sway_xwayland_view *xwayland_view = | ||
328 | wl_container_of(listener, xwayland_view, set_window_type); | ||
329 | struct sway_view *view = &xwayland_view->view; | ||
330 | struct wlr_xwayland_surface *xsurface = view->wlr_xwayland_surface; | ||
331 | if (!xsurface->mapped) { | ||
332 | return; | ||
333 | } | ||
334 | view_execute_criteria(view); | ||
335 | } | ||
336 | |||
284 | void handle_xwayland_surface(struct wl_listener *listener, void *data) { | 337 | void handle_xwayland_surface(struct wl_listener *listener, void *data) { |
285 | struct sway_server *server = wl_container_of(listener, server, | 338 | struct sway_server *server = wl_container_of(listener, server, |
286 | xwayland_surface); | 339 | xwayland_surface); |
@@ -319,6 +372,16 @@ void handle_xwayland_surface(struct wl_listener *listener, void *data) { | |||
319 | &xwayland_view->request_fullscreen); | 372 | &xwayland_view->request_fullscreen); |
320 | xwayland_view->request_fullscreen.notify = handle_request_fullscreen; | 373 | xwayland_view->request_fullscreen.notify = handle_request_fullscreen; |
321 | 374 | ||
375 | wl_signal_add(&xsurface->events.set_title, &xwayland_view->set_title); | ||
376 | xwayland_view->set_title.notify = handle_set_title; | ||
377 | |||
378 | wl_signal_add(&xsurface->events.set_class, &xwayland_view->set_class); | ||
379 | xwayland_view->set_class.notify = handle_set_class; | ||
380 | |||
381 | wl_signal_add(&xsurface->events.set_window_type, | ||
382 | &xwayland_view->set_window_type); | ||
383 | xwayland_view->set_window_type.notify = handle_set_window_type; | ||
384 | |||
322 | wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap); | 385 | wl_signal_add(&xsurface->events.unmap, &xwayland_view->unmap); |
323 | xwayland_view->unmap.notify = handle_unmap; | 386 | xwayland_view->unmap.notify = handle_unmap; |
324 | 387 | ||
diff --git a/sway/meson.build b/sway/meson.build index 570d4783..67c2a422 100644 --- a/sway/meson.build +++ b/sway/meson.build | |||
@@ -12,7 +12,6 @@ sway_sources = files( | |||
12 | 'desktop/desktop.c', | 12 | 'desktop/desktop.c', |
13 | 'desktop/layer_shell.c', | 13 | 'desktop/layer_shell.c', |
14 | 'desktop/output.c', | 14 | 'desktop/output.c', |
15 | 'desktop/wl_shell.c', | ||
16 | 'desktop/xdg_shell_v6.c', | 15 | 'desktop/xdg_shell_v6.c', |
17 | 'desktop/xdg_shell.c', | 16 | 'desktop/xdg_shell.c', |
18 | 'desktop/xwayland.c', | 17 | 'desktop/xwayland.c', |
diff --git a/sway/server.c b/sway/server.c index 6147a39d..050ddf56 100644 --- a/sway/server.c +++ b/sway/server.c | |||
@@ -8,15 +8,14 @@ | |||
8 | #include <wlr/render/wlr_renderer.h> | 8 | #include <wlr/render/wlr_renderer.h> |
9 | #include <wlr/types/wlr_compositor.h> | 9 | #include <wlr/types/wlr_compositor.h> |
10 | #include <wlr/types/wlr_gamma_control.h> | 10 | #include <wlr/types/wlr_gamma_control.h> |
11 | #include <wlr/types/wlr_linux_dmabuf.h> | 11 | #include <wlr/types/wlr_idle.h> |
12 | #include <wlr/types/wlr_layer_shell.h> | 12 | #include <wlr/types/wlr_layer_shell.h> |
13 | #include <wlr/types/wlr_linux_dmabuf.h> | ||
13 | #include <wlr/types/wlr_primary_selection.h> | 14 | #include <wlr/types/wlr_primary_selection.h> |
14 | #include <wlr/types/wlr_screenshooter.h> | 15 | #include <wlr/types/wlr_screenshooter.h> |
15 | #include <wlr/types/wlr_server_decoration.h> | 16 | #include <wlr/types/wlr_server_decoration.h> |
16 | #include <wlr/types/wlr_xcursor_manager.h> | 17 | #include <wlr/types/wlr_xcursor_manager.h> |
17 | #include <wlr/types/wlr_xdg_output.h> | 18 | #include <wlr/types/wlr_xdg_output.h> |
18 | #include <wlr/types/wlr_wl_shell.h> | ||
19 | #include <wlr/types/wlr_idle.h> | ||
20 | #include <wlr/util/log.h> | 19 | #include <wlr/util/log.h> |
21 | // TODO WLR: make Xwayland optional | 20 | // TODO WLR: make Xwayland optional |
22 | #include <wlr/xwayland.h> | 21 | #include <wlr/xwayland.h> |
@@ -88,11 +87,6 @@ bool server_init(struct sway_server *server) { | |||
88 | &server->xdg_shell_surface); | 87 | &server->xdg_shell_surface); |
89 | server->xdg_shell_surface.notify = handle_xdg_shell_surface; | 88 | server->xdg_shell_surface.notify = handle_xdg_shell_surface; |
90 | 89 | ||
91 | server->wl_shell = wlr_wl_shell_create(server->wl_display); | ||
92 | wl_signal_add(&server->wl_shell->events.new_surface, | ||
93 | &server->wl_shell_surface); | ||
94 | server->wl_shell_surface.notify = handle_wl_shell_surface; | ||
95 | |||
96 | // TODO make xwayland optional | 90 | // TODO make xwayland optional |
97 | server->xwayland = | 91 | server->xwayland = |
98 | wlr_xwayland_create(server->wl_display, server->compositor, true); | 92 | wlr_xwayland_create(server->wl_display, server->compositor, true); |
diff --git a/sway/tree/container.c b/sway/tree/container.c index 9f7294db..a17b20f7 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c | |||
@@ -6,7 +6,6 @@ | |||
6 | #include <strings.h> | 6 | #include <strings.h> |
7 | #include <wayland-server.h> | 7 | #include <wayland-server.h> |
8 | #include <wlr/types/wlr_output_layout.h> | 8 | #include <wlr/types/wlr_output_layout.h> |
9 | #include <wlr/types/wlr_wl_shell.h> | ||
10 | #include <wlr/types/wlr_xdg_shell_v6.h> | 9 | #include <wlr/types/wlr_xdg_shell_v6.h> |
11 | #include <wlr/types/wlr_xdg_shell.h> | 10 | #include <wlr/types/wlr_xdg_shell.h> |
12 | #include "cairo.h" | 11 | #include "cairo.h" |
@@ -446,11 +445,6 @@ struct sway_container *container_at(struct sway_container *parent, | |||
446 | _surface = wlr_surface_surface_at(sview->surface, | 445 | _surface = wlr_surface_surface_at(sview->surface, |
447 | view_sx, view_sy, &_sx, &_sy); | 446 | view_sx, view_sy, &_sx, &_sy); |
448 | break; | 447 | break; |
449 | case SWAY_VIEW_WL_SHELL: | ||
450 | _surface = wlr_wl_shell_surface_surface_at( | ||
451 | sview->wlr_wl_shell_surface, | ||
452 | view_sx, view_sy, &_sx, &_sy); | ||
453 | break; | ||
454 | case SWAY_VIEW_XDG_SHELL_V6: | 448 | case SWAY_VIEW_XDG_SHELL_V6: |
455 | // the top left corner of the sway container is the | 449 | // the top left corner of the sway container is the |
456 | // coordinate of the top left corner of the window geometry | 450 | // coordinate of the top left corner of the window geometry |
diff --git a/sway/tree/view.c b/sway/tree/view.c index 392cd59d..8da72667 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" |
@@ -21,6 +22,7 @@ void view_init(struct sway_view *view, enum sway_view_type type, | |||
21 | const struct sway_view_impl *impl) { | 22 | const struct sway_view_impl *impl) { |
22 | view->type = type; | 23 | view->type = type; |
23 | view->impl = impl; | 24 | view->impl = impl; |
25 | view->executed_criteria = create_list(); | ||
24 | wl_signal_init(&view->events.unmap); | 26 | wl_signal_init(&view->events.unmap); |
25 | } | 27 | } |
26 | 28 | ||
@@ -33,6 +35,8 @@ void view_destroy(struct sway_view *view) { | |||
33 | view_unmap(view); | 35 | view_unmap(view); |
34 | } | 36 | } |
35 | 37 | ||
38 | list_free(view->executed_criteria); | ||
39 | |||
36 | container_destroy(view->swayc); | 40 | container_destroy(view->swayc); |
37 | 41 | ||
38 | if (view->impl->destroy) { | 42 | if (view->impl->destroy) { |
@@ -43,37 +47,56 @@ void view_destroy(struct sway_view *view) { | |||
43 | } | 47 | } |
44 | 48 | ||
45 | const char *view_get_title(struct sway_view *view) { | 49 | const char *view_get_title(struct sway_view *view) { |
46 | if (view->impl->get_prop) { | 50 | if (view->impl->get_string_prop) { |
47 | return view->impl->get_prop(view, VIEW_PROP_TITLE); | 51 | return view->impl->get_string_prop(view, VIEW_PROP_TITLE); |
48 | } | 52 | } |
49 | return NULL; | 53 | return NULL; |
50 | } | 54 | } |
51 | 55 | ||
52 | const char *view_get_app_id(struct sway_view *view) { | 56 | const char *view_get_app_id(struct sway_view *view) { |
53 | if (view->impl->get_prop) { | 57 | if (view->impl->get_string_prop) { |
54 | return view->impl->get_prop(view, VIEW_PROP_APP_ID); | 58 | return view->impl->get_string_prop(view, VIEW_PROP_APP_ID); |
55 | } | 59 | } |
56 | return NULL; | 60 | return NULL; |
57 | } | 61 | } |
58 | 62 | ||
59 | const char *view_get_class(struct sway_view *view) { | 63 | const char *view_get_class(struct sway_view *view) { |
60 | if (view->impl->get_prop) { | 64 | if (view->impl->get_string_prop) { |
61 | return view->impl->get_prop(view, VIEW_PROP_CLASS); | 65 | return view->impl->get_string_prop(view, VIEW_PROP_CLASS); |
62 | } | 66 | } |
63 | return NULL; | 67 | return NULL; |
64 | } | 68 | } |
65 | 69 | ||
66 | const char *view_get_instance(struct sway_view *view) { | 70 | const char *view_get_instance(struct sway_view *view) { |
67 | if (view->impl->get_prop) { | 71 | if (view->impl->get_string_prop) { |
68 | return view->impl->get_prop(view, VIEW_PROP_INSTANCE); | 72 | return view->impl->get_string_prop(view, VIEW_PROP_INSTANCE); |
69 | } | 73 | } |
70 | return NULL; | 74 | return NULL; |
71 | } | 75 | } |
72 | 76 | ||
77 | uint32_t view_get_x11_window_id(struct sway_view *view) { | ||
78 | if (view->impl->get_int_prop) { | ||
79 | return view->impl->get_int_prop(view, VIEW_PROP_X11_WINDOW_ID); | ||
80 | } | ||
81 | return 0; | ||
82 | } | ||
83 | |||
84 | const char *view_get_window_role(struct sway_view *view) { | ||
85 | if (view->impl->get_string_prop) { | ||
86 | return view->impl->get_string_prop(view, VIEW_PROP_WINDOW_ROLE); | ||
87 | } | ||
88 | return NULL; | ||
89 | } | ||
90 | |||
91 | uint32_t view_get_window_type(struct sway_view *view) { | ||
92 | if (view->impl->get_int_prop) { | ||
93 | return view->impl->get_int_prop(view, VIEW_PROP_WINDOW_TYPE); | ||
94 | } | ||
95 | return 0; | ||
96 | } | ||
97 | |||
73 | const char *view_get_type(struct sway_view *view) { | 98 | const char *view_get_type(struct sway_view *view) { |
74 | switch(view->type) { | 99 | switch(view->type) { |
75 | case SWAY_VIEW_WL_SHELL: | ||
76 | return "wl_shell"; | ||
77 | case SWAY_VIEW_XDG_SHELL_V6: | 100 | case SWAY_VIEW_XDG_SHELL_V6: |
78 | return "xdg_shell_v6"; | 101 | return "xdg_shell_v6"; |
79 | case SWAY_VIEW_XDG_SHELL: | 102 | case SWAY_VIEW_XDG_SHELL: |
@@ -320,19 +343,36 @@ static void view_handle_container_reparent(struct wl_listener *listener, | |||
320 | } | 343 | } |
321 | } | 344 | } |
322 | 345 | ||
323 | static void view_execute_criteria(struct sway_view *view) { | 346 | static bool view_has_executed_criteria(struct sway_view *view, |
324 | if (!sway_assert(view->swayc, "cannot run criteria for unmapped view")) { | 347 | struct criteria *criteria) { |
348 | for (int i = 0; i < view->executed_criteria->length; ++i) { | ||
349 | struct criteria *item = view->executed_criteria->items[i]; | ||
350 | if (item == criteria) { | ||
351 | return true; | ||
352 | } | ||
353 | } | ||
354 | return false; | ||
355 | } | ||
356 | |||
357 | void view_execute_criteria(struct sway_view *view) { | ||
358 | if (!view->swayc) { | ||
325 | return; | 359 | return; |
326 | } | 360 | } |
327 | struct sway_seat *seat = input_manager_current_seat(input_manager); | 361 | struct sway_seat *seat = input_manager_current_seat(input_manager); |
328 | struct sway_container *prior_workspace = | 362 | struct sway_container *prior_workspace = |
329 | container_parent(view->swayc, C_WORKSPACE); | 363 | container_parent(view->swayc, C_WORKSPACE); |
330 | list_t *criteria = criteria_for(view->swayc); | 364 | list_t *criterias = criteria_for_view(view, CT_COMMAND); |
331 | for (int i = 0; i < criteria->length; i++) { | 365 | for (int i = 0; i < criterias->length; i++) { |
332 | struct criteria *crit = criteria->items[i]; | 366 | struct criteria *criteria = criterias->items[i]; |
333 | wlr_log(L_DEBUG, "for_window '%s' matches new view %p, cmd: '%s'", | 367 | wlr_log(L_DEBUG, "Checking criteria %s", criteria->raw); |
334 | crit->crit_raw, view, crit->cmdlist); | 368 | if (view_has_executed_criteria(view, criteria)) { |
335 | struct cmd_results *res = execute_command(crit->cmdlist, NULL); | 369 | wlr_log(L_DEBUG, "Criteria already executed"); |
370 | continue; | ||
371 | } | ||
372 | wlr_log(L_DEBUG, "for_window '%s' matches view %p, cmd: '%s'", | ||
373 | criteria->raw, view, criteria->cmdlist); | ||
374 | list_add(view->executed_criteria, criteria); | ||
375 | struct cmd_results *res = execute_command(criteria->cmdlist, NULL); | ||
336 | if (res->status != CMD_SUCCESS) { | 376 | if (res->status != CMD_SUCCESS) { |
337 | wlr_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error); | 377 | wlr_log(L_ERROR, "Command '%s' failed: %s", res->input, res->error); |
338 | } | 378 | } |
@@ -341,7 +381,7 @@ static void view_execute_criteria(struct sway_view *view) { | |||
341 | // so always refocus in-between command lists | 381 | // so always refocus in-between command lists |
342 | seat_set_focus(seat, view->swayc); | 382 | seat_set_focus(seat, view->swayc); |
343 | } | 383 | } |
344 | list_free(criteria); | 384 | list_free(criterias); |
345 | seat_set_focus(seat, seat_get_focus_inactive(seat, prior_workspace)); | 385 | seat_set_focus(seat, seat_get_focus_inactive(seat, prior_workspace)); |
346 | } | 386 | } |
347 | 387 | ||
@@ -351,9 +391,27 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) { | |||
351 | } | 391 | } |
352 | 392 | ||
353 | struct sway_seat *seat = input_manager_current_seat(input_manager); | 393 | struct sway_seat *seat = input_manager_current_seat(input_manager); |
354 | struct sway_container *focus = seat_get_focus_inactive(seat, | 394 | struct sway_container *focus = |
355 | &root_container); | 395 | seat_get_focus_inactive(seat, &root_container); |
356 | struct sway_container *cont = container_view_create(focus, view); | 396 | struct sway_container *cont = NULL; |
397 | |||
398 | // Check if there's any `assign` criteria for the view | ||
399 | list_t *criterias = criteria_for_view(view, | ||
400 | CT_ASSIGN_WORKSPACE | CT_ASSIGN_OUTPUT); | ||
401 | if (criterias->length) { | ||
402 | struct criteria *criteria = criterias->items[0]; | ||
403 | if (criteria->type == CT_ASSIGN_WORKSPACE) { | ||
404 | struct sway_container *workspace = workspace_by_name(criteria->target); | ||
405 | if (!workspace) { | ||
406 | workspace = workspace_create(NULL, criteria->target); | ||
407 | } | ||
408 | focus = seat_get_focus_inactive(seat, workspace); | ||
409 | } else { | ||
410 | // TODO: CT_ASSIGN_OUTPUT | ||
411 | } | ||
412 | } | ||
413 | free(criterias); | ||
414 | cont = container_view_create(focus, view); | ||
357 | 415 | ||
358 | view->surface = wlr_surface; | 416 | view->surface = wlr_surface; |
359 | view->swayc = cont; | 417 | view->swayc = cont; |
@@ -371,10 +429,11 @@ void view_map(struct sway_view *view, struct wlr_surface *wlr_surface) { | |||
371 | arrange_children_of(cont->parent); | 429 | arrange_children_of(cont->parent); |
372 | input_manager_set_focus(input_manager, cont); | 430 | input_manager_set_focus(input_manager, cont); |
373 | 431 | ||
432 | view_update_title(view, false); | ||
433 | view_execute_criteria(view); | ||
434 | |||
374 | container_damage_whole(cont); | 435 | container_damage_whole(cont); |
375 | view_handle_container_reparent(&view->container_reparent, NULL); | 436 | view_handle_container_reparent(&view->container_reparent, NULL); |
376 | |||
377 | view_execute_criteria(view); | ||
378 | } | 437 | } |
379 | 438 | ||
380 | void view_unmap(struct sway_view *view) { | 439 | void view_unmap(struct sway_view *view) { |