diff options
Diffstat (limited to 'sway/criteria.c')
-rw-r--r-- | sway/criteria.c | 684 |
1 files changed, 369 insertions, 315 deletions
diff --git a/sway/criteria.c b/sway/criteria.c index 7da790e6..22e9a49b 100644 --- a/sway/criteria.c +++ b/sway/criteria.c | |||
@@ -11,381 +11,435 @@ | |||
11 | #include "list.h" | 11 | #include "list.h" |
12 | #include "log.h" | 12 | #include "log.h" |
13 | 13 | ||
14 | bool criteria_is_empty(struct criteria *criteria) { | 14 | enum criteria_type { // *must* keep in sync with criteria_strings[] |
15 | return !criteria->title | 15 | CRIT_APP_ID, |
16 | && !criteria->app_id | 16 | CRIT_CLASS, |
17 | && !criteria->class | 17 | CRIT_CON_ID, |
18 | && !criteria->instance | 18 | CRIT_CON_MARK, |
19 | && !criteria->con_mark | 19 | CRIT_FLOATING, |
20 | && !criteria->con_id | 20 | CRIT_ID, |
21 | && !criteria->id | 21 | CRIT_INSTANCE, |
22 | && !criteria->window_role | 22 | CRIT_TILING, |
23 | && !criteria->window_type | 23 | CRIT_TITLE, |
24 | && !criteria->floating | 24 | CRIT_URGENT, |
25 | && !criteria->tiling | 25 | CRIT_WINDOW_ROLE, |
26 | && !criteria->urgent | 26 | CRIT_WINDOW_TYPE, |
27 | && !criteria->workspace; | 27 | CRIT_WORKSPACE, |
28 | } | 28 | CRIT_LAST |
29 | 29 | }; | |
30 | void criteria_destroy(struct criteria *criteria) { | ||
31 | pcre_free(criteria->title); | ||
32 | pcre_free(criteria->app_id); | ||
33 | pcre_free(criteria->class); | ||
34 | pcre_free(criteria->instance); | ||
35 | pcre_free(criteria->con_mark); | ||
36 | pcre_free(criteria->window_role); | ||
37 | free(criteria->workspace); | ||
38 | |||
39 | free(criteria->raw); | ||
40 | free(criteria); | ||
41 | } | ||
42 | |||
43 | static int regex_cmp(const char *item, const pcre *regex) { | ||
44 | return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); | ||
45 | } | ||
46 | 30 | ||
47 | static bool criteria_matches_view(struct criteria *criteria, | 31 | static const char * const criteria_strings[CRIT_LAST] = { |
48 | struct sway_view *view) { | 32 | [CRIT_APP_ID] = "app_id", |
49 | if (criteria->title) { | 33 | [CRIT_CLASS] = "class", |
50 | const char *title = view_get_title(view); | 34 | [CRIT_CON_ID] = "con_id", |
51 | if (!title || regex_cmp(title, criteria->title) != 0) { | 35 | [CRIT_CON_MARK] = "con_mark", |
52 | return false; | 36 | [CRIT_FLOATING] = "floating", |
53 | } | 37 | [CRIT_ID] = "id", |
54 | } | 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 | }; | ||
55 | 46 | ||
56 | if (criteria->app_id) { | 47 | /** |
57 | const char *app_id = view_get_app_id(view); | 48 | * A single criteria token (ie. value/regex pair), |
58 | if (!app_id || regex_cmp(app_id, criteria->app_id) != 0) { | 49 | * e.g. 'class="some class regex"'. |
59 | return false; | 50 | */ |
60 | } | 51 | struct crit_token { |
61 | } | 52 | enum criteria_type type; |
53 | pcre *regex; | ||
54 | char *raw; | ||
55 | }; | ||
62 | 56 | ||
63 | if (criteria->class) { | 57 | static void free_crit_token(struct crit_token *crit) { |
64 | const char *class = view_get_class(view); | 58 | pcre_free(crit->regex); |
65 | if (!class || regex_cmp(class, criteria->class) != 0) { | 59 | free(crit->raw); |
66 | return false; | 60 | free(crit); |
67 | } | 61 | } |
68 | } | ||
69 | 62 | ||
70 | if (criteria->instance) { | 63 | static void free_crit_tokens(list_t *crit_tokens) { |
71 | const char *instance = view_get_instance(view); | 64 | for (int i = 0; i < crit_tokens->length; i++) { |
72 | if (!instance || regex_cmp(instance, criteria->instance) != 0) { | 65 | free_crit_token(crit_tokens->items[i]); |
73 | return false; | ||
74 | } | ||
75 | } | 66 | } |
67 | list_free(crit_tokens); | ||
68 | } | ||
76 | 69 | ||
77 | if (criteria->con_mark) { | 70 | // Extracts criteria string from its brackets. Returns new (duplicate) |
78 | // TODO | 71 | // substring. |
79 | return false; | 72 | static char *criteria_from(const char *arg) { |
73 | char *criteria = NULL; | ||
74 | if (*arg == '[') { | ||
75 | criteria = strdup(arg + 1); | ||
76 | } else { | ||
77 | criteria = strdup(arg); | ||
80 | } | 78 | } |
81 | 79 | ||
82 | if (criteria->con_id) { // Internal ID | 80 | int last = strlen(criteria) - 1; |
83 | if (!view->swayc || view->swayc->id != criteria->con_id) { | 81 | if (criteria[last] == ']') { |
84 | return false; | 82 | criteria[last] = '\0'; |
85 | } | ||
86 | } | 83 | } |
84 | return criteria; | ||
85 | } | ||
87 | 86 | ||
88 | if (criteria->id) { // X11 window ID | 87 | // Return instances of c found in str. |
89 | uint32_t x11_window_id = view_get_x11_window_id(view); | 88 | static int countchr(char *str, char c) { |
90 | if (!x11_window_id || x11_window_id != criteria->id) { | 89 | int found = 0; |
91 | return false; | 90 | for (int i = 0; str[i]; i++) { |
91 | if (str[i] == c) { | ||
92 | ++found; | ||
92 | } | 93 | } |
93 | } | 94 | } |
95 | return found; | ||
96 | } | ||
94 | 97 | ||
95 | if (criteria->window_role) { | 98 | // criteria_str is e.g. '[class="some class regex" instance="instance name"]'. |
96 | // TODO | 99 | // |
97 | } | 100 | // Will create array of pointers in buf, where first is duplicate of given |
98 | 101 | // string (must be freed) and the rest are pointers to names and values in the | |
99 | if (criteria->window_type) { | 102 | // base string (every other, naturally). argc will be populated with the length |
100 | uint32_t type = view_get_window_type(view); | 103 | // of buf. |
101 | if (!type || type != criteria->window_type) { | 104 | // |
102 | return false; | 105 | // Returns error string or NULL if successful. |
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; | ||
103 | } | 174 | } |
175 | head++; | ||
104 | } | 176 | } |
105 | 177 | ||
106 | if (criteria->floating) { | 178 | // catch last unquoted value if needed |
107 | // TODO | 179 | if (valp && !quoted && !*head) { |
108 | return false; | 180 | argv[*argc] = valp; |
109 | } | 181 | *argc += 1; |
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 | } | 182 | } |
129 | 183 | ||
130 | return true; | 184 | return NULL; |
131 | } | 185 | } |
132 | 186 | ||
133 | list_t *criteria_for_view(struct sway_view *view, enum criteria_type types) { | 187 | // Returns error string on failure or NULL otherwise. |
134 | list_t *criterias = config->criteria; | 188 | static char *parse_criteria_name(enum criteria_type *type, char *name) { |
135 | list_t *matches = create_list(); | 189 | *type = CRIT_LAST; |
136 | for (int i = 0; i < criterias->length; ++i) { | 190 | for (int i = 0; i < CRIT_LAST; i++) { |
137 | struct criteria *criteria = criterias->items[i]; | 191 | if (strcmp(criteria_strings[i], name) == 0) { |
138 | if ((criteria->type & types) && criteria_matches_view(criteria, view)) { | 192 | *type = (enum criteria_type) i; |
139 | list_add(matches, criteria); | 193 | break; |
140 | } | 194 | } |
141 | } | 195 | } |
142 | return matches; | 196 | if (*type == CRIT_LAST) { |
143 | } | 197 | const char *fmt = "Criteria type '%s' is invalid or unsupported."; |
144 | 198 | int len = strlen(name) + strlen(fmt) - 1; | |
145 | struct match_data { | 199 | char *error = malloc(len); |
146 | struct criteria *criteria; | 200 | snprintf(error, len, fmt, name); |
147 | list_t *matches; | 201 | return error; |
148 | }; | 202 | } else if (*type == CRIT_URGENT || *type == CRIT_WINDOW_ROLE || |
149 | 203 | *type == CRIT_WINDOW_TYPE) { | |
150 | static void criteria_get_views_iterator(struct sway_container *container, | 204 | // (we're just being helpful here) |
151 | void *data) { | 205 | const char *fmt = "\"%s\" criteria currently unsupported, " |
152 | struct match_data *match_data = data; | 206 | "no window will match this"; |
153 | if (container->type == C_VIEW) { | 207 | int len = strlen(fmt) + strlen(name) - 1; |
154 | if (criteria_matches_view(match_data->criteria, container->sway_view)) { | 208 | char *error = malloc(len); |
155 | list_add(match_data->matches, container->sway_view); | 209 | snprintf(error, len, fmt, name); |
156 | } | 210 | return error; |
157 | } | 211 | } |
212 | return NULL; | ||
158 | } | 213 | } |
159 | 214 | ||
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 | |||
175 | // Returns error string on failure or NULL otherwise. | 215 | // Returns error string on failure or NULL otherwise. |
176 | static bool generate_regex(pcre **regex, char *value) { | 216 | static char *generate_regex(pcre **regex, char *value) { |
177 | const char *reg_err; | 217 | const char *reg_err; |
178 | int offset; | 218 | int offset; |
179 | 219 | ||
180 | *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); | 220 | *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, ®_err, &offset, NULL); |
181 | 221 | ||
182 | if (!*regex) { | 222 | if (!*regex) { |
183 | const char *fmt = "Regex compilation for '%s' failed: %s"; | 223 | const char *fmt = "Regex compilation (for '%s') failed: %s"; |
184 | int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; | 224 | int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3; |
185 | error = malloc(len); | 225 | char *error = malloc(len); |
186 | snprintf(error, len, fmt, value, reg_err); | 226 | snprintf(error, len, fmt, value, reg_err); |
187 | return false; | 227 | return error; |
188 | } | 228 | } |
229 | return NULL; | ||
230 | } | ||
189 | 231 | ||
190 | return true; | 232 | // Test whether the criterion corresponds to the currently focused window |
233 | static bool crit_is_focused(const char *value) { | ||
234 | return !strcmp(value, "focused") || !strcmp(value, "__focused__"); | ||
191 | } | 235 | } |
192 | 236 | ||
193 | static bool parse_token(struct criteria *criteria, char *name, char *value) { | 237 | // Populate list with crit_tokens extracted from criteria string, returns error |
194 | // Require value, unless token is floating or tiled | 238 | // string or NULL if successful. |
195 | if (!value && (strcmp(name, "title") == 0 | 239 | char *extract_crit_tokens(list_t *tokens, const char * const criteria) { |
196 | || strcmp(name, "app_id") == 0 | 240 | int argc; |
197 | || strcmp(name, "class") == 0 | 241 | char **argv = NULL, *error = NULL; |
198 | || strcmp(name, "instance") == 0 | 242 | if ((error = crit_tokens(&argc, &argv, criteria))) { |
199 | || strcmp(name, "con_id") == 0 | 243 | goto ect_cleanup; |
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; | ||
211 | } | 244 | } |
212 | 245 | for (int i = 1; i + 1 < argc; i += 2) { | |
213 | if (strcmp(name, "title") == 0) { | 246 | char* name = argv[i], *value = argv[i + 1]; |
214 | generate_regex(&criteria->title, value); | 247 | struct crit_token *token = calloc(1, sizeof(struct crit_token)); |
215 | } else if (strcmp(name, "app_id") == 0) { | 248 | token->raw = strdup(value); |
216 | generate_regex(&criteria->app_id, value); | 249 | |
217 | } else if (strcmp(name, "class") == 0) { | 250 | if ((error = parse_criteria_name(&token->type, name))) { |
218 | generate_regex(&criteria->class, value); | 251 | free_crit_token(token); |
219 | } else if (strcmp(name, "instance") == 0) { | 252 | goto ect_cleanup; |
220 | generate_regex(&criteria->instance, value); | 253 | } else if (token->type == CRIT_URGENT || crit_is_focused(value)) { |
221 | } else if (strcmp(name, "con_id") == 0) { | 254 | wlr_log(L_DEBUG, "%s -> \"%s\"", name, value); |
222 | char *endptr; | 255 | list_add(tokens, token); |
223 | criteria->con_id = strtoul(value, &endptr, 10); | 256 | } else if((error = generate_regex(&token->regex, value))) { |
224 | if (*endptr != 0) { | 257 | free_crit_token(token); |
225 | error = strdup("The value for 'con_id' should be numeric"); | 258 | goto ect_cleanup; |
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'; | ||
248 | } else { | 259 | } else { |
249 | error = | 260 | wlr_log(L_DEBUG, "%s -> /%s/", name, value); |
250 | strdup("The value for 'urgent' must be 'latest' or 'oldest'"); | 261 | list_add(tokens, token); |
251 | } | 262 | } |
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); | ||
259 | } | 263 | } |
260 | 264 | ect_cleanup: | |
261 | if (error) { | 265 | free(argv[0]); // base string |
262 | return false; | 266 | free(argv); |
263 | } | 267 | return error; |
264 | |||
265 | return true; | ||
266 | } | 268 | } |
267 | 269 | ||
268 | static void skip_spaces(char **head) { | 270 | static int regex_cmp(const char *item, const pcre *regex) { |
269 | while (**head == ' ') { | 271 | return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0); |
270 | ++*head; | ||
271 | } | ||
272 | } | 272 | } |
273 | 273 | ||
274 | // Remove escaping slashes from value | 274 | // test a single view if it matches list of criteria tokens (all of them). |
275 | static void unescape(char *value) { | 275 | static bool criteria_test(struct sway_container *cont, list_t *tokens) { |
276 | if (!strchr(value, '\\')) { | 276 | if (cont->type != C_CONTAINER && cont->type != C_VIEW) { |
277 | return; | 277 | return false; |
278 | } | ||
279 | char *copy = calloc(strlen(value) + 1, 1); | ||
280 | char *readhead = value; | ||
281 | char *writehead = copy; | ||
282 | while (*readhead) { | ||
283 | if (*readhead == '\\' && *(readhead + 1) == '"') { | ||
284 | // skip the slash | ||
285 | ++readhead; | ||
286 | } | ||
287 | *writehead = *readhead; | ||
288 | ++writehead; | ||
289 | ++readhead; | ||
290 | } | 278 | } |
291 | strcpy(value, copy); | 279 | int matches = 0; |
292 | free(copy); | 280 | for (int i = 0; i < tokens->length; i++) { |
293 | } | 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); | ||
294 | 298 | ||
295 | /** | 299 | if (*endptr == 0 && cont->id == crit_id) { |
296 | * Parse a raw criteria string such as [class="foo" instance="bar"] into a | 300 | ++matches; |
297 | * criteria struct. | 301 | } |
298 | * | 302 | break; |
299 | * If errors are found, NULL will be returned and the error argument will be | ||
300 | * populated with an error string. | ||
301 | */ | ||
302 | struct criteria *criteria_parse(char *raw, char **error_arg) { | ||
303 | free(error); | ||
304 | error = NULL; | ||
305 | |||
306 | char *head = raw; | ||
307 | skip_spaces(&head); | ||
308 | if (*head != '[') { | ||
309 | *error_arg = strdup("No criteria"); | ||
310 | return NULL; | ||
311 | } | ||
312 | ++head; | ||
313 | |||
314 | struct criteria *criteria = calloc(sizeof(struct criteria), 1); | ||
315 | char *name = NULL, *value = NULL; | ||
316 | bool in_quotes = false; | ||
317 | |||
318 | while (*head && *head != ']') { | ||
319 | skip_spaces(&head); | ||
320 | // Parse token name | ||
321 | char *namestart = head; | ||
322 | while ((*head >= 'a' && *head <= 'z') || *head == '_') { | ||
323 | ++head; | ||
324 | } | ||
325 | name = calloc(head - namestart + 1, 1); | ||
326 | strncpy(name, namestart, head - namestart); | ||
327 | // Parse token value | ||
328 | skip_spaces(&head); | ||
329 | value = NULL; | ||
330 | if (*head == '=') { | ||
331 | ++head; | ||
332 | skip_spaces(&head); | ||
333 | if (*head == '"') { | ||
334 | in_quotes = true; | ||
335 | ++head; | ||
336 | } | 303 | } |
337 | char *valuestart = head; | 304 | case CRIT_CON_MARK: |
338 | if (in_quotes) { | 305 | // TODO |
339 | while (*head && (*head != '"' || *(head - 1) == '\\')) { | 306 | break; |
340 | ++head; | 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; | ||
341 | } | 318 | } |
342 | if (!*head) { | 319 | |
343 | *error_arg = strdup("Quote mismatch in criteria"); | 320 | if (crit->regex && regex_cmp(app_id, crit->regex) == 0) { |
344 | goto cleanup; | 321 | matches++; |
345 | } | 322 | } |
346 | } else { | 323 | break; |
347 | while (*head && *head != ' ' && *head != ']') { | 324 | } |
348 | ++head; | 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++; | ||
349 | } | 334 | } |
335 | break; | ||
350 | } | 336 | } |
351 | value = calloc(head - valuestart + 1, 1); | 337 | case CRIT_TILING: |
352 | strncpy(value, valuestart, head - valuestart); | 338 | // TODO |
353 | if (in_quotes) { | 339 | break; |
354 | ++head; | 340 | case CRIT_TITLE: |
355 | in_quotes = false; | 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; | ||
356 | } | 351 | } |
357 | unescape(value); | 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; | ||
358 | } | 367 | } |
359 | wlr_log(L_DEBUG, "Found pair: %s=%s", name, value); | 368 | } |
360 | if (!parse_token(criteria, name, value)) { | 369 | return matches == tokens->length; |
361 | *error_arg = error; | 370 | } |
362 | goto cleanup; | 371 | |
372 | int criteria_cmp(const void *a, const void *b) { | ||
373 | if (a == b) { | ||
374 | return 0; | ||
375 | } else if (!a) { | ||
376 | return -1; | ||
377 | } else if (!b) { | ||
378 | return 1; | ||
379 | } | ||
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 | } | ||
387 | |||
388 | void free_criteria(struct criteria *crit) { | ||
389 | if (crit->tokens) { | ||
390 | free_crit_tokens(crit->tokens); | ||
391 | } | ||
392 | if (crit->cmdlist) { | ||
393 | free(crit->cmdlist); | ||
394 | } | ||
395 | if (crit->crit_raw) { | ||
396 | free(crit->crit_raw); | ||
397 | } | ||
398 | free(crit); | ||
399 | } | ||
400 | |||
401 | bool criteria_any(struct sway_container *cont, list_t *criteria) { | ||
402 | for (int i = 0; i < criteria->length; i++) { | ||
403 | struct criteria *bc = criteria->items[i]; | ||
404 | if (criteria_test(cont, bc->tokens)) { | ||
405 | return true; | ||
363 | } | 406 | } |
364 | skip_spaces(&head); | ||
365 | free(name); | ||
366 | free(value); | ||
367 | name = NULL; | ||
368 | value = NULL; | ||
369 | } | 407 | } |
370 | if (*head != ']') { | 408 | return false; |
371 | *error_arg = strdup("No closing brace found in criteria"); | 409 | } |
372 | goto cleanup; | 410 | |
411 | list_t *criteria_for(struct sway_container *cont) { | ||
412 | list_t *criteria = config->criteria, *matches = create_list(); | ||
413 | for (int i = 0; i < criteria->length; i++) { | ||
414 | struct criteria *bc = criteria->items[i]; | ||
415 | if (criteria_test(cont, bc->tokens)) { | ||
416 | list_add(matches, bc); | ||
417 | } | ||
373 | } | 418 | } |
419 | return matches; | ||
420 | } | ||
374 | 421 | ||
375 | if (criteria_is_empty(criteria)) { | 422 | struct list_tokens { |
376 | *error_arg = strdup("Criteria is empty"); | 423 | list_t *list; |
377 | goto cleanup; | 424 | list_t *tokens; |
425 | }; | ||
426 | |||
427 | static 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); | ||
378 | } | 431 | } |
432 | } | ||
379 | 433 | ||
380 | ++head; | 434 | list_t *container_for_crit_tokens(list_t *tokens) { |
381 | int len = head - raw; | 435 | struct list_tokens list_tokens = |
382 | criteria->raw = calloc(len + 1, 1); | 436 | (struct list_tokens){create_list(), tokens}; |
383 | strncpy(criteria->raw, raw, len); | ||
384 | return criteria; | ||
385 | 437 | ||
386 | cleanup: | 438 | container_for_each_descendant_dfs(&root_container, |
387 | free(name); | 439 | (void (*)(struct sway_container *, void *))container_match_add, |
388 | free(value); | 440 | &list_tokens); |
389 | criteria_destroy(criteria); | 441 | |
390 | return NULL; | 442 | // TODO look in the scratchpad |
443 | |||
444 | return list_tokens.list; | ||
391 | } | 445 | } |