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.
This commit is contained in:
codegax 2026-05-02 18:03:30 -06:00
parent 0bb4aba044
commit 93931c37a6
2 changed files with 43 additions and 21 deletions

View file

@ -426,7 +426,13 @@ runtime.
*class*, *instance*, *title*, *window_role*, and *window_type* keys
(which target xwayland views), each *swallows* entry may include
*app_id* to match Wayland views. The *machine* key is logged and
ignored. All values are PCRE2 regex strings.
ignored. *class*, *instance*, *title*, *window_role* and *app_id*
are PCRE2 regex strings. *window_type* must be a literal atom name
(*normal*, *dialog*, *utility*, *toolbar*, *splash*, *menu*,
*dropdown_menu*, *popup_menu*, *tooltip*, *notification*); the
i3-save-tree anchored form _^name$_ is accepted, but regex
alternation such as _^(normal|dialog)$_ is rejected, split such
patterns into multiple swallows entries (which match as OR).
A minimal example targeting one Wayland and one xwayland window in
a vertical split:

View file

@ -227,27 +227,38 @@ static bool append_bare(char **buf, const char *key, const char *value) {
return written >= 0;
}
// i3-save-tree emits window_type as a regex-anchored enum name like
// "^normal$". The criteria parser treats window_type as a bare enum token,
// not a regex, so the anchored form fails. Strip a single leading ^ and a
// single trailing $ before passing through.
static char *unanchor_enum(const char *value) {
// window_type is parsed as an enum, not a regex. Accept i3-save-tree's
// "^name$" form and bare names; reject anything else (including alternation)
// rather than letting the criteria parser silently fall through to ATOM_LAST.
static const char *known_window_types[] = {
"normal", "dialog", "utility", "toolbar", "splash", "menu",
"dropdown_menu", "popup_menu", "tooltip", "notification", NULL,
};
static char *parse_window_type_value(const char *value, char **error_out) {
size_t n = strlen(value);
size_t start = 0;
size_t end = n;
if (n > 0 && value[0] == '^') {
start = 1;
}
if (end > start && value[end - 1] == '$') {
end--;
}
char *out = malloc(end - start + 1);
if (!out) {
size_t start = (n > 0 && value[0] == '^') ? 1 : 0;
size_t end = (n > start && value[n - 1] == '$') ? n - 1 : n;
size_t bare_len = end - start;
char *bare = malloc(bare_len + 1);
if (!bare) {
*error_out = format_str("append_layout: out of memory");
return NULL;
}
memcpy(out, value + start, end - start);
out[end - start] = '\0';
return out;
memcpy(bare, value + start, bare_len);
bare[bare_len] = '\0';
for (int i = 0; known_window_types[i]; i++) {
if (strcasecmp(bare, known_window_types[i]) == 0) {
return bare;
}
}
*error_out = format_str("append_layout: window_type %s is not a "
"supported literal value (use one of: normal, dialog, "
"utility, toolbar, splash, menu, dropdown_menu, popup_menu, "
"tooltip, notification; regex alternation is not supported, "
"split into multiple swallow entries instead)", value);
free(bare);
return NULL;
}
// app_id is a sway extension over i3's swallows schema; machine is ignored.
@ -287,8 +298,13 @@ static struct criteria *build_swallow_criteria(struct json_object *entry,
"append_layout: swallows.window_type is not a string");
return NULL;
}
char *bare = unanchor_enum(json_object_get_string(wt));
if (!bare || !append_bare(&body, "window_type", bare)) {
char *bare = parse_window_type_value(json_object_get_string(wt),
error_out);
if (!bare) {
free(body);
return NULL;
}
if (!append_bare(&body, "window_type", bare)) {
free(bare);
free(body);
*error_out = format_str("append_layout: out of memory");