diff --git a/Cargo.lock b/Cargo.lock index 7cf9bca..e7e1c8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,27 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "bytecheck" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bytemuck" version = "1.13.0" @@ -397,12 +418,15 @@ name = "cosmic-settings" version = "0.1.0" dependencies = [ "apply", + "bytecheck", "derive_setters", + "dirs", "i18n-embed", "i18n-embed-fl", "libcosmic", "once_cell", "regex", + "rkyv", "rust-embed", "slotmap", ] @@ -2545,6 +2569,26 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74605f360ce573babfe43964cbe520294dcb081afbf8c108fc6e23036b4da2df" +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quick-xml" version = "0.23.1" @@ -2719,6 +2763,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "rend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +dependencies = [ + "bytecheck", +] + [[package]] name = "renderdoc-sys" version = "0.7.1" @@ -2750,6 +2803,31 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rkyv" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +dependencies = [ + "bytecheck", + "hashbrown", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ron" version = "0.8.0" @@ -2901,6 +2979,12 @@ dependencies = [ "tiny-skia 0.7.0", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "self_cell" version = "0.10.2" diff --git a/Cargo.toml b/Cargo.toml index c739218..0201dce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,9 @@ once_cell = "1.17.0" regex = "1.7.1" rust-embed = "6.4.2" slotmap = "1.0.6" +dirs = "4.0.0" +rkyv = { version = "0.7.39", features = ["validation"]} +bytecheck = "0.6.9" [dependencies.i18n-embed] version = "0.13.8" @@ -22,4 +25,4 @@ features = ["fluent-system", "desktop-requester"] git = "https://github.com/pop-os/libcosmic" branch = "settings-improv" default-features = false -features = ["debug", "winit", "dyrend"] \ No newline at end of file +features = ["debug", "winit", "dyrend"] diff --git a/src/app.rs b/src/app.rs index c8955e5..0882b3b 100644 --- a/src/app.rs +++ b/src/app.rs @@ -18,6 +18,7 @@ use cosmic::{ }; use crate::{ + config::{self, Config}, page::{self, desktop, section, sound, system, time, Page}, widget::{page_title, parent_page_button, search_header, sub_page_button}, }; @@ -27,6 +28,7 @@ use crate::{ pub struct SettingsApp { pub active_page: page::Entity, pub config: Config, + pub config_path: config::PathManager, pub debug: bool, pub is_condensed: bool, pub nav_bar_toggled_condensed: bool, @@ -43,11 +45,6 @@ pub struct SettingsApp { pub window_width: u32, } -#[derive(Debug, Default)] -pub struct Config { - nav_page: &'static str, -} - #[allow(dead_code)] #[derive(Clone, Debug)] pub enum Message { @@ -73,11 +70,12 @@ impl Application for SettingsApp { type Theme = Theme; fn new(_flags: ()) -> (Self, Command) { + let mut config_path = config::PathManager::new(); + let mut app = SettingsApp { active_page: page::Entity::default(), - config: Config { - nav_page: "desktop", - }, + config: config_path.config("main", Config::deserialize), + config_path, debug: false, is_condensed: false, nav_bar: segmented_button::Model::default(), @@ -119,7 +117,12 @@ impl Application for SettingsApp { // app.insert_page::(); // app.insert_page::(); - app.activate_navbar(app.active_page); + for (id, info) in app.pages.pages.iter() { + if info.id == &*app.config.active_page { + app.activate_page(id); + break; + } + } (app, Command::none()) } @@ -299,8 +302,15 @@ impl SettingsApp { /// Activates a page. fn activate_page(&mut self, page: page::Entity) { self.nav_bar_toggled_condensed = false; + let current_page = self.active_page; self.active_page = page; + if current_page != page { + self.config.active_page = Box::from(self.pages.pages[page].id); + self.config_path + .config("main", |path| self.config.serialize(path)); + } + self.search_clear(); self.search.state = search::State::Inactive; self.activate_navbar(page); @@ -320,13 +330,8 @@ impl SettingsApp { /// Adds a main page to the settings application. fn insert_page(&mut self) -> page::Insert { let id = self.pages.register::

().id(); - self.navbar_insert(id); - if P::PERSISTENT_ID == self.config.nav_page { - self.active_page = id; - } - page::Insert { model: &mut self.pages, id, diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..7a8360a --- /dev/null +++ b/src/config.rs @@ -0,0 +1,100 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: GPL-3.0-only + +mod path { + use std::path::Path; + + #[must_use] + + pub struct Manager { + base: String, + } + + impl Manager { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let mut base = dirs::config_dir() + .expect("XDG config directory missing") + .join("cosmic-settings") + .into_os_string() + .into_string() + .expect("XDG config path is not UTF-8"); + + base.push('/'); + + if !Path::new(&base).exists() { + let _res = std::fs::create_dir_all(&base); + } + + Self { base } + } + + pub fn config(&mut self, page: &str, with: impl Fn(&Path) -> T) -> T { + let truncate_length = self.base.len(); + + self.base.push_str(page); + self.base.push_str(".config"); + + let output = with(Path::new(&self.base)); + self.base.truncate(truncate_length); + output + } + } +} + +pub use path::Manager as PathManager; + +use bytecheck::CheckBytes; +use rkyv::{ser::Serializer, Archive, Deserialize, Serialize}; +use std::{io::Read, path::Path}; + +#[must_use] +#[repr(C)] +#[derive(Archive, Debug, Deserialize, Serialize)] +#[archive_attr(derive(CheckBytes, Debug))] +pub struct Config { + pub active_page: Box, +} + +impl Config { + pub fn deserialize(path: &Path) -> Self { + let Ok(mut file) = std::fs::File::open(path) else { + return Self::default(); + }; + + let mut buffer = Vec::with_capacity(128); + if file.read_to_end(&mut buffer).is_err() { + return Self::default(); + } + + buffer.shrink_to_fit(); + + let Ok(archived) = rkyv::check_archived_root::(buffer.as_slice()) else { + return Self::default(); + }; + + archived + .deserialize(&mut rkyv::Infallible) + .unwrap_or_default() + } + + pub fn serialize(&self, path: &Path) { + let mut serializer = rkyv::ser::serializers::AllocSerializer::<0>::default(); + + if serializer.serialize_value(self).is_err() { + return; + }; + + let bytes = serializer.into_serializer().into_inner(); + + let _res = std::fs::write(path, &bytes); + } +} + +impl Default for Config { + fn default() -> Self { + Self { + active_page: Box::from("desktop"), + } + } +} diff --git a/src/main.rs b/src/main.rs index 5435bc3..9d80e9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,8 @@ pub mod app; pub use app::{Message, SettingsApp}; +pub mod config; + #[macro_use] pub mod localize; diff --git a/src/page/desktop/appearance.rs b/src/page/desktop/appearance.rs index 9ace7e1..22855f9 100644 --- a/src/page/desktop/appearance.rs +++ b/src/page/desktop/appearance.rs @@ -9,13 +9,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "appearance"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("appearance", "preferences-pop-desktop-appearance-symbolic") .title(fl!("appearance")) .description(fl!("appearance", "desc")) - .icon_name("preferences-pop-desktop-appearance-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/desktop/dock.rs b/src/page/desktop/dock.rs index ecc6eb7..1ed7388 100644 --- a/src/page/desktop/dock.rs +++ b/src/page/desktop/dock.rs @@ -10,13 +10,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "dock"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("dock", "preferences-pop-desktop-dock-symbolic") .title(fl!("dock")) .description(fl!("dock", "desc")) - .icon_name("preferences-pop-desktop-dock-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/desktop/mod.rs b/src/page/desktop/mod.rs index cc2b741..e372ccf 100644 --- a/src/page/desktop/mod.rs +++ b/src/page/desktop/mod.rs @@ -15,12 +15,8 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "desktop"; - fn page() -> page::Meta { - page::Meta::default() - .title(fl!("desktop")) - .icon_name("video-display-symbolic") + page::Meta::new("desktop", "video-display-symbolic").title(fl!("desktop")) } fn sub_pages(page: page::Insert) -> page::Insert { diff --git a/src/page/desktop/notifications.rs b/src/page/desktop/notifications.rs index f2e6f74..cff02ef 100644 --- a/src/page/desktop/notifications.rs +++ b/src/page/desktop/notifications.rs @@ -10,13 +10,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "notifications"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("notifications", "preferences-system-notifications-symbolic") .title(fl!("notifications")) .description(fl!("notifications", "desc")) - .icon_name("preferences-system-notifications-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/desktop/options.rs b/src/page/desktop/options.rs index ed8ee17..f9ad6e4 100644 --- a/src/page/desktop/options.rs +++ b/src/page/desktop/options.rs @@ -19,13 +19,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "desktop-options"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("desktop-options", "video-display-symbolic") .title(fl!("desktop-options")) .description(fl!("desktop-options", "desc")) - .icon_name("video-display-symbolic") } #[allow(clippy::too_many_lines)] diff --git a/src/page/desktop/wallpaper.rs b/src/page/desktop/wallpaper.rs index 17500b0..fcc8fde 100644 --- a/src/page/desktop/wallpaper.rs +++ b/src/page/desktop/wallpaper.rs @@ -19,13 +19,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "wallpaper"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("wallpaper", "preferences-desktop-wallpaper-symbolic") .title(fl!("wallpaper")) .description(fl!("wallpaper", "desc")) - .icon_name("preferences-desktop-wallpaper-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/desktop/workspaces.rs b/src/page/desktop/workspaces.rs index 96927fa..2d76a3e 100644 --- a/src/page/desktop/workspaces.rs +++ b/src/page/desktop/workspaces.rs @@ -12,13 +12,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "workspaces"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("workspaces", "preferences-pop-desktop-workspaces-symbolic") .title(fl!("workspaces")) .description(fl!("workspaces", "desc")) - .icon_name("preferences-pop-desktop-workspaces-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/mod.rs b/src/page/mod.rs index da8d19d..16f06a5 100644 --- a/src/page/mod.rs +++ b/src/page/mod.rs @@ -24,9 +24,6 @@ slotmap::new_key_type! { pub trait Page { type Model: Default + 'static; - /// A unique identity that is the same between application runs. - const PERSISTENT_ID: &'static str; - fn page() -> Meta; #[must_use] @@ -41,17 +38,40 @@ pub trait Page { } } -#[derive(Default, Setters)] +#[derive(Setters)] #[must_use] pub struct Meta { + /// A unique identity that is the same between application runs. + #[setters(skip)] + pub id: &'static str, + + /// The icon associated with the page. + #[setters(skip)] + pub icon_name: &'static str, + + /// The title of the page. #[setters(into)] pub title: String, - #[setters(into)] - pub icon_name: &'static str, + + /// A description of the page. #[setters(into)] pub description: String, + + /// The parent of the page. #[setters(strip_option)] pub parent: Option, } +impl Meta { + pub const fn new(id: &'static str, icon_name: &'static str) -> Self { + Self { + title: String::new(), + icon_name, + id, + description: String::new(), + parent: None, + } + } +} + pub type Content = Vec; diff --git a/src/page/networking/accounts.rs b/src/page/networking/accounts.rs index 781a44b..87833fd 100644 --- a/src/page/networking/accounts.rs +++ b/src/page/networking/accounts.rs @@ -4,8 +4,7 @@ use crate::page; pub fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("online-accounts", "goa-panel-symbolic") .title(fl!("online-accounts")) .description(fl!("online-accounts", "desc")) - .icon_name("goa-panel-symbolic") } diff --git a/src/page/networking/wired.rs b/src/page/networking/wired.rs index c1cc124..f652fdb 100644 --- a/src/page/networking/wired.rs +++ b/src/page/networking/wired.rs @@ -4,8 +4,7 @@ use crate::page; pub fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("wired", "network-workgroup-symbolic") .title(fl!("wired")) .description(fl!("wired", "desc")) - .icon_name("network-workgroup-symbolic") } diff --git a/src/page/sound.rs b/src/page/sound.rs index 95ba0d4..40afaf0 100644 --- a/src/page/sound.rs +++ b/src/page/sound.rs @@ -15,13 +15,10 @@ pub struct Page; impl page::Page for Page { type Model = Sound; - const PERSISTENT_ID: &'static str = "sound"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("sound", "multimedia-volume-control-symbolic") .title(fl!("sound")) .description(fl!("sound", "desc")) - .icon_name("multimedia-volume-control-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/system/about.rs b/src/page/system/about.rs index 0a1598a..1bd1752 100644 --- a/src/page/system/about.rs +++ b/src/page/system/about.rs @@ -16,13 +16,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "about"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("about", "help-about-symbolic") .title(fl!("about")) .description(fl!("about", "desc")) - .icon_name("help-about-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/system/firmware.rs b/src/page/system/firmware.rs index af6afb6..366cd69 100644 --- a/src/page/system/firmware.rs +++ b/src/page/system/firmware.rs @@ -10,13 +10,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "firmware"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("firmware", "firmware-manager-symbolic") .title(fl!("firmware")) .description(fl!("firmware", "desc")) - .icon_name("firmware-manager-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/system/mod.rs b/src/page/system/mod.rs index 3ffa4d8..b21d6f1 100644 --- a/src/page/system/mod.rs +++ b/src/page/system/mod.rs @@ -12,12 +12,8 @@ pub struct Page; impl page::Page for Page { type Model = Model; - const PERSISTENT_ID: &'static str = "system"; - fn page() -> page::Meta { - page::Meta::default() - .title(fl!("system")) - .icon_name("system-users-symbolic") + page::Meta::new("system", "system-users-symbolic").title(fl!("system")) } fn sub_pages(page: page::Insert) -> page::Insert { diff --git a/src/page/system/users.rs b/src/page/system/users.rs index f3d77e3..0fa16d8 100644 --- a/src/page/system/users.rs +++ b/src/page/system/users.rs @@ -10,13 +10,10 @@ pub struct Page; impl page::Page for Page { type Model = super::Model; - const PERSISTENT_ID: &'static str = "users"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("users", "system-users-symbolic") .title(fl!("users")) .description(fl!("users", "desc")) - .icon_name("system-users-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/time/date.rs b/src/page/time/date.rs index ea13185..6992c36 100644 --- a/src/page/time/date.rs +++ b/src/page/time/date.rs @@ -38,13 +38,10 @@ pub struct Page; impl page::Page for Page { type Model = Model; - const PERSISTENT_ID: &'static str = "time-date"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("time-date", "preferences-system-time-symbolic") .title(fl!("time-date")) .description(fl!("time-date", "desc")) - .icon_name("preferences-system-time-symbolic") } fn content(sections: &mut SlotMap) -> Option { diff --git a/src/page/time/mod.rs b/src/page/time/mod.rs index 1bd6546..563b365 100644 --- a/src/page/time/mod.rs +++ b/src/page/time/mod.rs @@ -11,13 +11,10 @@ pub struct Page; impl page::Page for Page { type Model = (); - const PERSISTENT_ID: &'static str = "time"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("time", "preferences-system-time-symbolic") .title(fl!("time")) .description(fl!("time", "desc")) - .icon_name("preferences-system-time-symbolic") } fn sub_pages(page: page::Insert) -> page::Insert { diff --git a/src/page/time/region.rs b/src/page/time/region.rs index ac384f0..a17983e 100644 --- a/src/page/time/region.rs +++ b/src/page/time/region.rs @@ -9,13 +9,10 @@ pub struct Page; impl page::Page for Page { type Model = (); - const PERSISTENT_ID: &'static str = "time-region"; - fn page() -> page::Meta { - page::Meta::default() + page::Meta::new("time-region", "preferences-desktop-locale-symbolic") .title(fl!("time-region")) .description(fl!("time-region", "desc")) - .icon_name("preferences-desktop-locale-symbolic") } fn content(sections: &mut SlotMap) -> Option {