wayle-wallpaper 0.1.4

Wallpaper management with cycling and color extraction
Documentation
mod cycle_runner;
mod timer;
mod watcher;

use std::sync::Arc;

use cycle_runner::CyclingTask;
use futures::StreamExt;
use tokio_util::sync::CancellationToken;
use tracing::{error, info, warn};
use wayle_traits::ServiceMonitoring;

use crate::{
    error::Error,
    service::WallpaperService,
    wayland::{OutputEvent, OutputWatcher},
};

impl ServiceMonitoring for WallpaperService {
    type Error = Error;

    async fn start_monitoring(&self) -> Result<(), Self::Error> {
        discover_initial_outputs(self);
        spawn_cycling_task(self);
        Ok(())
    }
}

fn discover_initial_outputs(service: &WallpaperService) {
    let Some(outputs) = OutputWatcher::query_outputs() else {
        warn!("cannot query wayland outputs, no monitors registered");
        return;
    };

    if outputs.is_empty() {
        warn!("No Wayland outputs found");
        return;
    }

    info!(count = outputs.len(), "Discovered Wayland outputs");
    for output in outputs {
        service.register_monitor(&output);
    }
}

fn spawn_cycling_task(service: &WallpaperService) {
    let task = CyclingTask::new(
        service.cycling.clone(),
        service.monitors.clone(),
        service.transition.clone(),
        service.shared_cycle.clone(),
        service.engine_active.clone(),
    );

    let cancellation = service.cancellation_token.clone();

    tokio::spawn(async move {
        task.run(cancellation).await;
    });
}

pub(crate) fn spawn_output_watcher(service: Arc<WallpaperService>) {
    let Some(mut watcher) = OutputWatcher::start() else {
        return;
    };

    let cancellation = service.cancellation_token.clone();

    tokio::spawn(async move {
        run_output_watcher(&mut watcher, &service, cancellation).await;
    });
}

async fn run_output_watcher(
    watcher: &mut OutputWatcher,
    service: &WallpaperService,
    cancellation: CancellationToken,
) {
    loop {
        tokio::select! {
            _ = cancellation.cancelled() => {
                info!("Output watcher cancelled");
                return;
            }

            Some(event) = watcher.events().recv() => {
                match event {
                    OutputEvent::Added(name) => {
                        service.register_monitor(&name);
                    }
                    OutputEvent::Removed(name) => {
                        service.unregister_monitor(&name);
                    }
                }
            }
        }
    }
}

pub(crate) fn spawn_color_extractor(service: Arc<WallpaperService>) {
    let cancellation = service.cancellation_token.clone();

    tokio::spawn(async move {
        let mut monitor_watch = service.monitors.watch();
        let mut color_extractor = service.color_extractor.watch();
        let mut theming_monitor = service.theming_monitor.watch();

        info!("Color extractor task started");

        loop {
            tokio::select! {
                _ = cancellation.cancelled() => {
                    info!("Color extractor cancelled");
                    return;
                }

                _ = monitor_watch.next() => {
                    if let Err(e) = service.extract_colors().await {
                        error!(error = %e, "cannot extract colors");
                    }
                }

                _ = color_extractor.next() => {
                    service.last_extracted_wallpaper.set(None);
                    if let Err(e) = service.extract_colors().await {
                        error!(error = %e, "cannot extract colors");
                    }
                }

                _ = theming_monitor.next() => {
                    service.last_extracted_wallpaper.set(None);
                    if let Err(e) = service.extract_colors().await {
                        error!(error = %e, "cannot extract colors");
                    }
                }
            }
        }
    });
}