Skip to content

Commit 86cbe37

Browse files
committed
tree/load_layout: validate window_type literals strictly
Anchored ^name$ and bare names are accepted; everything else, including regex alternation, returns a clear error instead of silently falling through to ATOM_LAST.
1 parent 7217f96 commit 86cbe37

2 files changed

Lines changed: 50 additions & 21 deletions

File tree

sway/sway.5.scd

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,13 @@ runtime.
426426
*class*, *instance*, *title*, *window_role*, and *window_type* keys
427427
(which target xwayland views), each *swallows* entry may include
428428
*app_id* to match Wayland views. The *machine* key is logged and
429-
ignored. All values are PCRE2 regex strings.
429+
ignored. *class*, *instance*, *title*, *window_role* and *app_id*
430+
are PCRE2 regex strings. *window_type* must be a literal atom name
431+
(*normal*, *dialog*, *utility*, *toolbar*, *splash*, *menu*,
432+
*dropdown_menu*, *popup_menu*, *tooltip*, *notification*); the
433+
i3-save-tree anchored form _^name$_ is accepted, but regex
434+
alternation such as _^(normal|dialog)$_ is rejected, split such
435+
patterns into multiple swallows entries (which match as OR).
430436

431437
A minimal example targeting one Wayland and one xwayland window in
432438
a vertical split:

sway/tree/load_layout.c

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -251,27 +251,45 @@ static bool append_bare(char **buf, const char *key, const char *value) {
251251
return written >= 0;
252252
}
253253

254-
// i3-save-tree emits window_type as a regex-anchored enum name like
255-
// "^normal$". The criteria parser treats window_type as a bare enum token,
256-
// not a regex, so the anchored form fails. Strip a single leading ^ and a
257-
// single trailing $ before passing through.
258-
static char *unanchor_enum(const char *value) {
254+
// Sway's criteria parser treats window_type as a bare enum token, not a
255+
// regex. To stay i3-save-tree compatible we accept the anchored literal form
256+
// "^name$" (and the unanchored "name") and reject everything else with a
257+
// clear error rather than silently dropping the constraint via ATOM_LAST.
258+
// Real regex constructs like "^(normal|dialog)$" are not supported; users
259+
// who want alternation should split into multiple swallow entries (which
260+
// match as OR).
261+
static const char *known_window_types[] = {
262+
"normal", "dialog", "utility", "toolbar", "splash", "menu",
263+
"dropdown_menu", "popup_menu", "tooltip", "notification", NULL,
264+
};
265+
266+
// Strip a single leading ^ and a single trailing $, then validate that the
267+
// remainder is one of the known atom names. Returns the bare name on
268+
// success, or NULL with *error_out populated on failure.
269+
static char *parse_window_type_value(const char *value, char **error_out) {
259270
size_t n = strlen(value);
260-
size_t start = 0;
261-
size_t end = n;
262-
if (n > 0 && value[0] == '^') {
263-
start = 1;
264-
}
265-
if (end > start && value[end - 1] == '$') {
266-
end--;
267-
}
268-
char *out = malloc(end - start + 1);
269-
if (!out) {
271+
size_t start = (n > 0 && value[0] == '^') ? 1 : 0;
272+
size_t end = (n > start && value[n - 1] == '$') ? n - 1 : n;
273+
size_t bare_len = end - start;
274+
char *bare = malloc(bare_len + 1);
275+
if (!bare) {
276+
*error_out = format_str("append_layout: out of memory");
270277
return NULL;
271278
}
272-
memcpy(out, value + start, end - start);
273-
out[end - start] = '\0';
274-
return out;
279+
memcpy(bare, value + start, bare_len);
280+
bare[bare_len] = '\0';
281+
for (int i = 0; known_window_types[i]; i++) {
282+
if (strcasecmp(bare, known_window_types[i]) == 0) {
283+
return bare;
284+
}
285+
}
286+
*error_out = format_str("append_layout: window_type %s is not a "
287+
"supported literal value (use one of: normal, dialog, "
288+
"utility, toolbar, splash, menu, dropdown_menu, popup_menu, "
289+
"tooltip, notification; regex alternation is not supported, "
290+
"split into multiple swallow entries instead)", value);
291+
free(bare);
292+
return NULL;
275293
}
276294

277295
// Build a single criteria from one entry of a swallows array. The entry is a
@@ -316,8 +334,13 @@ static struct criteria *build_swallow_criteria(struct json_object *entry,
316334
"append_layout: swallows.window_type is not a string");
317335
return NULL;
318336
}
319-
char *bare = unanchor_enum(json_object_get_string(wt));
320-
if (!bare || !append_bare(&body, "window_type", bare)) {
337+
char *bare = parse_window_type_value(json_object_get_string(wt),
338+
error_out);
339+
if (!bare) {
340+
free(body);
341+
return NULL;
342+
}
343+
if (!append_bare(&body, "window_type", bare)) {
321344
free(bare);
322345
free(body);
323346
*error_out = format_str("append_layout: out of memory");

0 commit comments

Comments
 (0)