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