From 91cfee9609a70c84fd0129055573e2ce2947113c Mon Sep 17 00:00:00 2001 From: en Date: Wed, 16 Sep 2026 07:59:56 +0200 Subject: [PATCH] Warn on malformed arm_auto_apply regex instead of silently falling through Both window_match.class and window_match.title patterns are now logged as warnings when regex::Regex::new returns Err. Before, .ok() silently swallowed compile errors and treated a bad pattern as 'not configured', which collapsed back to the original bug: any open window could fire layout-apply. The no-configured-patterns fallback (accept a window with a non-empty class) is preserved for users who haven't set window_match at all. --- src/gui.rs | 28 ++++++++++++++++++++++++---- src/session.rs | 15 ++++++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/gui.rs b/src/gui.rs index 80ca088..7a56a0a 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1633,20 +1633,40 @@ impl App { let profile_path = self.path.clone(); let allow = self.allow_layout; // Compile the profile's window_match patterns once, outside the - // poll loop. If a pattern is malformed we treat it as "not - // configured" rather than crashing the auto-apply thread. + // poll loop. Malformed patterns are logged as a warning rather + // than silently treated as "no pattern" — the latter would make + // a typo in the YAML fall back to firing on ANY client, which + // is the exact bug we fixed in 7c94417. let class_pat = self .profile .window_match .class .as_deref() - .and_then(|p| regex::Regex::new(p).ok()); + .and_then(|p| match regex::Regex::new(p) { + Ok(re) => Some(re), + Err(e) => { + tracing::warn!( + "arm_auto_apply: invalid window_match.class regex {:?}: {}", + p, e + ); + None + } + }); let title_pat = self .profile .window_match .title .as_deref() - .and_then(|p| regex::Regex::new(p).ok()); + .and_then(|p| match regex::Regex::new(p) { + Ok(re) => Some(re), + Err(e) => { + tracing::warn!( + "arm_auto_apply: invalid window_match.title regex {:?}: {}", + p, e + ); + None + } + }); let any_pattern = class_pat.is_some() || title_pat.is_some(); std::thread::Builder::new() .name("enboxer-auto-apply".into()) diff --git a/src/session.rs b/src/session.rs index 9527ee3..5a9bbf6 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1022,7 +1022,20 @@ async fn execute(actions: Vec, slots: &[(u32, Client)]) -> Result<()> { }; for id in ids { if let Some((_, c)) = slots.iter().find(|(s, _)| *s == id) { - hypr::deliver_key(c, &key, parsed_state).await?; + // Per-slot failures must NOT abort the rest of the + // chain. For example, a smart_interact (CTM on -> Alt+J + // -> sleep -> CTM off) must keep going through every + // captured slot even if one wlr-keyboard barf means + // Alt+J never lands on that client — otherwise CTM + // can stay on for the survivors and the user has to + // manually reset it. + if let Err(e) = + hypr::deliver_key(c, &key, parsed_state).await + { + tracing::warn!( + "deliver_key slot {id} key {key:?} failed: {e}" + ); + } } } }