diff --git a/Cargo.lock b/Cargo.lock index 072728d..b3f1b8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,6 +416,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "concat-in-place" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b80dba65d26e0c4b692ad0312b837f1177e8175031af57fd1de4f3bc36b430" + [[package]] name = "concurrent-queue" version = "2.1.0" @@ -425,6 +431,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const_format" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "const_panic" version = "0.2.7" @@ -492,6 +518,7 @@ dependencies = [ "async-channel", "bytecheck", "color-eyre", + "cosmic-settings-system", "derive_setters", "dirs", "generator", @@ -506,6 +533,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "cosmic-settings-system" +version = "0.1.0" +dependencies = [ + "bumpalo", + "concat-in-place", + "const_format", + "memchr", + "sysinfo", +] + [[package]] name = "cosmic-text" version = "0.6.0" @@ -2228,6 +2266,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ntapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +dependencies = [ + "winapi", +] + [[package]] name = "num" version = "0.4.0" @@ -3406,6 +3453,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "sysinfo" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975fe381e0ecba475d4acff52466906d95b153a40324956552e027b2a9eaa89e" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "termcolor" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 391a715..9ce0cd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,9 @@ rust-embed = "6.4.2" slotmap = "1.0.6" tokio = "1.25.0" +[dependencies.cosmic-settings-system] +path = "pages/system" + [dependencies.i18n-embed] version = "0.13.8" features = ["fluent-system", "desktop-requester"] @@ -32,7 +35,7 @@ default-features = false features = ["debug", "winit", "dyrend", "tokio"] [profile.dev] -opt-level = 2 +opt-level = "s" incremental = true [profile.release] diff --git a/pages/system/Cargo.lock b/pages/system/Cargo.lock new file mode 100644 index 0000000..bb65fd3 --- /dev/null +++ b/pages/system/Cargo.lock @@ -0,0 +1,263 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "concat-in-place" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b80dba65d26e0c4b692ad0312b837f1177e8175031af57fd1de4f3bc36b430" + +[[package]] +name = "const_format" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cosmic-settings-system" +version = "0.1.0" +dependencies = [ + "bumpalo", + "concat-in-place", + "const_format", + "memchr", + "sysinfo", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ntapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +dependencies = [ + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sysinfo" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975fe381e0ecba475d4acff52466906d95b153a40324956552e027b2a9eaa89e" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/pages/system/Cargo.toml b/pages/system/Cargo.toml new file mode 100644 index 0000000..bc8614a --- /dev/null +++ b/pages/system/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cosmic-settings-system" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +const_format = "0.2.30" +concat-in-place = "1.1.0" +sysinfo = "0.27.7" +memchr = "2.5.0" + +[dependencies.bumpalo] +version = "3.12.0" +features = ["collections"] diff --git a/pages/system/src/lib.rs b/pages/system/src/lib.rs new file mode 100644 index 0000000..a363ddb --- /dev/null +++ b/pages/system/src/lib.rs @@ -0,0 +1,202 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use bumpalo::Bump; +use std::{ffi::OsStr, io::Read}; +use sysinfo::{DiskExt, SystemExt}; + +use concat_in_place::strcat; +use const_format::concatcp; + +const DMI_DIR: &str = "/sys/devices/virtual/dmi/id/"; +const BOARD_NAME: &str = concatcp!(DMI_DIR, "board_name"); +const BOARD_VERSION: &str = concatcp!(DMI_DIR, "board_version"); +const SYS_VENDOR: &str = concatcp!(DMI_DIR, "sys_vendor"); + +#[derive(Clone, Debug, Default)] +pub struct Info { + pub desktop_environment: String, + pub device_name: String, + pub disk_capacity: String, + pub graphics: Vec, + pub hardware_model: String, + pub memory: String, + pub operating_system: String, + pub os_architecture: String, + pub processor: String, + pub windowing_system: String, +} + +impl Info { + pub fn load() -> Info { + let mut info = Info::default(); + let mut bump = Bump::with_capacity(8 * 1024); + + architecture(&bump, &mut info.os_architecture); + bump.reset(); + + hardware_model(&bump, &mut info.hardware_model); + bump.reset(); + + operating_system(&bump, &mut info.operating_system); + bump.reset(); + + processor_name(&bump, &mut info.processor); + bump.reset(); + + let mut sys = sysinfo::System::new(); + + sys.refresh_disks_list(); + sys.refresh_disks(); + sys.refresh_memory(); + + let mut total_capacity = 0; + for disk in sys.disks() { + total_capacity += disk.total_space(); + } + + info.disk_capacity = format!("{} GB", (total_capacity / 1_000_000_000)); + + if let Some(name) = sys.host_name() { + info.device_name = name; + } + + info.memory = format!("{} GB", (sys.total_memory() / 1_000_000_000 + 1)); + + if let Ok(mut session) = std::env::var("XDG_SESSION_TYPE") { + if let Some(first) = session.get_mut(0..1) { + first.make_ascii_uppercase(); + } + + info.windowing_system = session; + } + + if let Ok(mut session) = std::env::var("DESKTOP_SESSION") { + if let Some(first) = session.get_mut(0..1) { + first.make_ascii_uppercase(); + } + + info.desktop_environment = session; + } + + if let Ok(output) = std::process::Command::new("lspci").output() { + if let Ok(stdout) = std::str::from_utf8(&output.stdout) { + for line in stdout.lines() { + if let Some(pos) = memchr::memmem::find(line.as_bytes(), b"VGA") { + let line = &line[pos + 3..]; + if let Some(pos) = memchr::memmem::find(line.as_bytes(), b": ") { + info.graphics.push(String::from(&line[pos + 2..])); + } + } + } + } + } + + info + } +} + +pub fn architecture(bump: &Bump, arch: &mut String) { + let buffer = &mut bumpalo::collections::Vec::new_in(bump); + if let Some(value) = read_to_string("/proc/sys/kernel/arch", buffer) { + arch.push_str(value.trim()); + } +} + +pub fn hardware_model(bump: &Bump, hardware_model: &mut String) { + let buffer = &mut bumpalo::collections::Vec::new_in(bump); + if let Some(mut sys_vendor) = read_to_string(SYS_VENDOR, buffer) { + sys_vendor = sys_vendor.trim(); + + hardware_model.push_str(sys_vendor); + + let buffer = &mut bumpalo::collections::Vec::new_in(bump); + if let Some(mut name) = read_to_string(BOARD_NAME, buffer) { + name = name.trim(); + + if !name.is_empty() && name != sys_vendor { + // Ensure that the name does not contain the vendor. + name = match name.strip_prefix(sys_vendor) { + Some(stripped) => stripped.trim(), + None => name, + }; + + strcat!(&mut *hardware_model, " " name); + } + + let buffer = &mut bumpalo::collections::Vec::new_in(bump); + if let Some(mut version) = read_to_string(BOARD_VERSION, buffer) { + version = version.trim(); + + // These have bogus values for their version. + const IGNORE_PRODUCTS: &[&str] = &["Dev One"]; + + if !version.is_empty() && !IGNORE_PRODUCTS.contains(&name) { + strcat!(hardware_model, " (" version.trim() ")"); + } + } + } + } +} + +pub fn operating_system(bump: &Bump, operating_system: &mut String) { + let mut buffer = bumpalo::collections::Vec::new_in(bump); + let Some(os_release) = read_to_string("/etc/os-release", &mut buffer) else { + return; + }; + + for line in os_release.lines() { + if let Some(mut value) = line.strip_prefix("PRETTY_NAME=") { + if let Some(v) = value.strip_prefix('"') { + value = v; + } + + if let Some(v) = value.strip_suffix('"') { + value = v; + } + + operating_system.push_str(value.trim()); + break; + } + } +} + +pub fn processor_name(bump: &Bump, name: &mut String) { + if let Some(cpuinfo) = read_to_string( + "/proc/cpuinfo", + &mut bumpalo::collections::Vec::new_in(bump), + ) { + for line in cpuinfo.lines() { + if let Some(info) = line.strip_prefix("model name") { + if let Some(info) = info.trim_start().strip_prefix(":") { + name.push_str(info.trim()); + } + + break; + } + } + } +} + +pub fn read_to_string<'a, P: AsRef>( + path: P, + buffer: &'a mut bumpalo::collections::Vec, +) -> Option<&'a str> { + let mut file = std::fs::File::open(path.as_ref()).ok()?; + + if let Ok(metadata) = file.metadata() { + buffer.reserve_exact(metadata.len() as usize); + } + + let mut buf = [0; 16 * 1024]; + + loop { + match file.read(&mut buf) { + Ok(0) => break, + Ok(read) => buffer.extend_from_slice(&buf[..read]), + Err(_) => return None, + } + } + + std::str::from_utf8(buffer.as_slice()).ok() +} diff --git a/src/page/system/about.rs b/src/page/system/about.rs index 552289f..efa7a73 100644 --- a/src/page/system/about.rs +++ b/src/page/system/about.rs @@ -1,9 +1,10 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use std::future::Future; - -use crate::page::{self, Content, Section}; +use crate::{ + page::{self, desktop::wallpaper::settings, Content, Section}, + SettingsApp, +}; use cosmic::{ iced::{ widget::{horizontal_space, row}, @@ -11,6 +12,7 @@ use cosmic::{ }, widget::{icon, list_column, settings, text}, }; +use cosmic_settings_system::Info; use slotmap::SlotMap; #[derive(Clone, Debug)] @@ -24,19 +26,6 @@ pub struct Model { support_page: page::Entity, } -#[derive(Clone, Debug, Default)] -pub struct Info { - hardware_model: String, - memory: String, - processor: String, - graphics: String, - disk_capacity: String, - operating_system: String, - os_architecture: String, - desktop_environment: String, - windowing_system: String, -} - impl Model { pub fn update(&mut self, message: Message) { match message { @@ -67,28 +56,20 @@ impl page::Page for Page { } fn load(_page: page::Entity) -> crate::page::PageTask { - Box::pin(async move { - let info = Info { - windowing_system: std::env::var("XDG_SESSION_TYPE") - .ok() - .unwrap_or_else(|| fl!("unknown")), - ..Info::default() - }; - - crate::Message::About(Message::Info(info)) - }) + Box::pin(async move { crate::Message::About(Message::Info(Info::load())) }) } } fn device() -> Section { Section::new() .descriptions(vec![fl!("about-device"), fl!("about-device", "desc")]) - .view_fn(|_app, section| { + .view_fn(|app, section| { let desc = §ion.descriptions; + let model = model(app); let device_name = settings::item::builder(&desc[0]) .description(&desc[1]) - .control(text("TODO")); + .control(text(&model.info.device_name)); list_column().add(device_name).into() }) @@ -117,14 +98,22 @@ fn hardware() -> Section { fl!("about-hardware", "graphics"), fl!("about-hardware", "disk-capacity"), ]) - .view_fn(|_app, section| { + .view_fn(|app, section| { let desc = §ion.descriptions; - settings::view_section(§ion.title) - .add(settings::item(&desc[0], text("TODO"))) - .add(settings::item(&desc[1], text("TODO"))) - .add(settings::item(&desc[2], text("TODO"))) - .add(settings::item(&desc[3], text("TODO"))) - .add(settings::item(&desc[4], text("TODO"))) + let model = model(app); + + let mut sections = settings::view_section(§ion.title) + .add(settings::item(&desc[0], text(&model.info.hardware_model))) + .add(settings::item(&desc[1], text(&model.info.memory))) + .add(settings::item(&desc[2], text(&model.info.processor))); + + for card in &model.info.graphics { + sections = sections.add(settings::item(&desc[3], text(card.as_str()))); + } + + // .add(settings::item(&desc[3], text(&model.info.graphics))) + sections + .add(settings::item(&desc[4], text(&model.info.disk_capacity))) .into() }) } @@ -139,16 +128,16 @@ fn os() -> Section { fl!("about-os", "windowing-system"), ]) .view_fn(|app, section| { - let model = app - .pages - .resource::() - .expect("missing system->about model"); + let model = model(app); let desc = §ion.descriptions; settings::view_section(§ion.title) - .add(settings::item(&desc[0], text("TODO"))) - .add(settings::item(&desc[1], text("TODO"))) - .add(settings::item(&desc[2], text("TODO"))) + .add(settings::item(&desc[0], text(&model.info.operating_system))) + .add(settings::item(&desc[1], text(&model.info.os_architecture))) + .add(settings::item( + &desc[2], + text(&model.info.desktop_environment), + )) .add(settings::item(&desc[3], text(&model.info.windowing_system))) .into() }) @@ -164,3 +153,9 @@ fn related() -> Section { .into() }) } + +fn model(app: &SettingsApp) -> &Model { + app.pages + .resource::() + .expect("missing system->about model") +}