1use crate::config::WallrConfig;
2use crate::ipc::{IpcCommand, IpcResponse, start_ipc_server};
3use crate::renderer::Renderer;
4use crate::wallpaper::{SetOptions, WallpaperEngine};
5use notify::{Event, EventKind, RecursiveMode, Watcher};
6use std::path::PathBuf;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use tokio::sync::Mutex;
10
11use raw_window_handle::{
12 DisplayHandle, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle,
13 WaylandDisplayHandle, WaylandWindowHandle, WindowHandle,
14};
15use smithay_client_toolkit::{
16 compositor::{CompositorHandler, CompositorState},
17 delegate_compositor, delegate_layer, delegate_output, delegate_registry, delegate_shm,
18 output::{OutputHandler, OutputState},
19 registry::{ProvidesRegistryState, RegistryState},
20 registry_handlers,
21 shell::WaylandSurface,
22 shell::wlr_layer::{
23 Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
24 LayerSurfaceConfigure,
25 },
26 shm::{Shm, ShmHandler},
27};
28use wayland_client::{
29 Connection, Proxy, QueueHandle,
30 globals::registry_queue_init,
31 protocol::{wl_output, wl_surface},
32};
33
34#[derive(Debug, thiserror::Error)]
35pub enum DaemonError {
36 #[error("daemon already running: {0}")]
37 AlreadyRunning(String),
38 #[error("failed to start daemon: {0}")]
39 StartError(String),
40 #[error("I/O error: {0}")]
41 Io(#[from] std::io::Error),
42 #[error("IPC error: {0}")]
43 Ipc(#[from] crate::ipc::IpcError),
44 #[error("Config error: {0}")]
45 Config(#[from] crate::config::ConfigError),
46 #[error("Wallpaper error: {0}")]
47 Wallpaper(#[from] crate::wallpaper::WallpaperError),
48}
49
50pub struct WaylandWindow {
51 pub display: *mut std::ffi::c_void,
52 pub surface: *mut std::ffi::c_void,
53}
54
55unsafe impl Send for WaylandWindow {}
56unsafe impl Sync for WaylandWindow {}
57
58impl HasWindowHandle for WaylandWindow {
59 fn window_handle(&self) -> Result<WindowHandle<'_>, raw_window_handle::HandleError> {
60 let surface = std::ptr::NonNull::new(self.surface)
61 .ok_or(raw_window_handle::HandleError::Unavailable)?;
62 let handle = WaylandWindowHandle::new(surface);
63 unsafe { Ok(WindowHandle::borrow_raw(RawWindowHandle::Wayland(handle))) }
64 }
65}
66
67impl HasDisplayHandle for WaylandWindow {
68 fn display_handle(&self) -> Result<DisplayHandle<'_>, raw_window_handle::HandleError> {
69 let display = std::ptr::NonNull::new(self.display)
70 .ok_or(raw_window_handle::HandleError::Unavailable)?;
71 let handle = WaylandDisplayHandle::new(display);
72 unsafe { Ok(DisplayHandle::borrow_raw(RawDisplayHandle::Wayland(handle))) }
73 }
74}
75
76struct WaylandState {
77 registry_state: RegistryState,
78 output_state: OutputState,
79 compositor_state: CompositorState,
80 shm: Shm,
81 surfaces: Vec<LayerSurface>,
82 width: u32,
83 height: u32,
84 scale_factor: i32,
85}
86
87impl ProvidesRegistryState for WaylandState {
88 fn registry(&mut self) -> &mut RegistryState {
89 &mut self.registry_state
90 }
91
92 registry_handlers![OutputState,];
93}
94
95impl CompositorHandler for WaylandState {
96 fn scale_factor_changed(
97 &mut self,
98 _conn: &Connection,
99 _qh: &QueueHandle<Self>,
100 _surface: &wl_surface::WlSurface,
101 new_factor: i32,
102 ) {
103 self.scale_factor = new_factor;
104 }
105 fn transform_changed(
106 &mut self,
107 _conn: &Connection,
108 _qh: &QueueHandle<Self>,
109 _surface: &wl_surface::WlSurface,
110 _new_transform: wl_output::Transform,
111 ) {
112 }
113 fn frame(
114 &mut self,
115 _conn: &Connection,
116 _qh: &QueueHandle<Self>,
117 _surface: &wl_surface::WlSurface,
118 _time: u32,
119 ) {
120 }
121 fn surface_enter(
122 &mut self,
123 _conn: &Connection,
124 _qh: &QueueHandle<Self>,
125 _surface: &wl_surface::WlSurface,
126 _output: &wl_output::WlOutput,
127 ) {
128 }
129 fn surface_leave(
130 &mut self,
131 _conn: &Connection,
132 _qh: &QueueHandle<Self>,
133 _surface: &wl_surface::WlSurface,
134 _output: &wl_output::WlOutput,
135 ) {
136 }
137}
138
139impl LayerShellHandler for WaylandState {
140 fn configure(
141 &mut self,
142 _conn: &Connection,
143 _qh: &QueueHandle<Self>,
144 layer: &LayerSurface,
145 configure: LayerSurfaceConfigure,
146 _serial: u32,
147 ) {
148 if configure.new_size.0 > 0 {
149 self.width = configure.new_size.0;
150 }
151 if configure.new_size.1 > 0 {
152 self.height = configure.new_size.1;
153 }
154 layer.commit();
155 }
156
157 fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _layer: &LayerSurface) {}
158}
159
160impl ShmHandler for WaylandState {
161 fn shm_state(&mut self) -> &mut Shm {
162 &mut self.shm
163 }
164}
165
166impl OutputHandler for WaylandState {
167 fn output_state(&mut self) -> &mut OutputState {
168 &mut self.output_state
169 }
170 fn new_output(
171 &mut self,
172 _conn: &Connection,
173 _qh: &QueueHandle<Self>,
174 _output: wl_output::WlOutput,
175 ) {
176 }
177 fn update_output(
178 &mut self,
179 _conn: &Connection,
180 _qh: &QueueHandle<Self>,
181 _output: wl_output::WlOutput,
182 ) {
183 }
184 fn output_destroyed(
185 &mut self,
186 _conn: &Connection,
187 _qh: &QueueHandle<Self>,
188 _output: wl_output::WlOutput,
189 ) {
190 }
191}
192
193delegate_compositor!(WaylandState);
194delegate_layer!(WaylandState);
195delegate_output!(WaylandState);
196delegate_registry!(WaylandState);
197delegate_shm!(WaylandState);
198
199struct RenderState {
200 renderer: std::sync::Arc<Renderer>,
201 surface: &'static wgpu::Surface<'static>,
202 render_lock: std::sync::Arc<std::sync::Mutex<()>>,
206 playback_gen: std::sync::Arc<std::sync::atomic::AtomicU64>,
209 current_bind: Option<wgpu::BindGroup>,
210 current_tex: Option<wgpu::Texture>,
211 width: u32,
212 height: u32,
213 current_width: u32,
214 current_height: u32,
215 format: wgpu::TextureFormat,
216}
217
218struct CommitData {
221 bg_bind: wgpu::BindGroup,
222 new_bind: wgpu::BindGroup,
223 img_width: u32,
224 img_height: u32,
225 old_img_width: u32,
226 old_img_height: u32,
227 format: wgpu::TextureFormat,
228 width: u32,
229 height: u32,
230 animated: Option<crate::animated::AnimatedImage>,
233 generation: u64,
236}
237
238impl RenderState {
239 async fn set_wallpaper(
240 &mut self,
241 path: &std::path::Path,
242 effect: &crate::animation::Effect,
243 duration_ms: u32,
244 ) -> anyhow::Result<()> {
245 let commit = self.commit_wallpaper(path)?;
246 self.spawn_transition(commit, effect, duration_ms);
247 Ok(())
248 }
249
250 fn commit_wallpaper(&mut self, path: &std::path::Path) -> anyhow::Result<CommitData> {
254 use image::ImageReader;
255
256 let animated = crate::animated::AnimatedImage::decode(path)?;
260 let new_img = ImageReader::open(path)?.decode()?;
261 let (new_tex, new_bind) = self.renderer.load_texture(&new_img)?;
262 let img_width = new_img.width();
263 let img_height = new_img.height();
264
265 let old_bind = self.current_bind.take();
266 let (old_img_width, old_img_height) = if old_bind.is_some() {
267 (self.current_width.max(1), self.current_height.max(1))
268 } else {
269 (img_width, img_height)
270 };
271 let bg_bind = old_bind.unwrap_or_else(|| new_bind.clone());
276
277 drop(self.current_tex.take());
278 self.current_tex = Some(new_tex);
279 self.current_bind = Some(new_bind.clone());
280 self.current_width = img_width;
281 self.current_height = img_height;
282
283 let generation = self.playback_gen.fetch_add(1, Ordering::SeqCst) + 1;
284
285 Ok(CommitData {
286 bg_bind,
287 new_bind,
288 img_width,
289 img_height,
290 old_img_width,
291 old_img_height,
292 format: self.format,
293 width: self.width,
294 height: self.height,
295 animated,
296 generation,
297 })
298 }
299
300 fn spawn_transition(
305 &self,
306 commit: CommitData,
307 effect: &crate::animation::Effect,
308 duration_ms: u32,
309 ) {
310 let renderer = self.renderer.clone();
311 let surface: &'static wgpu::Surface<'static> = self.surface;
312 let render_lock = self.render_lock.clone();
313 let playback_gen = self.playback_gen.clone();
314 let effect = effect.clone();
315 drop(tokio::task::spawn_blocking(move || {
316 render_transition(
317 renderer,
318 surface,
319 render_lock,
320 playback_gen,
321 commit,
322 effect,
323 duration_ms,
324 );
325 }));
326 }
327}
328
329fn render_transition(
337 renderer: std::sync::Arc<Renderer>,
338 surface: &'static wgpu::Surface<'static>,
339 render_lock: std::sync::Arc<std::sync::Mutex<()>>,
340 playback_gen: std::sync::Arc<std::sync::atomic::AtomicU64>,
341 commit: CommitData,
342 effect: crate::animation::Effect,
343 duration_ms: u32,
344) {
345 let _guard = render_lock
346 .lock()
347 .unwrap_or_else(|poisoned| poisoned.into_inner());
348
349 let duration = std::time::Duration::from_millis(u64::from(duration_ms.max(1)));
350 let start = std::time::Instant::now();
351 loop {
352 let progress = start.elapsed().as_secs_f32() / duration.as_secs_f32();
353 let uniforms = crate::animation::compute_effect_uniforms(&effect, progress.clamp(0.0, 1.0));
354 let status = renderer.render_frame(crate::renderer::FrameRequest {
355 surface,
356 format: commit.format,
357 bg_bind: &commit.bg_bind,
358 new_bind: &commit.new_bind,
359 effect: &uniforms,
360 width: commit.width,
361 height: commit.height,
362 img_width: commit.img_width,
363 img_height: commit.img_height,
364 old_img_width: commit.old_img_width,
365 old_img_height: commit.old_img_height,
366 });
367 let status = match status {
368 Ok(status) => status,
369 Err(err) => {
370 eprintln!("wallr: transition render failed: {err}");
371 break;
372 }
373 };
374 if progress >= 1.0 || status == crate::renderer::FrameStatus::TimedOut {
375 break;
376 }
377 }
378
379 if let Some(animated) = &commit.animated
383 && playback_gen.load(Ordering::SeqCst) == commit.generation
384 {
385 play_live(&renderer, surface, &commit, animated, &playback_gen);
386 }
387}
388
389fn play_live(
394 renderer: &Renderer,
395 surface: &'static wgpu::Surface<'static>,
396 commit: &CommitData,
397 animated: &crate::animated::AnimatedImage,
398 playback_gen: &std::sync::atomic::AtomicU64,
399) {
400 let (texture, bind) = renderer.create_texture(animated.width, animated.height);
401 renderer.update_texture(
402 &texture,
403 animated.first_frame(),
404 animated.width,
405 animated.height,
406 );
407
408 let mut shown = usize::MAX;
409 let start = std::time::Instant::now();
410 let static_effect = crate::animation::Effect::Fade(crate::animation::FadeParams::default());
411 loop {
412 if playback_gen.load(Ordering::SeqCst) != commit.generation {
413 return;
414 }
415 let index = animated.frame_index_at(start.elapsed());
416 if index != shown {
417 renderer.update_texture(
418 &texture,
419 animated.frame_at(index),
420 animated.width,
421 animated.height,
422 );
423 shown = index;
424 }
425 let uniforms = crate::animation::compute_effect_uniforms(&static_effect, 1.0);
426 let status = renderer.render_frame(crate::renderer::FrameRequest {
427 surface,
428 format: commit.format,
429 bg_bind: &bind,
430 new_bind: &bind,
431 effect: &uniforms,
432 width: commit.width,
433 height: commit.height,
434 img_width: animated.width,
435 img_height: animated.height,
436 old_img_width: animated.width,
437 old_img_height: animated.height,
438 });
439 match status {
440 Ok(crate::renderer::FrameStatus::Presented) => {}
441 _ => return,
445 }
446 }
447}
448
449pub struct Daemon {
450 config: WallrConfig,
451 paused: Arc<AtomicBool>,
452 engine: Arc<Mutex<WallpaperEngine>>,
453}
454
455impl Daemon {
456 pub fn new(config: WallrConfig) -> Result<Self, DaemonError> {
457 let engine = WallpaperEngine::new(config.clone())?;
458 Ok(Self {
459 config,
460 paused: Arc::new(AtomicBool::new(false)),
461 engine: Arc::new(Mutex::new(engine)),
462 })
463 }
464
465 pub async fn start(self) -> Result<(), DaemonError> {
466 let socket_path = crate::config::expand_path(&self.config.daemon.socket);
467 if socket_path.exists() {
468 if tokio::net::UnixStream::connect(&socket_path).await.is_ok() {
469 return Err(DaemonError::AlreadyRunning(
470 socket_path.to_string_lossy().to_string(),
471 ));
472 }
473 let _ = std::fs::remove_file(&socket_path);
474 }
475
476 let renderer = Renderer::new()
477 .await
478 .map_err(|e| DaemonError::StartError(format!("GPU init failed: {e}")))?;
479
480 let conn = Connection::connect_to_env()
481 .map_err(|e| DaemonError::StartError(format!("Failed to connect to Wayland: {e:?}")))?;
482 let backend = conn.backend();
483 let display_ptr = backend.display_ptr() as *mut std::ffi::c_void;
484
485 let (globals, mut event_queue) = registry_queue_init(&conn)
486 .map_err(|e| DaemonError::StartError(format!("registry_queue_init failed: {e:?}")))?;
487 let qh = event_queue.handle();
488
489 let compositor_state = CompositorState::bind(&globals, &qh)
490 .map_err(|e| DaemonError::StartError(format!("compositor bind failed: {e:?}")))?;
491 let layer_shell = LayerShell::bind(&globals, &qh)
492 .map_err(|e| DaemonError::StartError(format!("layer_shell bind failed: {e:?}")))?;
493 let shm = Shm::bind(&globals, &qh)
494 .map_err(|e| DaemonError::StartError(format!("shm bind failed: {e:?}")))?;
495
496 let mut wayland_state = WaylandState {
497 registry_state: RegistryState::new(&globals),
498 output_state: OutputState::new(&globals, &qh),
499 compositor_state,
500 shm,
501 surfaces: Vec::new(),
502 width: 1920,
503 height: 1080,
504 scale_factor: 1,
505 };
506
507 let wl_surface = wayland_state.compositor_state.create_surface(&qh);
508 let layer_surface = layer_shell.create_layer_surface(
509 &qh,
510 wl_surface,
511 Layer::Background,
512 Some("wallr"),
513 None,
514 );
515 layer_surface.set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
516 layer_surface.set_exclusive_zone(-1);
517 layer_surface.set_keyboard_interactivity(KeyboardInteractivity::None);
518 layer_surface.commit();
519
520 event_queue
521 .roundtrip(&mut wayland_state)
522 .map_err(|e| DaemonError::StartError(format!("roundtrip failed: {e:?}")))?;
523 event_queue
524 .roundtrip(&mut wayland_state)
525 .map_err(|e| DaemonError::StartError(format!("roundtrip2 failed: {e:?}")))?;
526
527 let scale_factor = if wayland_state.scale_factor > 0 {
528 wayland_state.scale_factor
529 } else {
530 1
531 };
532 layer_surface.wl_surface().set_buffer_scale(scale_factor);
533
534 let width = wayland_state.width * scale_factor as u32;
535 let height = wayland_state.height * scale_factor as u32;
536
537 let raw_surface = layer_surface.wl_surface().id().as_ptr() as *mut std::ffi::c_void;
538 wayland_state.surfaces.push(layer_surface);
539
540 let window_handle = WaylandWindow {
541 display: display_ptr,
542 surface: raw_surface,
543 };
544
545 let wgpu_surface = renderer
546 .instance
547 .create_surface(&window_handle)
548 .map_err(|e| DaemonError::StartError(format!("wgpu surface creation failed: {e:?}")))?;
549
550 let adapter = renderer
551 .instance
552 .request_adapter(&wgpu::RequestAdapterOptions {
553 compatible_surface: Some(&wgpu_surface),
554 power_preference: wgpu::PowerPreference::HighPerformance,
555 force_fallback_adapter: false,
556 })
557 .await;
558 let surf_format = adapter
559 .as_ref()
560 .map(|a| {
561 let caps = wgpu_surface.get_capabilities(a);
562 caps.formats
563 .into_iter()
564 .next()
565 .unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb)
566 })
567 .unwrap_or(wgpu::TextureFormat::Bgra8UnormSrgb);
568
569 let surf_config = wgpu::SurfaceConfiguration {
570 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
571 format: surf_format,
572 width,
573 height,
574 present_mode: wgpu::PresentMode::Fifo,
575 alpha_mode: wgpu::CompositeAlphaMode::Opaque,
576 view_formats: vec![],
577 desired_maximum_frame_latency: 2,
578 };
579 wgpu_surface.configure(&renderer.device, &surf_config);
580
581 let wgpu_surface: wgpu::Surface<'static> = unsafe { std::mem::transmute(wgpu_surface) };
585 let surface: &'static wgpu::Surface<'static> = Box::leak(Box::new(wgpu_surface));
589
590 let render_state = Arc::new(Mutex::new(RenderState {
591 renderer: std::sync::Arc::new(renderer),
592 surface,
593 render_lock: std::sync::Arc::new(std::sync::Mutex::new(())),
594 playback_gen: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(0)),
595 current_bind: None,
596 current_tex: None,
597 width,
598 height,
599 current_width: 0,
600 current_height: 0,
601 format: surf_format,
602 }));
603
604 {
605 let state_path = dirs::cache_dir()
606 .unwrap_or_else(|| std::path::PathBuf::from("/tmp"))
607 .join("wallr/last_wallpaper");
608 if let Ok(path_str) = std::fs::read_to_string(&state_path) {
609 let p = std::path::Path::new(path_str.trim());
610 if p.exists() {
611 let mut rs = render_state.lock().await;
612 let effect =
613 crate::animation::Effect::Fade(crate::animation::FadeParams::default());
614 let _ = rs.set_wallpaper(p, &effect, 0).await;
615 }
616 }
617 }
618
619 let paused_clone = self.paused.clone();
620 let engine_clone = self.engine.clone();
621 let render_state_clone = render_state.clone();
622
623 start_ipc_server(&socket_path, move |cmd| {
624 let paused = paused_clone.clone();
625 let engine = engine_clone.clone();
626 let rs = render_state_clone.clone();
627 async move {
628 match cmd {
629 IpcCommand::Pause => {
630 paused.store(true, Ordering::SeqCst);
631 IpcResponse {
632 success: true,
633 message: Some("Paused".into()),
634 }
635 }
636 IpcCommand::Resume => {
637 paused.store(false, Ordering::SeqCst);
638 IpcResponse {
639 success: true,
640 message: Some("Resumed".into()),
641 }
642 }
643 IpcCommand::Reload => {
644 let lock = engine.lock().await;
645 match lock.reload() {
646 Ok(_) => IpcResponse {
647 success: true,
648 message: Some("Reloaded".into()),
649 },
650 Err(e) => IpcResponse {
651 success: false,
652 message: Some(e.to_string()),
653 },
654 }
655 }
656 IpcCommand::Preview {
657 path,
658 effect,
659 duration_ms,
660 no_theme,
661 theme_override,
662 monitor,
663 } => {
664 if paused.load(Ordering::SeqCst) {
665 return IpcResponse {
666 success: false,
667 message: Some("Daemon is paused".into()),
668 };
669 }
670 let p = std::path::PathBuf::from(&path);
671 if !p.exists() {
672 return IpcResponse {
673 success: false,
674 message: Some(format!("File not found: {}", path)),
675 };
676 }
677
678 let state_path = dirs::cache_dir()
679 .unwrap_or_else(|| std::path::PathBuf::from("/tmp"))
680 .join("wallr/last_wallpaper");
681 if let Some(parent) = state_path.parent() {
682 let _ = std::fs::create_dir_all(parent);
683 }
684 let _ = std::fs::write(&state_path, &path);
685
686 let effect = effect.unwrap_or_else(|| {
687 crate::animation::Effect::Fade(crate::animation::FadeParams::default())
688 });
689 let duration = duration_ms.unwrap_or(2000);
690
691 let rs_clone = rs.clone();
692 let p_clone = p.clone();
693 let result = tokio::task::spawn_blocking(move || {
694 let rt = tokio::runtime::Handle::current();
695 rt.block_on(async {
696 let mut lock = rs_clone.lock().await;
697 lock.set_wallpaper(&p_clone, &effect, duration).await
698 })
699 })
700 .await;
701
702 match result {
703 Ok(Ok(())) => {
704 let opts = SetOptions {
705 no_theme,
706 theme_provider: theme_override,
707 monitor,
708 };
709 let mut eng = engine.lock().await;
710 match eng.set_wallpaper(&p, &opts).await {
711 Ok(()) => IpcResponse {
712 success: true,
713 message: None,
714 },
715 Err(e) => IpcResponse {
716 success: true,
717 message: Some(format!(
718 "Wallpaper set, but hooks/theme failed: {e}"
719 )),
720 },
721 }
722 }
723 Ok(Err(e)) => IpcResponse {
724 success: false,
725 message: Some(format!("Render failed: {}", e)),
726 },
727 Err(e) => IpcResponse {
728 success: false,
729 message: Some(format!("Task spawn failed: {}", e)),
730 },
731 }
732 }
733 IpcCommand::Stop => {
734 tokio::spawn(async {
735 tokio::time::sleep(std::time::Duration::from_millis(300)).await;
736 std::process::exit(0);
737 });
738 IpcResponse {
739 success: true,
740 message: Some("Stopping".into()),
741 }
742 }
743 IpcCommand::Status => {
744 let state = if paused.load(Ordering::SeqCst) {
745 "paused"
746 } else {
747 "running"
748 };
749 IpcResponse {
750 success: true,
751 message: Some(format!("wallr daemon {}", state)),
752 }
753 }
754 }
755 }
756 })
757 .await?;
758
759 if self.config.watch.enabled
761 && let Some(ref watch_dir) = self.config.watch.dir
762 {
763 let watch_path = crate::config::expand_path(watch_dir);
764 self.start_watcher(watch_path, render_state.clone()).await?;
765 }
766
767 tokio::task::spawn_blocking(move || {
768 loop {
769 if let Err(e) = event_queue.blocking_dispatch(&mut wayland_state) {
770 eprintln!("Wayland dispatch error: {e:?}");
771 break;
772 }
773 }
774 });
775
776 loop {
777 tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
778 }
779 }
780
781 async fn start_watcher(
782 &self,
783 dir: PathBuf,
784 render_state: Arc<Mutex<RenderState>>,
785 ) -> Result<(), DaemonError> {
786 let engine = self.engine.clone();
787 let paused = self.paused.clone();
788 let debounce = crate::config::parse_duration(&self.config.watch.debounce)
789 .unwrap_or(std::time::Duration::from_millis(500));
790
791 let (tx, mut rx) = tokio::sync::mpsc::channel(100);
792
793 let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
794 if let Ok(event) = res
795 && let EventKind::Create(_) = event.kind
796 {
797 for path in event.paths {
798 let _ = tx.blocking_send(path);
799 }
800 }
801 })
802 .map_err(|e| DaemonError::StartError(e.to_string()))?;
803
804 watcher
805 .watch(&dir, RecursiveMode::NonRecursive)
806 .map_err(|e| DaemonError::StartError(e.to_string()))?;
807
808 tokio::spawn(async move {
809 let _watcher = watcher;
810 let mut last: Option<(PathBuf, std::time::Instant)> = None;
811
812 while let Some(path) = rx.recv().await {
813 if paused.load(Ordering::SeqCst) {
814 continue;
815 }
816 if let Some((ref lp, ref lt)) = last
817 && lp == &path
818 && lt.elapsed() < debounce
819 {
820 continue;
821 }
822 let ext = path
823 .extension()
824 .unwrap_or_default()
825 .to_string_lossy()
826 .to_lowercase();
827 if !["jpg", "jpeg", "png", "gif", "webp"].contains(&ext.as_str()) {
828 continue;
829 }
830 last = Some((path.clone(), std::time::Instant::now()));
831
832 let rs = render_state.clone();
833 let eng = engine.clone();
834 let p = path.clone();
835 tokio::spawn(async move {
836 let mut lock = rs.lock().await;
837 let effect =
838 crate::animation::Effect::Fade(crate::animation::FadeParams::default());
839 let _ = lock.set_wallpaper(&p, &effect, 600).await;
840 drop(lock);
841 let opts = SetOptions {
842 no_theme: false,
843 theme_provider: None,
844 monitor: None,
845 };
846 let mut elock = eng.lock().await;
847 let _ = elock.set_wallpaper(&p, &opts).await;
848 });
849 }
850 });
851
852 Ok(())
853 }
854}