Repository navigation
0.7.0 performance hardening and latest crate upgrades - #31
Conversation
Reviewer's GuideThis release combines a Rust/dependency and release-profile upgrade with broad hardening: IPC ownership and timeouts, blocking isolation for external work, strict configuration and CLI input handling, defensive GIF/video validation, safer Wayland/wgpu resource lifetimes, and regression tests covering the new failure modes. Sequence diagram for hardened IPC command handlingsequenceDiagram
participant Client
participant Socket as IpcSocket
participant Server as IPC server
participant Handler as Command handler
Client->>Socket: acquire(path)
Socket->>Socket: lock and validate socket ownership
Socket-->>Client: private listener
Client->>Server: send_ipc_command(command)
Server->>Server: limit connections and apply request timeout
Server->>Handler: validate_command(command)
Handler-->>Server: IpcResponse
Server-->>Client: bounded response or timeout
Sequence diagram for blocking theme and reload worksequenceDiagram
participant Client
participant Daemon
participant Runtime as Tokio runtime
participant Worker as Blocking worker
participant Process as Theme or hook process
Client->>Daemon: Reload or wallpaper command
Daemon->>Runtime: spawn_blocking(...)
Runtime->>Worker: execute reload_blocking or set_wallpaper_blocking
Worker->>Process: run provider, hook, or reload command
Process-->>Worker: exit or timeout
Worker-->>Runtime: result
Runtime-->>Daemon: response
Daemon-->>Client: IpcResponse
Flow diagram for defensive media input processingflowchart TD
Input[Untrusted GIF or video input] --> Bounds{Input and metadata bounds valid?}
Bounds -- No --> Reject[Return decode or format error]
Bounds -- Yes --> Decode[Decode and validate dimensions, rectangles, strides]
Decode --> Safe{Frame data complete and in range?}
Safe -- No --> Reject
Safe -- Yes --> Pipeline[Composite, copy, or render frame]
Flow diagram for safe release configuration and runtime tuningflowchart LR
Rust88[Rust 1.88 MSRV] --> Build[Native CPU and lld flags]
Build --> Release[Fat LTO, abort panic, overflow checks off]
Release --> Binary[wallr binary]
Binary --> Allocator[mimalloc global allocator]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 16 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="wallr-core/src/ipc/mod.rs" line_range="116-118" />
<code_context>
+ Err(err) => return Err(IpcError::Io(err)),
+ }
+
+ let listener = UnixListener::bind(&path)?;
+ use std::os::unix::fs::{MetadataExt, PermissionsExt};
+ if let Err(err) = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)) {
+ let _ = std::fs::remove_file(&path);
+ return Err(IpcError::Io(err));
</code_context>
<issue_to_address>
**Unauthorized socket access**
When the daemon binds a socket in a directory accessible to other users and the socket is initially writable by them, `UnixListener::bind` creates the socket before `set_permissions` applies mode `0600`, leaving a window for another local user to connect and queue privileged IPC commands that the daemon later processes.
Create the socket with restrictive permissions from the outset so no other user can connect before permissions are applied.
</issue_to_address>
### Comment 2
<location path="wallr-core/src/theme/mod.rs" line_range="444" />
<code_context>
+ match child.try_wait() {
+ Ok(Some(status)) => return Ok(status),
+ Ok(None) if started.elapsed() >= timeout => {
+ let _ = child.kill();
+ let _ = child.wait();
+ return Err(ThemeError::Timeout(timeout, label.to_string()));
</code_context>
<issue_to_address>
**Timed-out commands keep running**
When a timed-out shell hook or reload command has spawned a child process that outlives the shell, `wait_with_timeout` kills and reaps only the direct child, so descendants continue consuming resources or performing side effects after the daemon reports a timeout.
Terminate and reap the command’s process group when it times out.
Also at `wallr-core/src/theme/mod.rs:399-402`.
</issue_to_address>
### Comment 3
<location path="wallr/src/main.rs" line_range="273" />
<code_context>
+ let listener = UnixListener::bind(&path)?;
+ use std::os::unix::fs::{MetadataExt, PermissionsExt};
+ if let Err(err) = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)) {
+ let _ = std::fs::remove_file(&path);
+ return Err(IpcError::Io(err));
+ }
</code_context>
<issue_to_address>
**Stdin wallpaper disappears**
When a `wallr set -` request succeeds, the daemon persists the staged path for wallpaper restoration and starts detached theme work that uses it after sending the IPC response, but the CLI then unlinks the file. Theme processing fails, and the daemon cannot restore that wallpaper after a restart.
Keep the staged image available for daemon theme work and restart restoration instead of unlinking it when the IPC response arrives.
Also at `wallr-core/src/daemon/mod.rs:2891-2906`, `wallr/src/main.rs:274`.
</issue_to_address>
### Comment 4
<location path="wallr-core/src/daemon/mod.rs" line_range="2891-2905" />
<code_context>
- .await?;
+ });
tokio::task::spawn_blocking(move || {
loop {
diff --git a/wallr-core/src/ipc/mod.rs b/wallr-core/src/ipc/mod.rs
index 398b521..a999dc9 100644
--- a/wallr-core/src/ipc/mod.rs
+++ b/wallr-core/src/ipc/mod.rs
@@ -3,98 +3,452 @@
pub use wallr_common::ipc::*;
-use std::path::Path;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use std::time::Duration;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::net::{UnixListener, UnixStream};
+use tokio::sync::Semaphore;
+
</code_context>
<issue_to_address>
**Older theme overwrites newer one**
When an earlier wallpaper’s provider or hook is still running when a newer wallpaper request starts its theme work, the generation check runs only before `set_wallpaper_blocking`; the earlier task can finish afterward and overwrite the newer wallpaper’s theme, leaving colors inconsistent with the current wallpaper.
Prevent an older post-render task from writing its theme after a newer wallpaper request has started.
</issue_to_address>
### Comment 5
<location path="wallr-core/src/daemon/mod.rs" line_range="1878" />
<code_context>
+ let mut tmp = vec![0u8; size];
+ let ok = animated.decompress_into(index, &mut tmp);
+ if ok {
+ let mut mapped = slice.get_mapped_range_mut().unwrap();
+ mapped.copy_from_slice(&tmp);
+ }
</code_context>
<issue_to_address>
**Map failure aborts playback**
When wgpu fails to map the staging buffer, the map callback result and device-poll error are ignored, so `get_mapped_range_mut().unwrap()` panics; in release builds the detached playback task aborts the daemon instead of handling the GPU error.
Check and handle the map callback and device-poll results before accessing the mapped range.
Also at `wallr-core/src/daemon/mod.rs:1879`.
</issue_to_address>
### Comment 6
<location path=".cargo/config.toml" line_range="2" />
<code_context>
+[build]
+rustflags = ["-C", "target-cpu=native"]
+
+[target.x86_64-unknown-linux-gnu]
</code_context>
<issue_to_address>
**Release binary needs newer CPU**
When a published binary runs on an x86-64 CPU older than the build host, `target-cpu=native` lets code generation use the build host’s CPU features, so the release binary can execute unsupported instructions and terminate with an illegal-instruction fault.
Build distributable binaries for a supported baseline CPU instead of using `target-cpu=native`.
Also at `.cargo/config.toml:5`.
</issue_to_address>
### Comment 7
<location path="wallr-core/src/ipc/mod.rs" line_range="279-282" />
<code_context>
+ }
+ Ok(Ok(_)) => match serde_json::from_slice::<IpcCommand>(&buf) {
+ Ok(cmd) => match validate_command(&cmd) {
+ Ok(()) => match tokio::time::timeout(HANDLER_TIMEOUT, handler(cmd)).await {
+ Ok(response) => response,
+ Err(_) => IpcResponse::err("IPC command timed out"),
+ },
+ Err(reason) => IpcResponse::err(reason),
+ },
</code_context>
<issue_to_address>
**Timed-out render continues in background**
When rendering takes longer than the 120-second handler timeout, the handler timeout drops the future awaiting `set_wallpaper_with_retry`, but its `spawn_blocking` task continues; it can still change the wallpaper after the client receives a timeout, and a retry can enqueue another change. The caller sees failure while the wallpaper changes later.
Propagate cancellation to the blocking render operation, or avoid reporting a command timeout while its non-cancellable work can still mutate wallpaper state.
</issue_to_address>
### Comment 8
<location path="wallr-core/src/daemon/mod.rs" line_range="2746" />
<code_context>
{
let mut eng = engine.lock().await;
eng.config = fresh.clone();
- if let Err(e) = eng.reload() {
+ }
</code_context>
<issue_to_address>
**Failed reload leaves new config active**
When a reload command fails or times out after the new config passes validation, the reload handler replaces `engine.config` before running reload commands; if `reload_blocking` then fails, the handler returns an error but later wallpaper/theme operations still read the new config, leaving the daemon partially reloaded.
Run reload commands before committing `fresh` to `engine.config`, or restore the previous config if reload fails.
</issue_to_address>
### Comment 9
<location path="wallr-core/src/ipc/mod.rs" line_range="246" />
<code_context>
+ .create(true)
+ .truncate(false)
+ .mode(0o600)
+ .open(path)
+}
+
</code_context>
<issue_to_address>
**Lock file can block daemon startup**
When a different local user can write the configured socket’s parent directory and pre-creates a writable `<socket>.lock` file while holding an exclusive flock, `open_lock_file` opens the derived lock path and `lock_exclusive` maps a held lock to `AlreadyRunning`, so the daemon refuses to start even though no daemon owns the socket.
Store the lock in a user-private runtime directory, or securely create and validate the lock file’s ownership and permissions before relying on its lock.
</issue_to_address>
### Comment 10
<location path="wallr-common/src/config.rs" line_range="176" />
<code_context>
Some([x, y])
}
+/// Parse an IPC seek timestamp into milliseconds.
</code_context>
<issue_to_address>
**Default socket fails without runtime directory**
When `XDG_RUNTIME_DIR` is unset and a command uses the default socket, `expand_path` returns `UnsetEnv` for the default socket path, so CLI commands and daemon startup fail before using a socket, even when another writable runtime location is available.
Provide a fallback socket path when `XDG_RUNTIME_DIR` is unset.
Also at `wallr-common/src/config.rs:175`, `wallr-common/src/config.rs:287`.
</issue_to_address>
### Comment 11
<location path="wallr-common/src/cli.rs" line_range="171-174" />
<code_context>
+ return Err("timestamp must be a finite non-negative number".to_string());
+ }
+ let millis = (seconds * 1000.0).round();
+ if millis > u64::MAX as f64 {
+ return Err("timestamp overflows".to_string());
+ }
+ Ok(millis as u64)
+ }
+}
</code_context>
<issue_to_address>
**Overflowing timestamp is accepted**
When a finite bare-seconds timestamp rounds to 2^64 milliseconds, `u64::MAX as f64` rounds to 2^64, so the `>` check accepts that value and the cast saturates to `u64::MAX` instead of returning the documented overflow error.
Reject rounded millisecond values equal to 2^64 as well as larger values.
</issue_to_address>
### Comment 12
<location path="wallr-core/src/daemon/mod.rs" line_range="1875-1878" />
<code_context>
+ .poll(wgpu::PollType::wait_indefinitely())
+ .ok();
+ let size = slice.size() as usize;
+ let mut tmp = vec![0u8; size];
+ let ok = animated.decompress_into(index, &mut tmp);
+ if ok {
+ let mut mapped = slice.get_mapped_range_mut().unwrap();
+ mapped.copy_from_slice(&tmp);
+ }
</code_context>
<issue_to_address>
**GIF uploads churn memory**
When a GIF uses the direct-upload playback path, the path allocates a full-frame temporary `Vec` for each GIF frame, decompresses into it, then copies it into the mapped staging buffer. Large animations incur recurring allocation and memory-bandwidth overhead that can undermine smooth playback and exhaust memory.
Decompress directly into the mapped staging buffer without allocating and copying a full-frame temporary buffer.
</issue_to_address>
### Comment 13
<location path="wallr-core/src/theme/mod.rs" line_range="428" />
<code_context>
+ };
+ command.stdout(Stdio::null()).stderr(Stdio::null());
+ if let Ok(mut child) = command.spawn() {
+ let _ = wait_with_timeout(&mut child, RELOAD_TIMEOUT, cmd);
}
}
</code_context>
<issue_to_address>
**Timed-out reloads report success**
When a configured reload command exceeds `RELOAD_TIMEOUT`, `run_reload_list` discards the result of `wait_with_timeout` and returns `Ok(())`, so a reload command killed after its timeout is reported as successful and leaves no diagnostic for the operator.
Log timed-out reload commands and return an error or otherwise expose the timeout to the caller while continuing to ignore expected `pkill` nonzero exits.
</issue_to_address>
### Comment 14
<location path="wallr-common/src/config.rs" line_range="175" />
<code_context>
+
fn default_socket() -> String {
- "/tmp/wallr.sock".to_string()
+ "$XDG_RUNTIME_DIR/wallr.sock".to_string()
}
</code_context>
<issue_to_address>
**Upgrade starts a second daemon**
When a config omits `daemon.socket` and a 0.6.4 daemon is still running during an upgrade, a config without an explicit `daemon.socket` now expands to `$XDG_RUNTIME_DIR/wallr.sock`, while a still-running 0.6.4 daemon listens on `/tmp/wallr.sock`. The new CLI checks only the new path, so `wallr set` starts another daemon and both can control the same outputs.
Preserve the legacy default or detect and migrate an active daemon on the old socket before auto-starting a new one.
</issue_to_address>
### Comment 15
<location path="Cargo.toml" line_range="14" />
<code_context>
categories = ["command-line-utilities", "rendering"]
keywords = ["wallpaper", "wayland", "wgpu", "daemon", "matugen"]
-rust-version = "1.85"
+rust-version = "1.88"
# Optimized profile settings for faster compilation and better runtime performance
</code_context>
<issue_to_address>
**Contributor setup advertises older Rust**
When a contributor follows the documented 1.85 minimum, contributors following `CONTRIBUTING.md` can install Rust 1.85, but Cargo rejects the workspace because the declared minimum is now 1.88, so the documented setup does not build the project.
Update the contributor setup requirement to Rust 1.88 or newer.
</issue_to_address>
### Comment 16
<location path="wallr-core/src/animated/mod.rs" line_range="311-314" />
<code_context>
- self.next_index += 1;
- continue;
- }
+ // Do not skip cached frames without consuming the decoder: the
+ // decoded stream position and `next_index` must stay in lockstep.
+ // Otherwise a later uncached tail frame is stored under the wrong
+ // index after a loop wrap. Re-decoding a cached prefix still uses
+ // the same cache entry (decode_next only inserts when absent).
let before = self.next_index;
</code_context>
<issue_to_address>
**Cached GIF loops decode frames again**
When playback wraps an animation whose frames are already fully cached, after a full loop, `ensure_upto` restarts the GIF decoder and decodes each cached frame again to keep decoder position synchronized, even though playback can return those frames from cache. Fully cached animations therefore spend every loop decoding the animation a second time, increasing CPU use and risking playback stutter.
Use cached frames without decoding them when no uncached tail needs the decoder, and restart/advance the decoder only when an uncached frame must be produced.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 15 findings to address first, and the daemon now relies on socket permissions, ownership checks, and inode-based cleanup to control access to its IPC commands; if that boundary is wrong, another local user could control or disrupt the daemon. Reverting restores the previous behavior, but any unauthorized commands or external hooks already triggered would not be undone.
Blocking findings: wallr-core/src/ipc/mod.rs:118, wallr-core/src/theme/mod.rs:444, wallr/src/main.rs:273, wallr-core/src/daemon/mod.rs:2905, wallr-core/src/daemon/mod.rs:1878, and 10 more
| let listener = UnixListener::bind(&path)?; | ||
| use std::os::unix::fs::{MetadataExt, PermissionsExt}; | ||
| if let Err(err) = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)) { |
There was a problem hiding this comment.
🟡 Medium · Unauthorized socket access
When the daemon binds a socket in a directory accessible to other users and the socket is initially writable by them, UnixListener::bind creates the socket before set_permissions applies mode 0600, leaving a window for another local user to connect and queue privileged IPC commands that the daemon later processes.
Create the socket with restrictive permissions from the outset so no other user can connect before permissions are applied.
Prompt for AI agents
In `wallr-core/src/ipc/mod.rs` at lines 116-118:
**Unauthorized socket access**
When the daemon binds a socket in a directory accessible to other users and the socket is initially writable by them, `UnixListener::bind` creates the socket before `set_permissions` applies mode `0600`, leaving a window for another local user to connect and queue privileged IPC commands that the daemon later processes.
Create the socket with restrictive permissions from the outset so no other user can connect before permissions are applied.| match child.try_wait() { | ||
| Ok(Some(status)) => return Ok(status), | ||
| Ok(None) if started.elapsed() >= timeout => { | ||
| let _ = child.kill(); |
There was a problem hiding this comment.
🟡 Medium · Timed-out commands keep running
When a timed-out shell hook or reload command has spawned a child process that outlives the shell, wait_with_timeout kills and reaps only the direct child, so descendants continue consuming resources or performing side effects after the daemon reports a timeout.
Terminate and reap the command’s process group when it times out.
Also at wallr-core/src/theme/mod.rs:399-402.
Prompt for AI agents
In `wallr-core/src/theme/mod.rs` at line 444:
**Timed-out commands keep running**
When a timed-out shell hook or reload command has spawned a child process that outlives the shell, `wait_with_timeout` kills and reaps only the direct child, so descendants continue consuming resources or performing side effects after the daemon reports a timeout.
Terminate and reap the command’s process group when it times out.
Also at `wallr-core/src/theme/mod.rs:399-402`.| anyhow::bail!("{}", msg); | ||
| .await; | ||
| if staged { | ||
| let _ = std::fs::remove_file(&path); |
There was a problem hiding this comment.
🟠 High · Stdin wallpaper disappears
When a wallr set - request succeeds, the daemon persists the staged path for wallpaper restoration and starts detached theme work that uses it after sending the IPC response, but the CLI then unlinks the file. Theme processing fails, and the daemon cannot restore that wallpaper after a restart.
Keep the staged image available for daemon theme work and restart restoration instead of unlinking it when the IPC response arrives.
Also at wallr-core/src/daemon/mod.rs:2891-2906, wallr/src/main.rs:274.
Prompt for AI agents
In `wallr/src/main.rs` at line 273:
**Stdin wallpaper disappears**
When a `wallr set -` request succeeds, the daemon persists the staged path for wallpaper restoration and starts detached theme work that uses it after sending the IPC response, but the CLI then unlinks the file. Theme processing fails, and the daemon cannot restore that wallpaper after a restart.
Keep the staged image available for daemon theme work and restart restoration instead of unlinking it when the IPC response arrives.
Also at `wallr-core/src/daemon/mod.rs:2891-2906`, `wallr/src/main.rs:274`.| tokio::task::spawn_blocking(move || { | ||
| if theme_gen_check.load(Ordering::SeqCst) != generation { | ||
| return; | ||
| } | ||
| let mut eng = match WallpaperEngine::new(config) { | ||
| Ok(engine) => engine, | ||
| Err(err) => { | ||
| tracing::warn!( | ||
| "Post-render theme setup failed for {p_clone:?}: {err}" | ||
| ); | ||
| return; | ||
| } | ||
| }; | ||
| if let Err(e) = | ||
| eng.set_wallpaper(&p_clone, &opts).await | ||
| eng.set_wallpaper_blocking(&p_clone, &opts) |
There was a problem hiding this comment.
🟠 High · Older theme overwrites newer one
When an earlier wallpaper’s provider or hook is still running when a newer wallpaper request starts its theme work, the generation check runs only before set_wallpaper_blocking; the earlier task can finish afterward and overwrite the newer wallpaper’s theme, leaving colors inconsistent with the current wallpaper.
Prevent an older post-render task from writing its theme after a newer wallpaper request has started.
Prompt for AI agents
In `wallr-core/src/daemon/mod.rs` at lines 2891-2905:
**Older theme overwrites newer one**
When an earlier wallpaper’s provider or hook is still running when a newer wallpaper request starts its theme work, the generation check runs only before `set_wallpaper_blocking`; the earlier task can finish afterward and overwrite the newer wallpaper’s theme, leaving colors inconsistent with the current wallpaper.
Prevent an older post-render task from writing its theme after a newer wallpaper request has started.| let mut tmp = vec![0u8; size]; | ||
| let ok = animated.decompress_into(index, &mut tmp); | ||
| if ok { | ||
| let mut mapped = slice.get_mapped_range_mut().unwrap(); |
There was a problem hiding this comment.
🟠 High · Map failure aborts playback
When wgpu fails to map the staging buffer, the map callback result and device-poll error are ignored, so get_mapped_range_mut().unwrap() panics; in release builds the detached playback task aborts the daemon instead of handling the GPU error.
Check and handle the map callback and device-poll results before accessing the mapped range.
Also at wallr-core/src/daemon/mod.rs:1879.
Prompt for AI agents
In `wallr-core/src/daemon/mod.rs` at line 1878:
**Map failure aborts playback**
When wgpu fails to map the staging buffer, the map callback result and device-poll error are ignored, so `get_mapped_range_mut().unwrap()` panics; in release builds the detached playback task aborts the daemon instead of handling the GPU error.
Check and handle the map callback and device-poll results before accessing the mapped range.
Also at `wallr-core/src/daemon/mod.rs:1879`.| let mut tmp = vec![0u8; size]; | ||
| let ok = animated.decompress_into(index, &mut tmp); | ||
| if ok { | ||
| let mut mapped = slice.get_mapped_range_mut().unwrap(); |
There was a problem hiding this comment.
🟡 Medium · GIF uploads churn memory
When a GIF uses the direct-upload playback path, the path allocates a full-frame temporary Vec for each GIF frame, decompresses into it, then copies it into the mapped staging buffer. Large animations incur recurring allocation and memory-bandwidth overhead that can undermine smooth playback and exhaust memory.
Decompress directly into the mapped staging buffer without allocating and copying a full-frame temporary buffer.
Prompt for AI agents
In `wallr-core/src/daemon/mod.rs` at lines 1875-1878:
**GIF uploads churn memory**
When a GIF uses the direct-upload playback path, the path allocates a full-frame temporary `Vec` for each GIF frame, decompresses into it, then copies it into the mapped staging buffer. Large animations incur recurring allocation and memory-bandwidth overhead that can undermine smooth playback and exhaust memory.
Decompress directly into the mapped staging buffer without allocating and copying a full-frame temporary buffer.| }; | ||
| command.stdout(Stdio::null()).stderr(Stdio::null()); | ||
| if let Ok(mut child) = command.spawn() { | ||
| let _ = wait_with_timeout(&mut child, RELOAD_TIMEOUT, cmd); |
There was a problem hiding this comment.
🟡 Medium · Timed-out reloads report success
When a configured reload command exceeds RELOAD_TIMEOUT, run_reload_list discards the result of wait_with_timeout and returns Ok(()), so a reload command killed after its timeout is reported as successful and leaves no diagnostic for the operator.
Log timed-out reload commands and return an error or otherwise expose the timeout to the caller while continuing to ignore expected pkill nonzero exits.
Prompt for AI agents
In `wallr-core/src/theme/mod.rs` at line 428:
**Timed-out reloads report success**
When a configured reload command exceeds `RELOAD_TIMEOUT`, `run_reload_list` discards the result of `wait_with_timeout` and returns `Ok(())`, so a reload command killed after its timeout is reported as successful and leaves no diagnostic for the operator.
Log timed-out reload commands and return an error or otherwise expose the timeout to the caller while continuing to ignore expected `pkill` nonzero exits.|
|
||
| fn default_socket() -> String { | ||
| "/tmp/wallr.sock".to_string() | ||
| "$XDG_RUNTIME_DIR/wallr.sock".to_string() |
There was a problem hiding this comment.
🟡 Medium · Upgrade starts a second daemon
When a config omits daemon.socket and a 0.6.4 daemon is still running during an upgrade, a config without an explicit daemon.socket now expands to $XDG_RUNTIME_DIR/wallr.sock, while a still-running 0.6.4 daemon listens on /tmp/wallr.sock. The new CLI checks only the new path, so wallr set starts another daemon and both can control the same outputs.
Preserve the legacy default or detect and migrate an active daemon on the old socket before auto-starting a new one.
Prompt for AI agents
In `wallr-common/src/config.rs` at line 175:
**Upgrade starts a second daemon**
When a config omits `daemon.socket` and a 0.6.4 daemon is still running during an upgrade, a config without an explicit `daemon.socket` now expands to `$XDG_RUNTIME_DIR/wallr.sock`, while a still-running 0.6.4 daemon listens on `/tmp/wallr.sock`. The new CLI checks only the new path, so `wallr set` starts another daemon and both can control the same outputs.
Preserve the legacy default or detect and migrate an active daemon on the old socket before auto-starting a new one.| categories = ["command-line-utilities", "rendering"] | ||
| keywords = ["wallpaper", "wayland", "wgpu", "daemon", "matugen"] | ||
| rust-version = "1.85" | ||
| rust-version = "1.88" |
There was a problem hiding this comment.
⚪ Low · Contributor setup advertises older Rust
When a contributor follows the documented 1.85 minimum, contributors following CONTRIBUTING.md can install Rust 1.85, but Cargo rejects the workspace because the declared minimum is now 1.88, so the documented setup does not build the project.
Update the contributor setup requirement to Rust 1.88 or newer.
Prompt for AI agents
In `Cargo.toml` at line 14:
**Contributor setup advertises older Rust**
When a contributor follows the documented 1.85 minimum, contributors following `CONTRIBUTING.md` can install Rust 1.85, but Cargo rejects the workspace because the declared minimum is now 1.88, so the documented setup does not build the project.
Update the contributor setup requirement to Rust 1.88 or newer.| // Do not skip cached frames without consuming the decoder: the | ||
| // decoded stream position and `next_index` must stay in lockstep. | ||
| // Otherwise a later uncached tail frame is stored under the wrong | ||
| // index after a loop wrap. Re-decoding a cached prefix still uses |
There was a problem hiding this comment.
🟡 Medium · Cached GIF loops decode frames again
When playback wraps an animation whose frames are already fully cached, after a full loop, ensure_upto restarts the GIF decoder and decodes each cached frame again to keep decoder position synchronized, even though playback can return those frames from cache. Fully cached animations therefore spend every loop decoding the animation a second time, increasing CPU use and risking playback stutter.
Use cached frames without decoding them when no uncached tail needs the decoder, and restart/advance the decoder only when an uncached frame must be produced.
Prompt for AI agents
In `wallr-core/src/animated/mod.rs` at lines 311-314:
**Cached GIF loops decode frames again**
When playback wraps an animation whose frames are already fully cached, after a full loop, `ensure_upto` restarts the GIF decoder and decodes each cached frame again to keep decoder position synchronized, even though playback can return those frames from cache. Fully cached animations therefore spend every loop decoding the animation a second time, increasing CPU use and risking playback stutter.
Use cached frames without decoding them when no uncached tail needs the decoder, and restart/advance the decoder only when an uncached frame must be produced.
Bumps MSRV to 1.88, upgrades dependencies to latest compatible releases, adds mimalloc and native CPU release tuning, and continues performance/hardening work across the daemon, renderer, and media pipelines.
Summary by Sourcery
Harden daemon, IPC, configuration, and media pipelines while upgrading the toolchain and dependencies for safer and faster 0.7.0 releases.
New Features:
Bug Fixes:
Enhancements:
Build:
CI:
Documentation:
Tests:
Chores: