@@ -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