aboutsummaryrefslogtreecommitdiffstats
path: root/sway/criteria.c
diff options
context:
space:
mode:
authorLibravatar Tony Crisci <tony@dubstepdish.com>2018-01-20 16:21:45 -0500
committerLibravatar Tony Crisci <tony@dubstepdish.com>2018-01-20 16:21:45 -0500
commit6a1d71b8b8f33bdea3fb41bcd0de9439c0452682 (patch)
tree3ebbb611bb34864f9e86ceed87b6e32c73b93f84 /sway/criteria.c
parentadd kill command (diff)
downloadsway-6a1d71b8b8f33bdea3fb41bcd0de9439c0452682.tar.gz
sway-6a1d71b8b8f33bdea3fb41bcd0de9439c0452682.tar.zst
sway-6a1d71b8b8f33bdea3fb41bcd0de9439c0452682.zip
basic command criteria
Diffstat (limited to 'sway/criteria.c')
-rw-r--r--sway/criteria.c413
1 files changed, 413 insertions, 0 deletions
diff --git a/sway/criteria.c b/sway/criteria.c
new file mode 100644
index 00000000..c15f6354
--- /dev/null
+++ b/sway/criteria.c
@@ -0,0 +1,413 @@
1#define _XOPEN_SOURCE 700
2#include <stdlib.h>
3#include <stdio.h>
4#include <stdbool.h>
5#include <pcre.h>
6#include "sway/criteria.h"
7#include "sway/container.h"
8#include "sway/config.h"
9#include "sway/view.h"
10#include "stringop.h"
11#include "list.h"
12#include "log.h"
13
14enum criteria_type { // *must* keep in sync with criteria_strings[]
15 CRIT_APP_ID,
16 CRIT_CLASS,
17 CRIT_CON_ID,
18 CRIT_CON_MARK,
19 CRIT_FLOATING,
20 CRIT_ID,
21 CRIT_INSTANCE,
22 CRIT_TILING,
23 CRIT_TITLE,
24 CRIT_URGENT,
25 CRIT_WINDOW_ROLE,
26 CRIT_WINDOW_TYPE,
27 CRIT_WORKSPACE,
28 CRIT_LAST
29};
30
31static const char * const criteria_strings[CRIT_LAST] = {
32 [CRIT_APP_ID] = "app_id",
33 [CRIT_CLASS] = "class",
34 [CRIT_CON_ID] = "con_id",
35 [CRIT_CON_MARK] = "con_mark",
36 [CRIT_FLOATING] = "floating",
37 [CRIT_ID] = "id",
38 [CRIT_INSTANCE] = "instance",
39 [CRIT_TILING] = "tiling",
40 [CRIT_TITLE] = "title",
41 [CRIT_URGENT] = "urgent", // either "latest" or "oldest" ...
42 [CRIT_WINDOW_ROLE] = "window_role",
43 [CRIT_WINDOW_TYPE] = "window_type",
44 [CRIT_WORKSPACE] = "workspace"
45};
46
47/**
48 * A single criteria token (ie. value/regex pair),
49 * e.g. 'class="some class regex"'.
50 */
51struct crit_token {
52 enum criteria_type type;
53 pcre *regex;
54 char *raw;
55};
56
57static void free_crit_token(struct crit_token *crit) {
58 pcre_free(crit->regex);
59 free(crit->raw);
60 free(crit);
61}
62
63static void free_crit_tokens(list_t *crit_tokens) {
64 for (int i = 0; i < crit_tokens->length; i++) {
65 free_crit_token(crit_tokens->items[i]);
66 }
67 list_free(crit_tokens);
68}
69
70// Extracts criteria string from its brackets. Returns new (duplicate)
71// substring.
72static char *criteria_from(const char *arg) {
73 char *criteria = NULL;
74 if (*arg == '[') {
75 criteria = strdup(arg + 1);
76 } else {
77 criteria = strdup(arg);
78 }
79
80 int last = strlen(criteria) - 1;
81 if (criteria[last] == ']') {
82 criteria[last] = '\0';
83 }
84 return criteria;
85}
86
87// Return instances of c found in str.
88static int countchr(char *str, char c) {
89 int found = 0;
90 for (int i = 0; str[i]; i++) {
91 if (str[i] == c) {
92 ++found;
93 }
94 }
95 return found;
96}
97
98// criteria_str is e.g. '[class="some class regex" instance="instance name"]'.
99//
100// Will create array of pointers in buf, where first is duplicate of given
101// string (must be freed) and the rest are pointers to names and values in the
102// base string (every other, naturally). argc will be populated with the length
103// of buf.
104//
105// Returns error string or NULL if successful.
106static char *crit_tokens(int *argc, char ***buf, const char * const criteria_str) {
107 wlr_log(L_DEBUG, "Parsing criteria: '%s'", criteria_str);
108 char *base = criteria_from(criteria_str);
109 char *head = base;
110 char *namep = head; // start of criteria name
111 char *valp = NULL; // start of value
112
113 // We're going to place EOS markers where we need to and fill up an array
114 // of pointers to the start of each token (either name or value).
115 int pairs = countchr(base, '=');
116 int max_tokens = pairs * 2 + 1; // this gives us at least enough slots
117
118 char **argv = *buf = calloc(max_tokens, sizeof(char*));
119 argv[0] = base; // this needs to be freed by caller
120 bool quoted = true;
121
122 *argc = 1; // uneven = name, even = value
123 while (*head && *argc < max_tokens) {
124 if (namep != head && *(head - 1) == '\\') {
125 // escaped character: don't try to parse this
126 } else if (*head == '=' && namep != head) {
127 if (*argc % 2 != 1) {
128 // we're not expecting a name
129 return strdup("Unable to parse criteria: "
130 "Found out of place equal sign");
131 } else {
132 // name ends here
133 char *end = head; // don't want to rewind the head
134 while (*(end - 1) == ' ') {
135 --end;
136 }
137 *end = '\0';
138 if (*(namep) == ' ') {
139 namep = strrchr(namep, ' ') + 1;
140 }
141 argv[*argc] = namep;
142 *argc += 1;
143 }
144 } else if (*head == '"') {
145 if (*argc % 2 != 0) {
146 // we're not expecting a value
147 return strdup("Unable to parse criteria: "
148 "Found quoted value where it was not expected");
149 } else if (!valp) { // value starts here
150 valp = head + 1;
151 quoted = true;
152 } else {
153 // value ends here
154 argv[*argc] = valp;
155 *argc += 1;
156 *head = '\0';
157 valp = NULL;
158 namep = head + 1;
159 }
160 } else if (*argc % 2 == 0 && *head != ' ') {
161 // parse unquoted values
162 if (!valp) {
163 quoted = false;
164 valp = head; // value starts here
165 }
166 } else if (valp && !quoted && *head == ' ') {
167 // value ends here
168 argv[*argc] = valp;
169 *argc += 1;
170 *head = '\0';
171 valp = NULL;
172 namep = head + 1;
173 }
174 head++;
175 }
176
177 // catch last unquoted value if needed
178 if (valp && !quoted && !*head) {
179 argv[*argc] = valp;
180 *argc += 1;
181 }
182
183 return NULL;
184}
185
186// Returns error string on failure or NULL otherwise.
187static char *parse_criteria_name(enum criteria_type *type, char *name) {
188 *type = CRIT_LAST;
189 for (int i = 0; i < CRIT_LAST; i++) {
190 if (strcmp(criteria_strings[i], name) == 0) {
191 *type = (enum criteria_type) i;
192 break;
193 }
194 }
195 if (*type == CRIT_LAST) {
196 const char *fmt = "Criteria type '%s' is invalid or unsupported.";
197 int len = strlen(name) + strlen(fmt) - 1;
198 char *error = malloc(len);
199 snprintf(error, len, fmt, name);
200 return error;
201 } else if (*type == CRIT_URGENT || *type == CRIT_WINDOW_ROLE ||
202 *type == CRIT_WINDOW_TYPE) {
203 // (we're just being helpful here)
204 const char *fmt = "\"%s\" criteria currently unsupported, "
205 "no window will match this";
206 int len = strlen(fmt) + strlen(name) - 1;
207 char *error = malloc(len);
208 snprintf(error, len, fmt, name);
209 return error;
210 }
211 return NULL;
212}
213
214// Returns error string on failure or NULL otherwise.
215static char *generate_regex(pcre **regex, char *value) {
216 const char *reg_err;
217 int offset;
218
219 *regex = pcre_compile(value, PCRE_UTF8 | PCRE_UCP, &reg_err, &offset, NULL);
220
221 if (!*regex) {
222 const char *fmt = "Regex compilation (for '%s') failed: %s";
223 int len = strlen(fmt) + strlen(value) + strlen(reg_err) - 3;
224 char *error = malloc(len);
225 snprintf(error, len, fmt, value, reg_err);
226 return error;
227 }
228 return NULL;
229}
230
231// Test whether the criterion corresponds to the currently focused window
232static bool crit_is_focused(const char *value) {
233 return !strcmp(value, "focused") || !strcmp(value, "__focused__");
234}
235
236// Populate list with crit_tokens extracted from criteria string, returns error
237// string or NULL if successful.
238char *extract_crit_tokens(list_t *tokens, const char * const criteria) {
239 int argc;
240 char **argv = NULL, *error = NULL;
241 if ((error = crit_tokens(&argc, &argv, criteria))) {
242 goto ect_cleanup;
243 }
244 for (int i = 1; i + 1 < argc; i += 2) {
245 char* name = argv[i], *value = argv[i + 1];
246 struct crit_token *token = calloc(1, sizeof(struct crit_token));
247 token->raw = strdup(value);
248
249 if ((error = parse_criteria_name(&token->type, name))) {
250 free_crit_token(token);
251 goto ect_cleanup;
252 } else if (token->type == CRIT_URGENT || crit_is_focused(value)) {
253 wlr_log(L_DEBUG, "%s -> \"%s\"", name, value);
254 list_add(tokens, token);
255 } else if((error = generate_regex(&token->regex, value))) {
256 free_crit_token(token);
257 goto ect_cleanup;
258 } else {
259 wlr_log(L_DEBUG, "%s -> /%s/", name, value);
260 list_add(tokens, token);
261 }
262 }
263ect_cleanup:
264 free(argv[0]); // base string
265 free(argv);
266 return error;
267}
268
269static int regex_cmp(const char *item, const pcre *regex) {
270 return pcre_exec(regex, NULL, item, strlen(item), 0, 0, NULL, 0);
271}
272
273// test a single view if it matches list of criteria tokens (all of them).
274static bool criteria_test(swayc_t *cont, list_t *tokens) {
275 if (cont->type != C_VIEW) {
276 return false;
277 }
278 struct sway_view *view = cont->sway_view;
279
280 int matches = 0;
281 for (int i = 0; i < tokens->length; i++) {
282 struct crit_token *crit = tokens->items[i];
283 switch (crit->type) {
284 case CRIT_CLASS: // TODO
285 break;
286 case CRIT_CON_ID: {
287 char *endptr;
288 size_t crit_id = strtoul(crit->raw, &endptr, 10);
289
290 if (*endptr == 0 && cont->id == crit_id) {
291 ++matches;
292 }
293 break;
294 }
295 case CRIT_CON_MARK: // TODO
296 break;
297 case CRIT_FLOATING: // TODO
298 break;
299 case CRIT_ID: // TODO
300 break;
301 case CRIT_APP_ID:
302 if (!view->iface.get_prop) {
303 break;
304 }
305
306 const char *app_id =
307 cont->sway_view->iface.get_prop(view, VIEW_PROP_APP_ID);
308
309 if (!app_id) {
310 break;
311 }
312
313 if (crit->regex && regex_cmp(app_id, crit->regex) == 0) {
314 matches++;
315 }
316 break;
317 case CRIT_INSTANCE: // TODO
318 break;
319 case CRIT_TILING: // TODO
320 break;
321 case CRIT_TITLE:
322 if (!cont->name) {
323 // ignore
324 } else if (crit->regex && regex_cmp(cont->name, crit->regex) == 0) {
325 matches++;
326 }
327 break;
328 case CRIT_URGENT: // "latest" or "oldest"
329 break;
330 case CRIT_WINDOW_ROLE:
331 break;
332 case CRIT_WINDOW_TYPE:
333 break;
334 case CRIT_WORKSPACE: // TODO
335 break;
336 default:
337 sway_abort("Invalid criteria type (%i)", crit->type);
338 break;
339 }
340 }
341 return matches == tokens->length;
342}
343
344int criteria_cmp(const void *a, const void *b) {
345 if (a == b) {
346 return 0;
347 } else if (!a) {
348 return -1;
349 } else if (!b) {
350 return 1;
351 }
352 const struct criteria *crit_a = a, *crit_b = b;
353 int cmp = lenient_strcmp(crit_a->cmdlist, crit_b->cmdlist);
354 if (cmp != 0) {
355 return cmp;
356 }
357 return lenient_strcmp(crit_a->crit_raw, crit_b->crit_raw);
358}
359
360void free_criteria(struct criteria *crit) {
361 if (crit->tokens) {
362 free_crit_tokens(crit->tokens);
363 }
364 if (crit->cmdlist) {
365 free(crit->cmdlist);
366 }
367 if (crit->crit_raw) {
368 free(crit->crit_raw);
369 }
370 free(crit);
371}
372
373bool criteria_any(swayc_t *cont, list_t *criteria) {
374 for (int i = 0; i < criteria->length; i++) {
375 struct criteria *bc = criteria->items[i];
376 if (criteria_test(cont, bc->tokens)) {
377 return true;
378 }
379 }
380 return false;
381}
382
383list_t *criteria_for(swayc_t *cont) {
384 list_t *criteria = config->criteria, *matches = create_list();
385 for (int i = 0; i < criteria->length; i++) {
386 struct criteria *bc = criteria->items[i];
387 if (criteria_test(cont, bc->tokens)) {
388 list_add(matches, bc);
389 }
390 }
391 return matches;
392}
393
394struct list_tokens {
395 list_t *list;
396 list_t *tokens;
397};
398
399static void container_match_add(swayc_t *container, struct list_tokens *list_tokens) {
400 if (criteria_test(container, list_tokens->tokens)) {
401 list_add(list_tokens->list, container);
402 }
403}
404
405list_t *container_for(list_t *tokens) {
406 struct list_tokens list_tokens = (struct list_tokens){create_list(), tokens};
407
408 container_map(&root_container, (void (*)(swayc_t *, void *))container_match_add, &list_tokens);
409
410 // TODO look in the scratchpad
411
412 return list_tokens.list;
413}