diff --git a/daemon/manager.rs b/daemon/manager.rs index 6a21416..d328039 100644 --- a/daemon/manager.rs +++ b/daemon/manager.rs @@ -47,10 +47,11 @@ impl NiriTag { ts.occupied = false; } }); - if let Some(old) = self.tags.get(&old_tag) { - if old_tag != 0 && !old.occupied { - self.fire_event(TagEvent::TagEmpty(old_tag)).await; - } + if let Some(old) = self.tags.get(&old_tag) + && old_tag != 0 + && !old.occupied + { + self.fire_event(TagEvent::TagEmpty(old_tag)).await; }; } @@ -208,14 +209,14 @@ impl NiriTag { affected_windows, ) .await; - if let Some(focus) = focus { - if tag_visible { - tell( - &mut self.socket, - Request::Action(Action::FocusWindow { id: focus }), - ) - .await?; - } + if let Some(focus) = focus + && tag_visible + { + tell( + &mut self.socket, + Request::Action(Action::FocusWindow { id: focus }), + ) + .await?; } } TagExclusive(t) => { @@ -293,38 +294,68 @@ impl NiriTag { } Receivable::TagCmd(cmd) => match cmd { TagCmd::AddTagToWin(t) => { - let wid = self.get_focused_window().await?.id; - self.change_window_tag(wid, Some(t)).await?; - let entry = self.tags.entry(t).or_default(); - if entry.windows.len() == 1 && self.config.activation_on_fill { - entry.enabled = true; - self.fire_event(TagEvent::TagEnabled(t)).await; - &[Tag(t), Window(wid)] + let win = self.get_focused_window().await?; + if win + .app_id + .as_ref() + .is_some_and(|name| self.config.scratchpads.contains_key(name)) + { + &[] } else { - &[Window(wid)] + let wid = win.id; + self.change_window_tag(wid, Some(t)).await?; + let entry = self.tags.entry(t).or_default(); + if entry.windows.len() == 1 && self.config.activation_on_fill { + entry.enabled = true; + self.fire_event(TagEvent::TagEnabled(t)).await; + &[Tag(t), Window(wid)] + } else { + &[Window(wid)] + } } } TagCmd::RemoveTagFromWin(_) => { - let wid = self.get_focused_window().await?.id; - self.change_window_tag(wid, None).await?; - &[Window(wid)] + let win = self.get_focused_window().await?; + if win + .app_id + .as_ref() + .is_some_and(|name| self.config.scratchpads.contains_key(name)) + { + &[] + } else { + let wid = win.id; + self.change_window_tag(wid, None).await?; + &[Window(wid)] + } } TagCmd::ToggleTagOnWin(t) => { - let wid = self.get_focused_window().await?.id; - let new_tag = if *self.windows.entry(wid).or_insert(0) == t { - 0 + let win = self.get_focused_window().await?; + if win + .app_id + .as_ref() + .is_some_and(|name| self.config.scratchpads.contains_key(name)) + { + &[] } else { - t - }; - self.change_window_tag(wid, Some(new_tag)).await?; - tracing::debug!("toggling {} to tag {}", wid, new_tag); - let entry = self.tags.entry(new_tag).or_default(); - if new_tag != 0 && entry.windows.len() == 1 && self.config.activation_on_fill { - entry.enabled = true; - self.fire_event(TagEvent::TagEnabled(t)).await; - &[Tag(t), Window(wid)] - } else { - &[Window(wid)] + let wid = win.id; + let new_tag = if *self.windows.entry(wid).or_insert(0) == t { + 0 + } else { + t + }; + self.change_window_tag(wid, Some(new_tag)).await?; + tracing::debug!("toggling {} to tag {}", wid, new_tag); + let entry = self.tags.entry(new_tag).or_default(); + if new_tag != 0 + && entry.windows.len() == 1 + && self.config.activation_on_fill + { + entry.enabled = true; + self.fire_event(TagEvent::TagEnabled(t)).await; + &[Tag(t), Window(wid)] + } else { + &[Window(wid)] + } } } @@ -386,7 +417,26 @@ impl NiriTag { use Event::*; match ev { WindowOpenedOrChanged { window } => { - self.windows.entry(window.id).or_insert(0); + let wid = window.id; + let current_tag = self.windows.get(&wid).copied(); + // only reassign if window is new or still untagged + // this handles the common case where app_id: None arrives first + if current_tag.is_none() || current_tag == Some(0) { + if let Some(ref app_id) = window.app_id + && let Some(&tag) = self.config.scratchpads.get(app_id) + { + tracing::debug!( + "scratchpad: auto-assigning wid={} app_id={} to tag {}", + wid, + app_id, + tag + ); + self.change_window_tag(wid, Some(tag)).await?; + self.do_actions(&[TagAction::Window(wid)]).await?; + } else if current_tag.is_none() { + self.windows.insert(wid, 0); + } + } Ok(()) } WindowClosed { id } => { @@ -395,10 +445,10 @@ impl NiriTag { ts.windows.remove(&id); ts.occupied = !ts.windows.is_empty(); }); - if let Some(tag) = self.tags.get(&t) { - if !tag.occupied { - self.fire_event(TagEvent::TagEmpty(t)).await; - } + if let Some(tag) = self.tags.get(&t) + && !tag.occupied + { + self.fire_event(TagEvent::TagEmpty(t)).await; } } Ok(()) @@ -477,7 +527,6 @@ impl NiriTag { (0..=self.config.prepopulate).for_each(|i| { self.tags.entry(i).or_default(); }); - loop { let recvd: Receivable = future::or( async { ev_rx.recv().await.map(Receivable::Event) }, diff --git a/lib/main.rs b/lib/main.rs index fd38a01..82e243b 100644 --- a/lib/main.rs +++ b/lib/main.rs @@ -60,6 +60,7 @@ pub struct Config { pub strict: bool, #[serde(default = "default_true")] pub activation_on_fill: bool, + pub scratchpads: HashMap, } impl Default for Config { @@ -68,6 +69,7 @@ impl Default for Config { prepopulate: 3, strict: true, activation_on_fill: true, + scratchpads: HashMap::new(), } } } diff --git a/module.nix b/module.nix index 903effb..642abb9 100644 --- a/module.nix +++ b/module.nix @@ -8,9 +8,12 @@ let inherit (lib) mkEnableOption mkPackageOption + mkOption mkIf getExe + types ; + inherit (types) attrsOf bool; name = "Niri Tag Manager"; in { @@ -20,6 +23,18 @@ in nullable = true; default = "niri-tag"; }; + prepopulate = mkOption { + type = types.numbers.between 0 255; + default = 3; + }; + scratchpads = mkOption { + type = attrsOf (types.numbers.between 1 255); + default = { }; + }; + strict = mkOption { + type = bool; + default = true; + }; }; config = let @@ -40,6 +55,24 @@ in PrivateTmp = true; }; }; + environment.etc."niri-tag/config.toml".source = + let + scratchpads = + let + contents = + (lib.flip lib.concatStringsSep) "\n" + <| lib.mapAttrsToList (app: number: "${app} = ${toString number}\n") cfg.scratchpads; + in + lib.optionalString ((builtins.length <| lib.attrsToList cfg.scratchpads) > 0) '' + [scratchpads] + ${contents} + ''; + in + builtins.trace scratchpads '' + prepopulate = ${toString cfg.prepopulate} + strict = ${lib.boolToString cfg.strict} + ${scratchpads} + ''; environment.systemPackages = [ cfg.package ]; }; }