Wake the event loop when wallpaper decoding completes

This commit is contained in:
Samuel Onoja committed 2026-09-27 07:06:15 +01:00
1 parent 6dd132a4a5
commit b469929847
4 files changed
+49 -12

No files matched your search

+7 -7
View File
@@ -90,12 +90,6 @@ fn main() -> Result<(), Box<dyn Error>> {
event_loop.handle().insert_source( event_loop.handle().insert_source(
Timer::from_duration(Duration::from_millis(150)), Timer::from_duration(Duration::from_millis(150)),
move |_, _, state: &mut Ferese| { move |_, _, state: &mut Ferese| {
// Decoders finish on a worker even when an idle DRM output has no
// pending frame. Wake rendering once, never continuously for wallpaper.
if state.wallpaper.poll() {
state.backdrop_generation = state.backdrop_generation.wrapping_add(1);
backends::direct::render_all(state);
}
if let Some(result) = monitor if let Some(result) = monitor
.as_mut() .as_mut()
.and_then(|monitor| monitor.poll(std::time::Instant::now())) .and_then(|monitor| monitor.poll(std::time::Instant::now()))
@@ -124,9 +118,15 @@ fn main() -> Result<(), Box<dyn Error>> {
}, },
)?; )?;
let result = event_loop.run(None, &mut state, |state| { let result = event_loop.run(None, &mut state, |state| {
// The decoder wakes the loop after publishing its result, including
// when no output has a pending frame.
let wallpaper_changed = state.wallpaper.poll();
if wallpaper_changed {
state.backdrop_generation = state.backdrop_generation.wrapping_add(1);
}
// All input/Wayland callbacks have returned, releasing seat locks. // All input/Wayland callbacks have returned, releasing seat locks.
// Coalesce cursor changes and redraw here, never inside cursor_image. // Coalesce cursor changes and redraw here, never inside cursor_image.
if std::mem::take(&mut state.cursor_redraw_pending) { if std::mem::take(&mut state.cursor_redraw_pending) || wallpaper_changed {
backends::direct::render_all(state); backends::direct::render_all(state);
} }
// Registry/sync/configure replies must not depend on a submitted // Registry/sync/configure replies must not depend on a submitted
+4 -1
View File
@@ -602,7 +602,10 @@ impl Ferese {
resize_transactions: HashMap::new(), resize_transactions: HashMap::new(),
resize_snapshots: HashMap::new(), resize_snapshots: HashMap::new(),
nested_backend: None, nested_backend: None,
wallpaper: crate::wallpaper::WallpaperState::new(config.wallpaper), wallpaper: crate::wallpaper::WallpaperState::with_wakeup(
config.wallpaper,
Some(event_loop.get_signal()),
),
maximized_windows: HashSet::new(), maximized_windows: HashSet::new(),
maximized_column_widths: HashMap::new(), maximized_column_widths: HashMap::new(),
window_stack: crate::stacking::WindowStack::default(), window_stack: crate::stacking::WindowStack::default(),
+38 -1
View File
@@ -67,15 +67,25 @@ pub(crate) struct WallpaperState {
pixels: Option<image::RgbaImage>, pixels: Option<image::RgbaImage>,
textures: HashMap<ErasedContextId, WallpaperTexture>, textures: HashMap<ErasedContextId, WallpaperTexture>,
upload_retries: HashMap<ErasedContextId, Instant>, upload_retries: HashMap<ErasedContextId, Instant>,
wakeup: Option<smithay::reexports::calloop::LoopSignal>,
} }
impl WallpaperState { impl WallpaperState {
#[cfg(test)]
pub fn new(config: WallpaperConfig) -> Self { pub fn new(config: WallpaperConfig) -> Self {
Self::with_wakeup(config, None)
}
pub fn with_wakeup(
config: WallpaperConfig,
wakeup: Option<smithay::reexports::calloop::LoopSignal>,
) -> Self {
let retained = config.clone(); let retained = config.clone();
let (sender, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
let owned = config.path.as_ref().is_some_and(|path| path.is_file()); let owned = config.path.as_ref().is_some_and(|path| path.is_file());
if owned { if owned {
let path = config.path.unwrap(); let path = config.path.unwrap();
let wakeup = wakeup.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
let load = || -> Result<image::RgbaImage, String> { let load = || -> Result<image::RgbaImage, String> {
let (width, height) = let (width, height) =
@@ -88,6 +98,9 @@ impl WallpaperState {
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
}; };
let _ = sender.send(load()); let _ = sender.send(load());
if let Some(wakeup) = wakeup {
wakeup.wakeup();
}
}); });
} }
Self { Self {
@@ -100,6 +113,7 @@ impl WallpaperState {
pixels: None, pixels: None,
textures: HashMap::new(), textures: HashMap::new(),
upload_retries: HashMap::new(), upload_retries: HashMap::new(),
wakeup,
} }
} }
@@ -126,7 +140,7 @@ impl WallpaperState {
tracing::warn!("new wallpaper is unavailable; retaining previous image"); tracing::warn!("new wallpaper is unavailable; retaining previous image");
return; return;
} }
let mut replacement = Self::new(config); let mut replacement = Self::with_wakeup(config, self.wakeup.clone());
self.config = replacement.config; self.config = replacement.config;
self.mode = replacement.mode; self.mode = replacement.mode;
self.owned = replacement.owned; self.owned = replacement.owned;
@@ -296,6 +310,29 @@ fn image_geometry(
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn decoder_wakes_an_idle_event_loop() {
use smithay::reexports::calloop::{EventLoop, timer::Timer};
let mut event_loop = EventLoop::<WallpaperState>::try_new().unwrap();
event_loop
.handle()
.insert_source(Timer::from_duration(Duration::from_secs(5)), |_, _, _| {
panic!("wallpaper decoder failed to wake the idle event loop");
})
.unwrap();
let signal = event_loop.get_signal();
let mut state =
WallpaperState::with_wakeup(WallpaperConfig::default(), Some(signal.clone()));
event_loop
.run(None, &mut state, |state| {
if state.poll() {
assert!(state.pixels.is_some());
signal.stop();
}
})
.unwrap();
}
#[test] #[test]
fn failed_uploads_back_off_per_context_and_reset_for_new_pixels() { fn failed_uploads_back_off_per_context_and_reset_for_new_pixels() {
use smithay::backend::renderer::ContextId; use smithay::backend::renderer::ContextId;
-3
View File
@@ -1068,9 +1068,6 @@ fn output_elements(
); );
return elements; return elements;
} }
if state.wallpaper.poll() {
state.backdrop_generation = state.backdrop_generation.wrapping_add(1);
}
let Some(output_geometry) = state.space.output_geometry(output) else { let Some(output_geometry) = state.space.output_geometry(output) else {
return Vec::new(); return Vec::new();
}; };