From a4eee2186cf55a3fa5c834b34030d9d49e0d2e48 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 22 May 2023 17:26:14 +0200 Subject: [PATCH] feat: partial implementation of wallpaper settings Will take some time to refactor the rest --- Cargo.lock | 65 ++++++++--- Cargo.toml | 12 +- app/Cargo.toml | 6 +- app/src/app.rs | 20 ++-- app/src/main.rs | 4 +- app/src/pages/desktop/mod.rs | 8 +- app/src/pages/desktop/wallpaper.rs | 170 +++++++++++++++++++++++------ app/src/pages/mod.rs | 1 + app/src/widget/mod.rs | 10 +- justfile | 2 +- page/src/lib.rs | 9 ++ pages/desktop/Cargo.toml | 17 +++ pages/desktop/src/lib.rs | 1 + pages/desktop/src/wallpaper.rs | 130 ++++++++++++++++++++++ 14 files changed, 375 insertions(+), 80 deletions(-) create mode 100644 pages/desktop/Cargo.toml create mode 100644 pages/desktop/src/lib.rs create mode 100644 pages/desktop/src/wallpaper.rs diff --git a/Cargo.lock b/Cargo.lock index ecf64b9..094c1a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -715,12 +715,25 @@ dependencies = [ "libc", ] +[[package]] +name = "cosmic-bg-config" +version = "0.1.0" +source = "git+https://github.com/pop-os/cosmic-bg#c9fec966262a9a3572e662b4e98f647f4807ba33" +dependencies = [ + "cosmic-config", + "derive_setters", + "image", + "ron", + "serde", +] + [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "atomicwrites", + "calloop", "cosmic-config-derive", "dirs 5.0.1", "iced_futures", @@ -732,7 +745,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "quote", "syn 1.0.109", @@ -741,7 +754,7 @@ dependencies = [ [[package]] name = "cosmic-panel-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-panel#adbe256782887cb0a15b9b421a477e70d1ca1b80" +source = "git+https://github.com/pop-os/cosmic-panel?branch=settings_jammy#a71a4cba13184f22ba8874c910b20e99f60871c0" dependencies = [ "anyhow", "cosmic-config", @@ -760,6 +773,7 @@ dependencies = [ "async-channel", "color-eyre", "cosmic-panel-config", + "cosmic-settings-desktop", "cosmic-settings-page", "cosmic-settings-system", "cosmic-settings-time", @@ -779,6 +793,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "cosmic-settings-desktop" +version = "0.1.0" +dependencies = [ + "cosmic-bg-config", + "cosmic-config", + "dirs 5.0.1", + "freedesktop-icons", + "futures-lite", + "image", + "rayon", + "tokio", + "tracing", +] + [[package]] name = "cosmic-settings-page" version = "0.1.0" @@ -836,7 +865,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "anyhow", "cosmic-config", @@ -2015,7 +2044,7 @@ dependencies = [ [[package]] name = "iced" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "iced_accessibility", "iced_core", @@ -2031,7 +2060,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "accesskit", "accesskit_unix", @@ -2040,7 +2069,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.9.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "bitflags 1.3.2", "iced_accessibility", @@ -2055,7 +2084,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.6.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "futures", "iced_core", @@ -2068,7 +2097,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.8.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2085,7 +2114,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2097,7 +2126,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "iced_accessibility", "iced_core", @@ -2109,7 +2138,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "enum-repr", "float-cmp", @@ -2130,7 +2159,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.8.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "iced_core", "once_cell", @@ -2140,7 +2169,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "bytemuck", "cosmic-text", @@ -2158,7 +2187,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.10.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2180,7 +2209,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "iced_renderer", "iced_runtime", @@ -2195,7 +2224,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.9.1" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "iced_graphics", "iced_runtime", @@ -2487,7 +2516,7 @@ checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420" +source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04" dependencies = [ "apply", "cosmic-config", diff --git a/Cargo.toml b/Cargo.toml index a215df0..e93a354 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,20 @@ members = ["app", "page", "pages/*"] default-members = ["app"] +[workspace.dependencies.iced_core] +git = "https://github.com/pop-os/libcosmic" + [workspace.dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic" -rev = "f06a81c" default-features = false features = ["debug", "wayland", "tokio"] [workspace.dependencies.cosmic-config] git = "https://github.com/pop-os/libcosmic" -rev = "f06a81c" \ No newline at end of file + +[workspace.dependencies.cosmic-bg-config] +git = "https://github.com/pop-os/cosmic-bg" + +[workspace.dependencies.cosmic-panel-config] +git = "https://github.com/pop-os/cosmic-panel" +branch = "settings_jammy" \ No newline at end of file diff --git a/app/Cargo.toml b/app/Cargo.toml index 1f28f33..09ecc34 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -9,6 +9,7 @@ rust-version = "1.65.0" apply = "0.3.0" async-channel = "1.8.0" color-eyre = "0.6.2" +cosmic-settings-desktop = { path = "../pages/desktop" } cosmic-settings-page = { path = "../page" } cosmic-settings-system = { path = "../pages/system" } cosmic-settings-time = { path = "../pages/time" } @@ -23,7 +24,8 @@ rust-embed = "6.6.1" slotmap = "1.0.6" tokio = "1.28.2" downcast-rs = "1.2.0" -cosmic-panel-config = { git = "https://github.com/pop-os/cosmic-panel" } +# TODO: migrate this dependency to the pages/desktop crate. +cosmic-panel-config = { workspace = true } tracing = "0.1.37" tracing-subscriber = { version = "0.3.17", features = ["env-filter"]} @@ -33,6 +35,4 @@ features = ["fluent-system", "desktop-requester"] [profile.release] opt-level = "s" -overflow-checks = true lto = "thin" -incremental = false diff --git a/app/src/app.rs b/app/src/app.rs index b038305..c978d91 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -235,19 +235,16 @@ impl Application for SettingsApp { Message::None | Message::Search(_) => {} Message::PageMessage(message) => match message { crate::pages::Message::About(message) => { - if let Some(page) = self.pages.page_mut::() { - page.update(message); - } + page::update!(self.pages, message, system::about::Page); } crate::pages::Message::DateAndTime(message) => { - if let Some(page) = self.pages.page_mut::() { - page.update(message); - } + page::update!(self.pages, message, time::date::Page); } crate::pages::Message::Desktop(message) => { - if let Some(page) = self.pages.page_mut::() { - page.update(message); - } + page::update!(self.pages, message, desktop::Page); + } + crate::pages::Message::DesktopWallpaper(message) => { + page::update!(self.pages, message, desktop::wallpaper::Page); } crate::pages::Message::External { .. } => { todo!("external plugins not supported yet"); @@ -457,7 +454,10 @@ impl SettingsApp { ); } - settings::view_column(column_widgets).into() + settings::view_column(column_widgets) + .max_width(683) + .padding(0) + .into() } fn search_changed(&mut self, phrase: String) { diff --git a/app/src/main.rs b/app/src/main.rs index 5a03b21..3150a25 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -52,8 +52,8 @@ pub fn main() -> color_eyre::Result<()> { let localizer = crate::localize::localizer(); let requested_languages = DesktopLanguageRequester::requested_languages(); - if let Err(error) = localizer.select(&requested_languages) { - eprintln!("error while loading fluent localizations: {error}"); + if let Err(why) = localizer.select(&requested_languages) { + tracing::error!(%why, "error while loading fluent localizations"); } cosmic::settings::set_default_icon_theme("Pop"); diff --git a/app/src/pages/desktop/mod.rs b/app/src/pages/desktop/mod.rs index e1ee28c..9f7100f 100644 --- a/app/src/pages/desktop/mod.rs +++ b/app/src/pages/desktop/mod.rs @@ -19,8 +19,6 @@ pub struct Page { pub show_applications_button: bool, pub show_minimize_button: bool, pub show_maximize_button: bool, - pub slideshow: bool, - pub same_background: bool, } impl page::Page for Page { @@ -39,10 +37,8 @@ impl page::AutoBind for Page { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] pub enum Message { - Slideshow(bool), - SameBackground(bool), ShowWorkspacesButton(bool), ShowApplicationsButton(bool), ShowMinimizeButton(bool), @@ -53,12 +49,10 @@ pub enum Message { impl Page { pub fn update(&mut self, message: Message) { match message { - Message::SameBackground(value) => self.same_background = value, Message::ShowApplicationsButton(value) => self.show_applications_button = value, Message::ShowMaximizeButton(value) => self.show_maximize_button = value, Message::ShowMinimizeButton(value) => self.show_minimize_button = value, Message::ShowWorkspacesButton(value) => self.show_workspaces_button = value, - Message::Slideshow(value) => self.slideshow = value, Message::TopLeftHotCorner(value) => self.top_left_hot_corner = value, } } diff --git a/app/src/pages/desktop/wallpaper.rs b/app/src/pages/desktop/wallpaper.rs index 497106c..ee618f0 100644 --- a/app/src/pages/desktop/wallpaper.rs +++ b/app/src/pages/desktop/wallpaper.rs @@ -1,21 +1,74 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use super::Message; +use std::{path::PathBuf, time::Instant}; + use apply::Apply; use cosmic::{ - iced::widget::{column, container, horizontal_space, image, row, svg, text}, + iced::alignment::Horizontal, + iced::widget::{column, container, horizontal_space, row, text}, iced::Length, + iced_runtime::core::image::Handle as ImageHandle, theme, widget::{list_column, settings, toggler}, Element, }; +use cosmic_settings_desktop::wallpaper::{self, Entry, Output}; use cosmic_settings_page::Section; use cosmic_settings_page::{self as page, section}; -use slotmap::SlotMap; +use slotmap::{DefaultKey, SecondaryMap, SlotMap}; -#[derive(Default)] -pub struct Page; +#[derive(Clone, Debug)] +pub enum Message { + SameBackground(bool), + Select(DefaultKey), + Slideshow(bool), + Update((wallpaper::Config, Context)), +} + +pub struct Page { + pub config: wallpaper::Config, + pub selection: Context, + pub same_background: bool, + pub slideshow: bool, +} + +impl Default for Page { + fn default() -> Self { + Page { + config: wallpaper::Config::default(), + selection: Context::default(), + same_background: true, + slideshow: false, + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct Context { + active: DefaultKey, + handles: SlotMap, + paths: SecondaryMap, +} + +impl Page { + pub fn update(&mut self, message: Message) { + match message { + Message::SameBackground(value) => self.same_background = value, + Message::Select(id) => { + if let Some(path) = self.selection.paths.get(id) { + wallpaper::set(&mut self.config, Entry::new(Output::All, path.to_owned())); + self.selection.active = id; + } + } + Message::Slideshow(value) => self.slideshow = value, + Message::Update((config, selection)) => { + self.config = config; + self.selection = selection; + } + } + } +} impl page::Page for Page { fn content( @@ -30,6 +83,32 @@ impl page::Page for Page { .title(fl!("wallpaper")) .description(fl!("wallpaper", "desc")) } + + fn load(&self, _page: page::Entity) -> Option> { + Some(Box::pin(async move { + let config = wallpaper::config(); + + let mut backgrounds = wallpaper::load_each_from_path("/usr/share/backgrounds".into()); + let mut update = Context::default(); + + let start = Instant::now(); + + while let Some((path, image)) = backgrounds.recv().await { + let handle = + ImageHandle::from_pixels(image.width(), image.height(), image.into_vec()); + + let id = update.handles.insert(handle); + update.paths.insert(id, path); + } + + tracing::info!( + "loaded wallpapers in {:?}", + Instant::now().duration_since(start) + ); + + crate::pages::Message::DesktopWallpaper(Message::Update((config, update))) + })) + } } impl page::AutoBind for Page {} @@ -42,57 +121,84 @@ pub fn settings() -> Section { fl!("wallpaper", "slide"), fl!("wallpaper", "change"), ]) - .view::(|binder, _page, section| { - let desktop = binder - .page::() - .expect("desktop page not found"); + .view::(|_binder, page, section| { let descriptions = §ion.descriptions; - let image_paths: Vec = Vec::new(); - let mut image_column = Vec::with_capacity(image_paths.len() / 4); - for chunk in image_paths.chunks(4) { - let mut image_row = Vec::with_capacity(chunk.len()); - for image_path in chunk.iter() { - image_row.push(if image_path.ends_with(".svg") { - svg(svg::Handle::from_path(image_path)) - .width(Length::Fixed(150.)) - .into() - } else { - image(image_path).width(Length::Fixed(150.)).into() - }); + let mut image_column = Vec::with_capacity(page.selection.handles.len() / 4); + let mut image_handles = page.selection.handles.iter(); + + while let Some((id, handle)) = image_handles.next() { + let mut image_row = Vec::with_capacity(4); + + image_row.push(wallpaper_button(handle, id)); + + for (id, handle) in image_handles.by_ref().take(3) { + image_row.push(wallpaper_button(handle, id)); } + image_column.push(row(image_row).spacing(16).into()); } - let children = vec![ - row!( + let mut children = Vec::with_capacity(3); + + if let Some(image) = page.selection.handles.get(page.selection.active) { + let display_preview = row!( horizontal_space(Length::Fill), container( - image("/usr/share/backgrounds/pop/kate-hazen-COSMIC-desktop-wallpaper.png") - .width(Length::Fixed(300.)) + cosmic::iced::widget::image(image.clone()).width(Length::Fixed(300.0)) ) .padding(4) .style(theme::Container::Background), horizontal_space(Length::Fill), ) - .into(), + .padding([0, 0, 8, 0]) + .into(); + + children.push(display_preview); + } + + children.push( + cosmic::widget::text("All Displays") + .horizontal_alignment(Horizontal::Center) + .width(Length::Fill) + .apply(cosmic::iced::widget::container) + .width(Length::Fill) + .padding([0, 0, 16, 0]) + .into(), + ); + + children.push( list_column() .add(settings::item( &descriptions[0], - toggler(None, desktop.same_background, Message::SameBackground), + toggler(None, page.same_background, Message::SameBackground), )) .add(settings::item(&descriptions[1], text("TODO"))) .add(settings::item( &descriptions[2], - toggler(None, desktop.slideshow, Message::Slideshow), + toggler(None, page.slideshow, Message::Slideshow), )) .into(), - column(image_column).spacing(16).into(), - ]; + ); - settings::view_column(children) + children.push(column(image_column).spacing(12).padding(0).into()); + + cosmic::iced::widget::column(children) + .spacing(22) .padding(0) + .max_width(683) .apply(Element::from) - .map(crate::pages::Message::Desktop) + .map(crate::pages::Message::DesktopWallpaper) }) } + +pub fn wallpaper_button(handle: &ImageHandle, id: DefaultKey) -> Element { + let image = cosmic::iced::widget::image(handle.clone()).apply(cosmic::iced::Element::from); + + cosmic::iced::widget::button(image) + .width(Length::Fixed(158.0)) + .height(Length::Fixed(105.0)) + .style(cosmic::theme::Button::Transparent) + .on_press(Message::Select(id)) + .into() +} diff --git a/app/src/pages/mod.rs b/app/src/pages/mod.rs index 0df3529..6cdaf7e 100644 --- a/app/src/pages/mod.rs +++ b/app/src/pages/mod.rs @@ -15,6 +15,7 @@ pub enum Message { DateAndTime(time::date::Message), Desktop(desktop::Message), Panel(desktop::panel::Message), + DesktopWallpaper(desktop::wallpaper::Message), External { id: String, message: Vec }, Page(Entity), } diff --git a/app/src/widget/mod.rs b/app/src/widget/mod.rs index 5bf2180..4461809 100644 --- a/app/src/widget/mod.rs +++ b/app/src/widget/mod.rs @@ -25,7 +25,7 @@ pub fn search_header( column_children.push( text(parent_meta.title.as_str()) - .size(16) + .size(14) .apply(container) .padding([0, 0, 0, 6]) .into(), @@ -47,7 +47,7 @@ pub fn search_header( #[must_use] pub fn search_page_link(title: &str) -> Button { text(title) - .size(32) + .size(24) .horizontal_alignment(iced::alignment::Horizontal::Left) .apply(button) .style(cosmic::theme::Button::Link) @@ -56,7 +56,7 @@ pub fn search_page_link(title: &str) -> Button(page: &page::Info) -> Element { row!( - text(page.title.as_str()).size(32), + text(page.title.as_str()).size(24), horizontal_space(Length::Fill) ) .into() @@ -71,13 +71,13 @@ pub fn parent_page_button<'a, Message: Clone + 'static>( column!( button(row!( icon("go-previous-symbolic", 20).style(theme::Svg::SymbolicLink), - text(parent.title.as_str()).size(20), + text(parent.title.as_str()).size(14), )) .padding(0) .style(theme::Button::Link) .on_press(on_press), row!( - text(sub_page.title.as_str()).size(32), + text(sub_page.title.as_str()).size(24), horizontal_space(Length::Fill), ) .align_items(iced::alignment::Alignment::Center), diff --git a/justfile b/justfile index d68f25d..9275690 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,7 @@ name := 'cosmic-settings' appid := 'com.system76.CosmicSettings' -# Use the lld linker if it is available. +# Use lld linker if available ld-args := if `which lld || true` != '' { '-C link-arg=-fuse-ld=lld -C link-arg=-Wl,--build-id=sha1' } else { diff --git a/page/src/lib.rs b/page/src/lib.rs index d09540c..8a539ea 100644 --- a/page/src/lib.rs +++ b/page/src/lib.rs @@ -83,3 +83,12 @@ impl Info { } } } + +#[macro_export] +macro_rules! update { + ($binder:expr, $message:expr, $page:ty) => {{ + if let Some(page) = $binder.page_mut::<$page>() { + page.update($message); + } + }}; +} diff --git a/pages/desktop/Cargo.toml b/pages/desktop/Cargo.toml new file mode 100644 index 0000000..40a8f83 --- /dev/null +++ b/pages/desktop/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cosmic-settings-desktop" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmic-bg-config = { workspace = true } +cosmic-config = { workspace = true } +dirs = "5.0.1" +freedesktop-icons = "0.2.3" +futures-lite = "1.13.0" +image = "0.24.6" +rayon = "1.7.0" +tokio = { version = "1.28.0", features = ["sync"] } +tracing = "0.1.37" \ No newline at end of file diff --git a/pages/desktop/src/lib.rs b/pages/desktop/src/lib.rs new file mode 100644 index 0000000..5a20f26 --- /dev/null +++ b/pages/desktop/src/lib.rs @@ -0,0 +1 @@ +pub mod wallpaper; diff --git a/pages/desktop/src/wallpaper.rs b/pages/desktop/src/wallpaper.rs new file mode 100644 index 0000000..bf615b8 --- /dev/null +++ b/pages/desktop/src/wallpaper.rs @@ -0,0 +1,130 @@ +pub use cosmic_bg_config::{Config, Entry, Output}; +use image::RgbaImage; +use std::{ + collections::hash_map::DefaultHasher, + fs::DirEntry, + hash::{Hash, Hasher}, + path::{Path, PathBuf}, + sync::Arc, +}; +use tokio::sync::mpsc::{self, Receiver}; + +pub fn config() -> Config { + let helper = Config::helper().expect("failed to get helper for cosmic bg config"); + + match Config::load(&helper) { + Ok(conf) => conf, + Err(why) => { + tracing::warn!(?why, "Config file error, falling back to defaults"); + Config::default() + } + } +} + +pub fn set(config: &mut Config, entry: Entry) { + if let Ok(context) = Config::helper() { + tracing::info!( + "setting wallpaper for {} to {}", + entry.output.to_string(), + entry.source.display() + ); + if let Err(why) = config.set_entry(&context, entry) { + tracing::error!(?why, "failed to set background"); + } + } +} + +/// Path to directory where wallpaper thumbnails are stored. +#[must_use] +pub fn cache_dir() -> Option { + dirs::cache_dir().map(|path| { + let cache = path.join("cosmic-settings/wallpapers"); + let _res = std::fs::create_dir_all(&cache); + cache + }) +} + +/// Loads wallpapers in parallel by spawning tasks with a rayon thread pool. +#[must_use] +pub fn load_each_from_path(path: PathBuf) -> Receiver<(PathBuf, RgbaImage)> { + let cache_dir = Arc::new(cache_dir()); + + let (tx, rx) = mpsc::channel(1); + + tokio::task::spawn_blocking(move || { + let mut paths = vec![path]; + + while let Some(path) = paths.pop() { + if let Ok(dir) = path.read_dir() { + for entry in dir.filter_map(Result::ok) { + let Ok(file_type) = entry.file_type() else { + continue + }; + + let path = entry.path(); + + if file_type.is_dir() { + paths.push(path); + } else if file_type.is_file() { + let tx = tx.clone(); + let cache_dir = cache_dir.clone(); + rayon::spawn_fifo(move || { + let thumbnail = + load_thumbnail(cache_dir.as_deref(), &path, &entry, 300, 169); + if let Some(image) = thumbnail { + let _res = tx.blocking_send((path, image)); + } + }); + } + } + } + } + }); + + rx +} + +/// Generates and caches the thumbnail of a wallpaper. +/// +/// +/// Caching reduces time required to load a wallpaper by 99%. +#[must_use] +pub fn load_thumbnail( + cache_dir: Option<&Path>, + path: &Path, + entry: &DirEntry, + width: u32, + height: u32, +) -> Option { + if let Some(cache_dir) = cache_dir { + if let Ok(ctime) = entry.metadata().and_then(|meta| meta.created()) { + // Search for thumbnail by a unique hash string. + let mut hasher = DefaultHasher::new(); + path.hash(&mut hasher); + ctime.hash(&mut hasher); + let hash = hasher.finish(); + + let thumbnail_path = cache_dir.join(format!("{hash:x}.png")); + + // Load image from thumbnail if it exists and can be opened. + if thumbnail_path.exists() { + if let Ok(image) = image::open(&thumbnail_path) { + return Some(image.into_rgba8()); + } + } + + // Create new thumbnail and save it if not. + return image::open(path).ok().map(|mut image| { + image = image.thumbnail_exact(width, height); + let _res = image.save(&thumbnail_path); + image.into_rgba8() + }); + } + } + + // Generate thumbnail from wallpaper without saving it + image::open(path).ok().map(|mut image| { + image = image.thumbnail_exact(width, height); + image.into_rgba8() + }) +}