New workspaces grow in; config saves don't redraw everything or race; media peek skips the player in front

- A workspace Hyprland makes for a switch (createworkspacev2) grows into the row
  over the slide instead of appearing at full size; the window keeps the full
  room meanwhile.
- Every config.toml save restarted the colour Animation, and Animation::new
  runs its clock even from a colour to itself, so every window redrew for 0.4 s
  per save. The colours now only fade when they change.
- The file watch could read config.toml halfway through the shell's own save
  and reset settings to their defaults. Saves and rereads share one lock, the
  file is written to a new file and renamed over, and the shell's own saves
  are skipped by the watch.
- A song change or new player only pops the island when the focused window
  isn't that player's app (parse::same_app, tested).
This commit is contained in:
server2
2026-10-15 11:28:44 +02:00
parent 5d322b940d
commit 6399844731
5 changed files with 190 additions and 57 deletions
+33 -2
View File
@@ -56,6 +56,9 @@ pub struct Edge {
pops: HashMap<(Part, String), Instant>,
switched: HashMap<String, (i64, Instant)>,
// workspaces hyprland made a moment ago (by switching to a new one), by id
created: HashMap<i64, Instant>,
// the monitor whose top edge the pointer rests on, and whether it's over a part of the bar
at_edge: Option<String>,
over_bar: bool,
@@ -84,6 +87,16 @@ impl Service for Edge {
let event = line.split(">>").next().unwrap_or("");
// createworkspacev2>>ID,NAME
if event == "createworkspacev2"
&& let Some(id) = line.split(">>").nth(1).and_then(|data| data.split(',').next()).and_then(|id| id.parse::<i64>().ok())
{
let mut edge = Edge::write();
edge.created.retain(|_, at| at.elapsed().as_secs_f32() < peeks::switch_s());
edge.created.insert(id, Instant::now());
}
if matches!(
event.trim_end_matches("v2"),
"fullscreen" | "workspace" | "focusedmon" | "openwindow" | "closewindow" | "movewindow" | "moveworkspace" | "activespecial" | "monitoradded" | "monitorremoved"
@@ -130,6 +143,11 @@ impl Edge {
(t < 1.0).then_some((*from, t))
}
// how far a workspace hyprland just made has grown into the row, 0 to 1 over the switch; 1 for the rest
pub fn grown(&self, id: i64) -> f32 {
self.created.get(&id).map_or(1.0, |at| (at.elapsed().as_secs_f32() / peeks::switch_s().max(0.01)).min(1.0))
}
fn fullscreen_on(&self, monitor: &str) -> bool {
self.fullscreen.contains(monitor)
}
@@ -292,10 +310,15 @@ fn watch_media() {
};
if let Some((known, before)) = &last {
let new_player = players.iter().any(|name| !known.contains(name));
let new_player = players.iter().find(|name| !known.contains(name));
let new_song = !before.is_empty() && song != *before;
if new_player || new_song {
// the one that changed: a new player, or the one whose song this is
let changed = new_player.cloned().or_else(|| new_song.then(|| song.split('\u{1f}').next().unwrap_or("").to_string()));
if let Some(player) = changed
&& !in_front(&player)
{
announce_media();
}
}
@@ -304,6 +327,14 @@ fn watch_media() {
}
}
// whether the focused window is this player's app: then its song change is in plain sight
fn in_front(player: &str) -> bool {
let window = parse::json(&hyprctl("j/activewindow"));
let class = window.first().and_then(|window| window.get("class")).and_then(parse::Value::text).unwrap_or("");
parse::same_app(class, player)
}
fn announce_media() {
let mut edge = Edge::write();