summaryrefslogtreecommitdiffstats
path: root/sway/criteria.c
diff options
context:
space:
mode:
authorLibravatar S. Christoffer Eliesen <christoffer@eliesen.no>2015-11-17 19:27:01 +0100
committerLibravatar S. Christoffer Eliesen <christoffer@eliesen.no>2015-11-25 14:34:33 +0100
commita06cb7cd01acfbb5e31dd1aacbbde7887a0509b9 (patch)
tree1474dbed01d4c31318883a3a837e02ce30125bef /sway/criteria.c
parentMerge pull request #255 from christophgysin/ninja (diff)
downloadsway-a06cb7cd01acfbb5e31dd1aacbbde7887a0509b9.tar.gz
sway-a06cb7cd01acfbb5e31dd1aacbbde7887a0509b9.tar.zst
sway-a06cb7cd01acfbb5e31dd1aacbbde7887a0509b9.zip
criteria: Add. Learn for_window command.
A criteria is a string in the form of `[class="regex.*" title="str"]`. It is stored in a struct with a list of *tokens* which is a attribute/value pair (stored as a `crit_token` struct). Most tokens will also have a precompiled regex stored that will be used during criteria matching. for_window command: When a new view is created its metadata is tested against all stored criteria, and if a match is found the associated command list is executed. Unfortunately some metadata is not available in sway at the moment (specifically `instance`, `window_role` and `urgent`). Any criteria string that tries to match an unsupported attribute will fail. (Note that while the criteria code can be used to parse any criteria string it is currently only used by the `for_window` command.)
Diffstat (limited to 'sway/criteria.c')
-rw-r--r--sway/criteria.c349
1 files changed, 349 insertions, 0 deletions
diff --git a/sway/criteria.c b/sway/criteria.c
new file mode 100644
index 00000000..51779590
--- /dev/null
+++ b/sway/criteria.c
@@ -0,0 +1,349 @@
1#include <stdlib.h>
2#include <stdio.h>
3#include <stdbool.h>
4#include <regex.h>
5#include "criteria.h"
6#include "stringop.h"
7#include "list.h"
8#include "log.h"
9#include "container.h"
10#include "config.h"
11
12enum criteria_type { // *must* keep in sync with criteria_strings[]
13 CRIT_CLASS,
14 CRIT_ID,
15 CRIT_INSTANCE,
16 CRIT_TITLE,
17 CRIT_URGENT,
18 CRIT_WINDOW_ROLE,
19 CRIT_WINDOW_TYPE,
20 CRIT_WORKSPACE,
21 CRIT_LAST
22};
23
24// this *must* match the ordering in criteria_type enum
25static const char * const criteria_strings[] = {
26 "class",
27 "id",
28 "instance",
29 "title",
30 "urgent", // either "latest" or "oldest" ...
31 "window_role",
32 "window_type",
33 "workspace"
34};
35
36/**
37 * A single criteria token (ie. value/regex pair),
38 * e.g. 'class="some class regex"'.
39 */
40struct crit_token {
41 enum criteria_type type;
42 regex_t *regex;
43 char *raw;
44};
45
46static void free_crit_token(struct crit_token *crit) {
47 if (crit->regex) {
48 regfree(crit->regex);
49 free(crit->regex);
50 }
51 if (crit->raw) {
52 free(crit->raw);
53 }
54 free(crit);
55}
56
57static void free_crit_tokens(list_t *crit_tokens) {
58 for (int i = 0; i < crit_tokens->length; i++) {
59 free_crit_token(crit_tokens->items[i]);
60 }
61 list_free(crit_tokens);
62}
63
64// Extracts criteria string from its brackets. Returns new (duplicate)
65// substring.
66static char *criteria_from(const char *arg) {
67 char *criteria = NULL;
68 if (*arg == '[') {
69 criteria = strdup(arg + 1);
70 } else {
71 criteria = strdup(arg);
72 }
73
74 int last = strlen(criteria) - 1;
75 if (criteria[last] == ']') {
76 criteria[last] = '\0';
77 }
78 return criteria;
79}
80
81// Return instances of c found in str.
82static int countchr(char *str, char c) {
83 int found = 0;
84 for (int i = 0; str[i]; i++) {
85 if (str[i] == c) {
86 ++found;
87 }
88 }
89 return found;
90}
91
92// criteria_str is e.g. '[class="some class regex" instance="instance name"]'.
93//
94// Will create array of pointers in buf, where first is duplicate of given
95// string (must be freed) and the rest are pointers to names and values in the
96// base string (every other, naturally). argc will be populated with the length
97// of buf.
98//
99// Returns error string or NULL if successful.
100static char *crit_tokens(int *argc, char ***buf, const char * const criteria_str) {
101 sway_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str);
102 char *base = criteria_from(criteria_str);
103 char *head = base;
104 char *namep = head; // start of criteria name
105 char *valp = NULL; // start of value
106
107 // We're going to place EOS markers where we need to and fill up an array
108 // of pointers to the start of each token (either name or value).
109 int pairs = countchr(base, '=');
110 int max_tokens = pairs * 2 + 1; // this gives us at least enough slots
111
112 char **argv = *buf = calloc(max_tokens, sizeof(char*));
113 argv[0] = base; // this needs to be freed by caller
114
115 *argc = 1; // uneven = name, even = value
116 while (*head && *argc < max_tokens) {
117 if (namep != head && *(head - 1) == '\\') {
118 // escaped character: don't try to parse this
119 } else if (*head == '=' && namep != head) {
120 if (*argc % 2 != 1) {
121 // we're not expecting a name
122 return strdup("Unable to parse criteria: "
123 "Found out of place equal sign");
124 } else {
125 // name ends here
126 char *end = head; // don't want to rewind the head
127 while (*(end - 1) == ' ') {
128 --end;
129 }
130 *end = '\0';
131 if (*(namep) == ' ') {
132 namep = strrchr(namep, ' ') + 1;
133 }
134 argv[(*argc)++] = namep;
135 }
136 } else if (*head == '"') {
137 if (*argc % 2 != 0) {
138 // we're not expecting a value
139 return strdup("Unable to parse criteria: "
140 "Found quoted value where it was not expected");
141 } else if (!valp) { // value starts here
142 valp = head + 1;
143 } else {
144 // value ends here
145 argv[(*argc)++] = valp;
146 *head = '\0';
147 valp = NULL;
148 namep = head + 1;
149 }
150 } else if (*argc % 2 == 0 && !valp && *head != ' ') {
151 // We're expecting a quoted value, haven't found one yet, and this
152 // is not an empty space.
153 return strdup("Unable to parse criteria: "
154 "Names must be unquoted, values must be quoted");
155 }
156 head++;
157 }
158 return NULL;
159}
160
161// Returns error string on failure or NULL otherwise.
162static char *parse_criteria_name(enum criteria_type *type, char *name) {
163 *type = CRIT_LAST;
164 for (int i = 0; i < CRIT_LAST; i++) {
165 if (strcmp(criteria_strings[i], name) == 0) {
166 *type = (enum criteria_type) i;
167 break;
168 }
169 }
170 if (*type == CRIT_LAST) {
171 const char *fmt = "Criteria type '%s' is invalid or unsupported.";
172 int len = strlen(name) + strlen(fmt) - 1;
173 char *error = malloc(len);
174 snprintf(error, len, fmt, name);
175 return error;
176 } else if (*type == CRIT_INSTANCE || *type == CRIT_URGENT ||
177 *type == CRIT_WINDOW_ROLE || *type == CRIT_WINDOW_TYPE) {
178
179 // (we're just being helpful here)
180 const char *fmt = "\"%s\" criteria currently unsupported, "
181 "no window will match this";
182 int len = strlen(fmt) + strlen(name) - 1;
183 char *error = malloc(len);
184 snprintf(error, len, fmt, name);
185 return error;
186 }
187 return NULL;
188}
189
190// Returns error string on failure or NULL otherwise.
191static char *generate_regex(regex_t **regex, char *value) {
192 *regex = calloc(1, sizeof(regex_t));
193 int err = regcomp(*regex, value, REG_NOSUB);
194 if (err != 0) {
195 char *reg_err = malloc(64);
196 regerror(err, *regex, reg_err, 64);
197
198 const char *fmt = "Regex compilation (for '%s') failed: %s";
199 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3;
200 char *error = malloc(len);
201 snprintf(error, len, fmt, value, reg_err);
202 free(reg_err);
203 return error;
204 }
205 return NULL;
206}
207
208// Pouplate list with crit_tokens extracted from criteria string, returns error
209// string or NULL if successful.
210char *extract_crit_tokens(list_t *tokens, const char * const criteria) {
211 int argc;
212 char **argv = NULL, *error = NULL;
213 if ((error = crit_tokens(&argc, &argv, criteria))) {
214 goto ect_cleanup;
215 }
216 for (int i = 1; i + 1 < argc; i += 2) {
217 char* name = argv[i], *value = argv[i + 1];
218 struct crit_token *token = calloc(1, sizeof(struct crit_token));
219 token->raw = strdup(value);
220
221 if ((error = parse_criteria_name(&token->type, name))) {
222 free_crit_token(token);
223 goto ect_cleanup;
224 } else if (token->type == CRIT_URGENT || strcmp(value, "focused") == 0) {
225 sway_log(L_DEBUG, "%s -> \"%s\"", name, value);
226 list_add(tokens, token);
227 } else if((error = generate_regex(&token->regex, value))) {
228 free_crit_token(token);
229 goto ect_cleanup;
230 } else {
231 sway_log(L_DEBUG, "%s -> /%s/", name, value);
232 list_add(tokens, token);
233 }
234 }
235ect_cleanup:
236 free(argv[0]); // base string
237 free(argv);
238 return error;
239}
240
241// test a single view if it matches list of criteria tokens (all of them).
242static bool criteria_test(swayc_t *cont, list_t *tokens) {
243 if (cont->type != C_VIEW) {
244 return false;
245 }
246 int matches = 0;
247 for (int i = 0; i < tokens->length; i++) {
248 struct crit_token *crit = tokens->items[i];
249 switch (crit->type) {
250 case CRIT_CLASS:
251 if (!cont->class) {
252 // ignore
253 } else if (strcmp(crit->raw, "focused") == 0) {
254 swayc_t *focused = get_focused_view(&root_container);
255 if (focused->class && strcmp(cont->class, focused->class) == 0) {
256 matches++;
257 }
258 } else if (crit->regex && regexec(crit->regex, cont->class, 0, NULL, 0) == 0) {
259 matches++;
260 }
261 break;
262 case CRIT_ID:
263 if (!cont->app_id) {
264 // ignore
265 } else if (crit->regex && regexec(crit->regex, cont->app_id, 0, NULL, 0) == 0) {
266 matches++;
267 }
268 break;
269 case CRIT_INSTANCE:
270 break;
271 case CRIT_TITLE:
272 if (!cont->name) {
273 // ignore
274 } else if (strcmp(crit->raw, "focused") == 0) {
275 swayc_t *focused = get_focused_view(&root_container);
276 if (focused->name && strcmp(cont->name, focused->name) == 0) {
277 matches++;
278 }
279 } else if (crit->regex && regexec(crit->regex, cont->name, 0, NULL, 0) == 0) {
280 matches++;
281 }
282 break;
283 case CRIT_URGENT: // "latest" or "oldest"
284 break;
285 case CRIT_WINDOW_ROLE:
286 break;
287 case CRIT_WINDOW_TYPE:
288 // TODO wlc indeed exposes this information
289 break;
290 case CRIT_WORKSPACE: ;
291 swayc_t *cont_ws = swayc_parent_by_type(cont, C_WORKSPACE);
292 if (!cont_ws || !cont_ws->name) {
293 // ignore
294 } else if (strcmp(crit->raw, "focused") == 0) {
295 swayc_t *focused_ws = swayc_active_workspace();
296 if (focused_ws->name && strcmp(cont_ws->name, focused_ws->name) == 0) {
297 matches++;
298 }
299 } else if (crit->regex && regexec(crit->regex, cont_ws->name, 0, NULL, 0) == 0) {
300 matches++;
301 }
302 break;
303 default:
304 sway_abort("Invalid criteria type (%i)", crit->type);
305 break;
306 }
307 }
308 return matches == tokens->length;
309}
310
311int criteria_cmp(const void *a, const void *b) {
312 if (a == b) {
313 return 0;
314 } else if (!a) {
315 return -1;
316 } else if (!b) {
317 return 1;
318 }
319 const struct criteria *crit_a = a, *crit_b = b;
320 int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist);
321 if (cmp != 0) {
322 return cmp;
323 }
324 return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw);
325}
326
327void free_criteria(struct criteria *crit) {
328 if (crit->tokens) {
329 free_crit_tokens(crit->tokens);
330 }
331 if (crit->cmdlist) {
332 free(crit->cmdlist);
333 }
334 if (crit->crit_raw) {
335 free(crit->crit_raw);
336 }
337 free(crit);
338}
339
340list_t *criteria_for(swayc_t *cont) {
341 list_t *criteria = config->criteria, *matches = create_list();
342 for (int i = 0; i < criteria->length; i++) {
343 struct criteria *bc = criteria->items[i];
344 if (criteria_test(cont, bc->tokens)) {
345 list_add(matches, bc);
346 }
347 }
348 return matches;
349}