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.
This commit is contained in:
en 2026-09-16 07:59:56 +02:00
parent 0ad3e3335e
commit 91cfee9609
2 changed files with 38 additions and 5 deletions

View File

@ -1633,20 +1633,40 @@ impl App {
let profile_path = self.path.clone(); let profile_path = self.path.clone();
let allow = self.allow_layout; let allow = self.allow_layout;
// Compile the profile's window_match patterns once, outside the // Compile the profile's window_match patterns once, outside the
// poll loop. If a pattern is malformed we treat it as "not // poll loop. Malformed patterns are logged as a warning rather
// configured" rather than crashing the auto-apply thread. // 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 let class_pat = self
.profile .profile
.window_match .window_match
.class .class
.as_deref() .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 let title_pat = self
.profile .profile
.window_match .window_match
.title .title
.as_deref() .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(); let any_pattern = class_pat.is_some() || title_pat.is_some();
std::thread::Builder::new() std::thread::Builder::new()
.name("enboxer-auto-apply".into()) .name("enboxer-auto-apply".into())

View File

@ -1022,7 +1022,20 @@ async fn execute(actions: Vec<Action>, slots: &[(u32, Client)]) -> Result<()> {
}; };
for id in ids { for id in ids {
if let Some((_, c)) = slots.iter().find(|(s, _)| *s == id) { 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}"
);
}
} }
} }
} }