From 29f1386e58ae3af235f0d4b8fd101edad380b09e Mon Sep 17 00:00:00 2001 From: Fred <27208977+FreddyFunk@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:57:58 +0100 Subject: [PATCH] feat(about): improve GPU names by getting info from wgpu --- Cargo.lock | 276 +------------- cosmic-settings/Cargo.toml | 4 +- cosmic-settings/src/pages/system/about.rs | 93 ++--- cosmic-settings/src/pages/system/info.rs | 419 ++++++++++++++++++++++ cosmic-settings/src/pages/system/mod.rs | 2 + pages/system/Cargo.toml | 19 - pages/system/src/about.rs | 244 ------------- pages/system/src/lib.rs | 4 - 8 files changed, 488 insertions(+), 573 deletions(-) create mode 100644 cosmic-settings/src/pages/system/info.rs delete mode 100644 pages/system/Cargo.toml delete mode 100644 pages/system/src/about.rs delete mode 100644 pages/system/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5563b85..853db61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,17 +121,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.12" @@ -833,18 +822,6 @@ dependencies = [ "core2", ] -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block" version = "0.1.6" @@ -922,29 +899,6 @@ dependencies = [ "zbus 5.12.0", ] -[[package]] -name = "borsh" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" -dependencies = [ - "borsh-derive", - "cfg_aliases 0.2.1", -] - -[[package]] -name = "borsh-derive" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" -dependencies = [ - "once_cell", - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "branches" version = "0.3.0" @@ -1002,40 +956,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" -[[package]] -name = "byte-unit" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6d47a4e2961fb8721bcfc54feae6455f2f64e7054f9bc67e875f0e77f4c58d" -dependencies = [ - "rust_decimal", - "schemars 1.1.0", - "serde", - "utf8-width", -] - -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "bytemuck" version = "1.24.0" @@ -1433,12 +1353,6 @@ dependencies = [ "memchr", ] -[[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.5.0" @@ -1448,26 +1362,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const_format" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - [[package]] name = "convert_case" version = "0.8.0" @@ -1569,7 +1463,7 @@ dependencies = [ [[package]] name = "cosmic-comp-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-comp#df88a0e1814459bc43c05fd546030269f64639fb" +source = "git+https://github.com/pop-os/cosmic-comp#141fa472efb33427fdac2f817ef85763b2fbc5d0" dependencies = [ "cosmic-config", "input", @@ -1581,7 +1475,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1602,7 +1496,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "quote", "syn 2.0.111", @@ -1761,7 +1655,6 @@ dependencies = [ "cosmic-settings-network-manager-subscription", "cosmic-settings-page", "cosmic-settings-sound-subscription", - "cosmic-settings-system", "cosmic-settings-upower-subscription", "cosmic-settings-wallpaper", "derive_setters", @@ -1798,6 +1691,7 @@ dependencies = [ "smithay-client-toolkit 0.20.0", "static_init", "sunrise", + "sysinfo", "timedate-zbus", "tokio", "tracing", @@ -1944,18 +1838,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "cosmic-settings-system" -version = "1.0.0-beta6" -dependencies = [ - "bumpalo", - "byte-unit", - "concat-in-place", - "const_format", - "memchr", - "sysinfo", -] - [[package]] name = "cosmic-settings-upower-subscription" version = "1.0.0-beta6" @@ -2014,7 +1896,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "almost", "cosmic-config", @@ -2919,12 +2801,6 @@ dependencies = [ "libc", ] -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - [[package]] name = "futures" version = "0.3.31" @@ -3273,9 +3149,6 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] [[package]] name = "hashbrown" @@ -3461,7 +3334,7 @@ dependencies = [ [[package]] name = "iced" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "dnd", "iced_accessibility", @@ -3479,7 +3352,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "accesskit", "accesskit_winit", @@ -3488,7 +3361,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "bitflags 2.10.0", "bytes", @@ -3513,7 +3386,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "futures", "iced_core", @@ -3539,7 +3412,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "bitflags 2.10.0", "bytemuck", @@ -3561,7 +3434,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -3573,7 +3446,7 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "bytes", "cosmic-client-toolkit", @@ -3589,7 +3462,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "bytemuck", "cosmic-text", @@ -3605,7 +3478,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "as-raw-xcb-connection", "bitflags 2.10.0", @@ -3636,7 +3509,7 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -3657,7 +3530,7 @@ dependencies = [ [[package]] name = "iced_winit" version = "0.14.0-dev" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "cosmic-client-toolkit", "dnd", @@ -4677,7 +4550,7 @@ checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#3b8ad45950f5d23c8550e18e628f6e70b7089d89" +source = "git+https://github.com/pop-os/libcosmic#aabc8dcda530a6ac70617dd578cea55910af53c8" dependencies = [ "apply", "ashpd 0.12.0", @@ -6273,26 +6146,6 @@ dependencies = [ "syn 2.0.111", ] -[[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 1.0.109", -] - [[package]] name = "pwhash" version = "1.0.0" @@ -6371,12 +6224,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rand" version = "0.8.5" @@ -6623,15 +6470,6 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] - [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -6687,35 +6525,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "rkyv" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "ron" version = "0.11.0" @@ -6769,22 +6578,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "rust_decimal" -version = "1.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.5", - "rkyv", - "serde", - "serde_json", -] - [[package]] name = "rustc-demangle" version = "0.1.26" @@ -6938,12 +6731,6 @@ dependencies = [ "tiny-skia", ] -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - [[package]] name = "secure-string" version = "0.3.0" @@ -7160,12 +6947,6 @@ dependencies = [ "quote", ] -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - [[package]] name = "simplecss" version = "0.2.2" @@ -7534,12 +7315,6 @@ dependencies = [ "slotmap", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "target-lexicon" version = "0.13.3" @@ -8140,12 +7915,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" -[[package]] -name = "utf8-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -9158,7 +8927,7 @@ name = "winit" version = "0.30.5" source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13-rc#12a5f17d1811cdebbcbd310a3d92965e9142fa12" dependencies = [ - "ahash 0.8.12", + "ahash", "android-activity", "atomic-waker", "bitflags 2.10.0", @@ -9252,15 +9021,6 @@ dependencies = [ "either", ] -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "x11-dl" version = "2.21.0" diff --git a/cosmic-settings/Cargo.toml b/cosmic-settings/Cargo.toml index a27ab23..7a6687b 100644 --- a/cosmic-settings/Cargo.toml +++ b/cosmic-settings/Cargo.toml @@ -32,7 +32,6 @@ cosmic-settings-bluetooth-subscription = { path = "../subscriptions/bluetooth", cosmic-settings-network-manager-subscription = { path = "../subscriptions/network-manager", optional = true } cosmic-settings-upower-subscription = { path = "../subscriptions/upower", optional = true } cosmic-settings-sound-subscription = { path = "../subscriptions/sound", optional = true, features = ["auto-profile-init"] } -cosmic-settings-system = { path = "../pages/system", optional = true } cosmic-settings-wallpaper = { path = "../pages/wallpapers" } cosmic-settings-daemon-config = { git = "https://github.com/pop-os/cosmic-settings-daemon", optional = true } derive_setters = "0.1.8" @@ -55,6 +54,7 @@ itertools = "0.14.0" itoa = "1.0.15" libcosmic.workspace = true locale1 = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } +sysinfo = { version = "0.36.1", optional = true } mime-apps = { package = "cosmic-mime-apps", git = "https://github.com/pop-os/cosmic-mime-apps", optional = true } # TODO: updating notify beyond this hangs the app notify = "6.1.1" @@ -140,7 +140,7 @@ page-accessibility = [ "dep:cosmic-settings-accessibility-subscription", "dep:cosmic-settings-a11y-manager-subscription", ] -page-about = ["dep:cosmic-settings-system", "dep:hostname1-zbus", "dep:zbus"] +page-about = ["dep:hostname1-zbus", "dep:sysinfo", "dep:zbus"] page-bluetooth = [ "dep:cosmic-settings-bluetooth-subscription", "dep:zbus", diff --git a/cosmic-settings/src/pages/system/about.rs b/cosmic-settings/src/pages/system/about.rs index fa27df6..7972a28 100644 --- a/cosmic-settings/src/pages/system/about.rs +++ b/cosmic-settings/src/pages/system/about.rs @@ -3,9 +3,9 @@ use cosmic_settings_page::{self as page, Section, section}; +use super::info::Info; use cosmic::widget::{editable_input, list_column, settings, text}; use cosmic::{Apply, Task}; -use cosmic_settings_system::about::Info; use slab::Slab; use slotmap::SlotMap; @@ -119,46 +119,48 @@ impl Page { return Task::none(); } - let hostname = &self.hostname_input; - if hostname_validator::is_valid(hostname) { - self.editing_device_name = false; - let hostname = hostname.clone(); - return cosmic::Task::future(async move { - let connection = match zbus::Connection::system().await { - Ok(conn) => conn, - Err(why) => { - tracing::error!(?why, "failed to establish connection to dbus"); - return Message::Error(String::from( - "failed to establish connection to dbus", - )); - } - }; - - let hostname1 = match hostname1_zbus::Hostname1Proxy::new(&connection).await { - Ok(proxy) => proxy, - Err(why) => { - tracing::error!(?why, "failed to connect to org.freedesktop.hostname1"); - return Message::Error(String::from( - "failed to connect to org.freedesktop.hostname1", - )); - } - }; - - if let Err(why) = hostname1.set_static_hostname(&hostname, false).await { - tracing::error!(?why, "failed to set static hostname"); - return Message::Error(String::from("failed to set static hostname")); - } - - Message::HostnameSuccess(hostname) - }) - .map(crate::app::Message::from) - .map(Into::into); + if !hostname_validator::is_valid(&self.hostname_input) { + return Task::none(); } - Task::none() + self.editing_device_name = false; + let hostname = self.hostname_input.clone(); + + cosmic::Task::future(async move { set_hostname(hostname).await }) + .map(crate::app::Message::from) + .map(Into::into) } } +/// Sets the system hostname via D-Bus. +async fn set_hostname(hostname: String) -> Message { + match set_hostname_impl(&hostname).await { + Ok(()) => Message::HostnameSuccess(hostname), + Err(err) => { + tracing::error!("failed to set hostname: {}", err); + Message::Error(err) + } + } +} + +/// Implementation of hostname setting that uses Result for cleaner error handling. +async fn set_hostname_impl(hostname: &str) -> Result<(), String> { + let connection = zbus::Connection::system() + .await + .map_err(|e| format!("failed to establish connection to dbus: {}", e))?; + + let hostname1 = hostname1_zbus::Hostname1Proxy::new(&connection) + .await + .map_err(|e| format!("failed to connect to org.freedesktop.hostname1: {}", e))?; + + hostname1 + .set_static_hostname(hostname, false) + .await + .map_err(|e| format!("failed to set static hostname: {}", e))?; + + Ok(()) +} + fn device() -> Section { let mut descriptions = Slab::new(); @@ -207,7 +209,7 @@ fn hardware() -> Section { .view::(move |_binder, page, section| { let desc = §ion.descriptions; - let sections = settings::section() + let mut section_builder = settings::section() .title(§ion.title) .add(settings::flex_item( &*desc[model], @@ -222,15 +224,14 @@ fn hardware() -> Section { text::body(&page.info.processor), )); - page.info - .graphics - .iter() - .fold(sections, |sections, card| { - sections.add(settings::flex_item( - &*desc[graphics], - text::body(card.as_str()), - )) - }) + for card in &page.info.graphics { + section_builder = section_builder.add(settings::flex_item( + &*desc[graphics], + text::body(card.as_str()), + )); + } + + section_builder .add(settings::flex_item( &*desc[disk_capacity], text::body(&page.info.disk_capacity), diff --git a/cosmic-settings/src/pages/system/info.rs b/cosmic-settings/src/pages/system/info.rs new file mode 100644 index 0000000..23897d7 --- /dev/null +++ b/cosmic-settings/src/pages/system/info.rs @@ -0,0 +1,419 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: GPL-3.0-only + +use cosmic::iced_wgpu::wgpu; +use std::{collections::HashMap, collections::HashSet, ffi::OsStr, process::Command}; + +#[must_use] +#[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 kernel_version: String, + pub processor: String, + pub windowing_system: String, +} + +impl Info { + pub fn load() -> Info { + let mut info = Info::default(); + + info.os_architecture = architecture(); + info.kernel_version = kernel_version(); + info.hardware_model = hardware_model(); + info.operating_system = operating_system(); + info.processor = processor_name(); + + let mut sys = sysinfo::System::new(); + let disks = sysinfo::Disks::new_with_refreshed_list(); + sys.refresh_memory(); + + let mut total_capacity = 0; + let mut disk_set = HashSet::new(); + for disk in disks.list() { + if disk_set.contains(disk.name()) { + continue; + } + disk_set.insert(disk.name()); + total_capacity += disk.total_space(); + } + + info.disk_capacity = format_size(total_capacity); + + if let Some(name) = sysinfo::System::host_name() { + info.device_name = name; + } + + info.memory = format_size(sys.total_memory()); + + 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; + } + + // prefer XDG_SESSION_DESKTOP because the value is singular + if let Ok(mut session) = std::env::var("XDG_SESSION_DESKTOP") + .or_else(|_| std::env::var("XDG_CURRENT_DESKTOP")) + .or_else(|_| std::env::var("DESKTOP_SESSION")) + { + if let Some(first) = session.get_mut(0..1) { + first.make_ascii_uppercase(); + } + info.desktop_environment = session; + } + + // Use wgpu to enumerate GPUs. Works cross-platform and doesn't require external tools + let instance = wgpu::Instance::default(); + let adapters = instance.enumerate_adapters(wgpu::Backends::all()); + + // Track seen GPUs by (vendor, device) and by name to handle different scenarios: + // - Same GPU via different backends (Vulkan/OpenGL) -> deduplicate by device ID or name + // - Multiple identical GPUs -> show all (different device IDs or backend enumeration order) + // - Backends with invalid device IDs (0x0000) -> deduplicate by name + let mut seen_devices = HashSet::new(); + let mut seen_names = HashSet::new(); + + for adapter in adapters { + let adapter_info = adapter.get_info(); + + if adapter_info.device_type == wgpu::DeviceType::Cpu { + continue; + } + + let mut gpu_name = if let Some(pos) = adapter_info.name.find('(') { + adapter_info.name[..pos].trim().to_string() + } else { + adapter_info.name + }; + + // Intel GPU quirk: wgpu sometimes reports incomplete names (just "Intel" or "Mesa Intel"). + // Mesa OpenGL backend also reports Intel GPUs with "Mesa Intel(R)" prefix and device ID 0. + // These are generic/duplicate entries when the driver can't properly identify the GPU. + // Use lspci as fallback to get proper full name like "Intel Corporation HD Graphics 500" + let is_intel_quirk = gpu_name == "Intel" + || gpu_name == "Mesa Intel" + || (gpu_name.starts_with("Mesa Intel") && adapter_info.device == 0); + + if is_intel_quirk { + if adapter_info.device != 0 { + let lspci_gpus = get_lspci_gpu_names(); + if let Some(lspci_name) = lspci_gpus.get(&adapter_info.device) { + gpu_name = lspci_name.clone(); + } else { + // If lspci lookup fails, skip this generic name to avoid duplicates + continue; + } + } else { + // Skip generic Intel names with no device ID (Mesa OpenGL duplicates) + continue; + } + } + + // AMD parsing to append codename to specific name + let is_amd_gpu = gpu_name.starts_with("AMD"); + + if is_amd_gpu { + if adapter_info.device != 0 { + let lspci_gpus = get_lspci_gpu_names(); + // If we can't find an lspci name for a GPU, just keep its original name + if let Some(lspci_name) = lspci_gpus.get(&adapter_info.device) { + // Codename starts after first pair of brackets + if let Some(bracket_end) = lspci_name.find(']') { + let mut code_name = lspci_name[bracket_end + 1..].to_string(); + // After codename, there may be another pair of brackets. + if let Some(bracket_start) = code_name.find('[') { + code_name = code_name[..bracket_start].to_string(); + } + if code_name.trim() != "Device" { + gpu_name = format!("{} ({})", gpu_name, code_name.trim()); + } + } + } + } else { + // Can't identify GPU + continue; + } + } + + let device_key = (adapter_info.vendor, adapter_info.device); + if adapter_info.device != 0 && seen_devices.contains(&device_key) { + continue; + } + + if adapter_info.device == 0 && seen_names.contains(&gpu_name) { + continue; + } + + if adapter_info.device != 0 { + seen_devices.insert(device_key); + } + seen_names.insert(gpu_name.clone()); + info.graphics.push(gpu_name); + } + + // NVIDIA Optimus quirk: On laptops with NVIDIA Optimus (switchable graphics), + // the dedicated NVIDIA GPU may be powered off to save battery and won't be + // enumerated by wgpu. Use lspci as a fallback to detect these inactive GPUs. + // This ensures users can see all GPUs in their system, even when not in use. + // Reference: https://wiki.archlinux.org/title/NVIDIA_Optimus + let lspci_gpus = get_all_lspci_gpus(); + for (vendor, device, name) in lspci_gpus { + let device_key = (vendor, device); + if !seen_devices.contains(&device_key) { + seen_devices.insert(device_key); + info.graphics.push(name); + } + } + + info + } +} + +fn architecture() -> String { + read_to_string("/proc/sys/kernel/arch") + .unwrap_or_default() + .trim() + .to_string() +} + +fn kernel_version() -> String { + read_to_string("/proc/version") + .and_then(|content| content.split_whitespace().nth(2).map(|s| s.to_string())) + .unwrap_or_default() +} + +fn hardware_model() -> String { + const DMI_DIR: &str = "/sys/devices/virtual/dmi/id/"; + const VERSION_IGNORING_PRODUCTS: &[&str] = &["Dev One"]; + + let sys_vendor = read_to_string(&format!("{DMI_DIR}sys_vendor")) + .map(|s| s.trim().to_string()) + .unwrap_or_default(); + + if sys_vendor.is_empty() { + // Fallback to sysinfo if DMI information is not available + return sysinfo::Product::name().unwrap_or_default(); + } + + let mut model = sys_vendor.clone(); + + if let Some(mut name) = + read_to_string(&format!("{DMI_DIR}board_name")).map(|s| s.trim().to_string()) + { + if !name.is_empty() && name != sys_vendor { + // Ensure that the name does not contain the vendor + name = name + .strip_prefix(&sys_vendor) + .map(|s| s.trim().to_string()) + .unwrap_or(name); + + model.push(' '); + model.push_str(&name); + + if let Some(version) = + read_to_string(&format!("{DMI_DIR}board_version")).map(|s| s.trim().to_string()) + { + if !version.is_empty() && !VERSION_IGNORING_PRODUCTS.contains(&name.as_str()) { + model.push_str(&format!(" ({version})")); + } + } + } + } + + model +} + +fn operating_system() -> String { + let content = read_to_string("/etc/os-release").unwrap_or_default(); + + for line in content.lines() { + if let Some(mut value) = line.strip_prefix("PRETTY_NAME=") { + value = value.strip_prefix('"').unwrap_or(value); + value = value.strip_suffix('"').unwrap_or(value); + return value.trim().to_string(); + } + } + + String::new() +} + +fn processor_name() -> String { + if let Some(cpuinfo) = read_to_string("/proc/cpuinfo") { + for line in cpuinfo.lines() { + if let Some(info) = line.strip_prefix("model name") { + if let Some(mut info) = info.trim_start().strip_prefix(':') { + if let Some(cpu_with) = info.find(" w/ ") { + info = &info[..cpu_with]; + } + return info.trim().to_string(); + } + break; + } + } + } + + // Fallback to sysinfo if /proc/cpuinfo is not present + let sys = sysinfo::System::new_with_specifics( + sysinfo::RefreshKind::nothing().with_cpu(sysinfo::CpuRefreshKind::everything()), + ); + sys.cpus() + .first() + .map(|cpu| cpu.brand().to_string()) + .unwrap_or_default() +} + +fn read_to_string>(path: P) -> Option { + std::fs::read_to_string(path.as_ref()).ok() +} + +fn format_size(bytes: u64) -> String { + const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB", "PiB"]; + const FACTOR: f64 = 1024.0; + + if bytes == 0 { + return String::from("0 B"); + } + + let bytes_f64 = bytes as f64; + let exp = (bytes_f64.ln() / FACTOR.ln()).floor() as usize; + let exp = exp.min(UNITS.len() - 1); + let value = bytes_f64 / FACTOR.powi(exp as i32); + + format!("{:.2} {}", value, UNITS[exp]) +} + +/// Get all GPUs from lspci with their vendor ID, device ID, and name. +fn get_all_lspci_gpus() -> Vec<(u32, u32, String)> { + let mut gpus = Vec::new(); + + let output = match Command::new("lspci").arg("-nn").output() { + Ok(output) => output, + Err(_) => return gpus, + }; + + let stdout = match std::str::from_utf8(&output.stdout) { + Ok(s) => s, + Err(_) => return gpus, + }; + + for line in stdout.lines() { + if !line.contains("VGA compatible") // Avoid "Non-VGA unclassified" + && !line.contains("3D controller") + && !line.contains("Display controller") + { + continue; + } + + // Parse device ID from format: "00:02.0 VGA compatible controller [0300]: Intel Corporation HD Graphics 500 [8086:5a85] (rev 0b)" + // We want to extract vendor:device IDs (8086:5a85) and the name (Intel Corporation HD Graphics 500) + + if let Some(ids_start) = line.rfind('[') { + if let Some(ids_end) = line.rfind(']') { + let ids = &line[ids_start + 1..ids_end]; + + if let Some(colon_pos) = ids.find(':') { + let vendor_id_str = &ids[..colon_pos]; + let device_id_str = &ids[colon_pos + 1..]; + + if let (Ok(vendor_id), Ok(device_id)) = ( + u32::from_str_radix(vendor_id_str, 16), + u32::from_str_radix(device_id_str, 16), + ) { + if let Some(name_start) = line.find(": ") { + let full_name = line[name_start + 2..ids_start].trim(); + + // Look for marketing name in brackets like "GP108M [GeForce MX150]" + // We prefer the marketing name over the chip code + let gpu_name = if let Some(bracket_start) = full_name.find('[') { + if let Some(bracket_end) = full_name.find(']') { + let vendor_part = full_name[..bracket_start].trim(); + let marketing_name = + full_name[bracket_start + 1..bracket_end].trim(); + + let vendor = vendor_part + .split_whitespace() + .next() + .unwrap_or(vendor_part); + + format!("{} {}", vendor, marketing_name) + } else { + full_name.to_string() + } + } else { + full_name.to_string() + }; + + if !gpu_name.is_empty() { + gpus.push((vendor_id, device_id, gpu_name)); + } + } + } + } + } + } + } + + gpus +} + +/// Get GPU names from lspci, mapping device ID to full GPU name. +/// This is used as a quirk for Intel GPUs which often report incomplete names via their OpenGL and Vulkan drivers. +/// https://www.intel.com/content/www/us/en/support/articles/000005520/graphics.html +fn get_lspci_gpu_names() -> HashMap { + let mut gpu_map = HashMap::new(); + + let output = match Command::new("lspci").arg("-nn").output() { + Ok(output) => output, + Err(_) => return gpu_map, + }; + + let stdout = match std::str::from_utf8(&output.stdout) { + Ok(s) => s, + Err(_) => return gpu_map, + }; + + for line in stdout.lines() { + // Look for any line containing VGA, 3D controller, or Display controller + if !line.contains("VGA compatible") // Avoid "Non-VGA unclassified" + && !line.contains("3D controller") + && !line.contains("Display controller") + { + continue; + } + + // Parse device ID from format: "00:02.0 VGA compatible controller [0300]: Intel Corporation HD Graphics 500 [8086:5a85] (rev 0b)" + // Or with marketing name: "00:02.0 VGA compatible controller: Intel Corporation Alder Lake-P GT2 [Iris Xe Graphics] [8086:46a6] (rev 0c)" + // We want to extract the device ID (5a85/46a6) and the name, preferring marketing name if available + + if let Some(ids_start) = line.rfind('[') { + if let Some(ids_end) = line.rfind(']') { + let ids = &line[ids_start + 1..ids_end]; + + // Parse vendor:device format like "8086:5a85" + if let Some(colon_pos) = ids.find(':') { + let device_id_str = &ids[colon_pos + 1..]; + if let Ok(device_id) = u32::from_str_radix(device_id_str, 16) { + // Extract the GPU name between ": " and the last "[" + if let Some(name_start) = line.find(": ") { + let full_name = line[name_start + 2..ids_start].trim(); + let gpu_name = full_name.replace(" Corporation", "").replace(" [Intel Graphics]", ""); + if !gpu_name.is_empty() { + gpu_map.insert(device_id, gpu_name); + } + } + } + } + } + } + } + + gpu_map +} diff --git a/cosmic-settings/src/pages/system/mod.rs b/cosmic-settings/src/pages/system/mod.rs index 510b136..65f059f 100644 --- a/cosmic-settings/src/pages/system/mod.rs +++ b/cosmic-settings/src/pages/system/mod.rs @@ -4,6 +4,8 @@ #[cfg(feature = "page-about")] pub mod about; +#[cfg(feature = "page-about")] +pub mod info; #[cfg(feature = "page-users")] pub mod users; diff --git a/pages/system/Cargo.toml b/pages/system/Cargo.toml deleted file mode 100644 index 281dd7a..0000000 --- a/pages/system/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "cosmic-settings-system" -version = "1.0.0-beta6" -edition = "2024" -license = "GPL-3.0-only" -rust-version.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -byte-unit = "5.1.6" -const_format = "0.2.35" -concat-in-place = "1.1.0" -sysinfo = "0.36.1" -memchr = "2.7.6" - -[dependencies.bumpalo] -version = "3.19.0" -features = ["collections"] diff --git a/pages/system/src/about.rs b/pages/system/src/about.rs deleted file mode 100644 index ed1ef22..0000000 --- a/pages/system/src/about.rs +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -use bumpalo::Bump; -use std::{collections::HashSet, ffi::OsStr, io::Read}; - -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"); - -const VERSION_IGNORING_PRODUCTS: &[&str] = &["Dev One"]; - -#[must_use] -#[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 kernel_version: 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(); - - kernel_version(&bump, &mut info.kernel_version); - 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(); - - let disks = sysinfo::Disks::new_with_refreshed_list(); - - sys.refresh_memory(); - - let mut total_capacity = 0; - let mut disk_set = HashSet::new(); - for disk in disks.list() { - if disk_set.contains(disk.name()) { - continue; - } - disk_set.insert(disk.name()); - total_capacity += disk.total_space(); - } - - info.disk_capacity = format_size(total_capacity); - - if let Some(name) = sysinfo::System::host_name() { - info.device_name = name; - } - - info.memory = format_size(sys.total_memory()); - - 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; - } - - // prefer XDG_SESSION_DESKTOP because the value is singular: https://www.freedesktop.org/software/systemd/man/latest/pam_systemd.html - if let Ok(mut session) = std::env::var("XDG_SESSION_DESKTOP") - // otherwise, XDG_CURRENT_DESKTOP (could be plural): https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html - .or_else(|_| std::env::var("XDG_CURRENT_DESKTOP")) - // fallback to legacy environment variable - .or_else(|_| 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 kernel_version(bump: &Bump, kernel: &mut String) { - let buffer = &mut bumpalo::collections::Vec::new_in(bump); - if let Some(value) = read_to_string("/proc/version", buffer) { - if let Some(version) = value.split_whitespace().nth(2) { - kernel.push_str(version.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, - }; - - let _str = 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(); - - if !version.is_empty() && !VERSION_IGNORING_PRODUCTS.contains(&name) { - let _str = strcat!(hardware_model, " (" version.trim() ")"); - } - } - } - } else { - // simple fallback to sysinfo if DMI information is not available - hardware_model.push_str(&sysinfo::Product::name().unwrap_or("".to_string())); - } -} - -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()); - return; - } - - break; - } - } - } - - // fallback to sysinfo if /proc/cpuinfo is not present - let s = sysinfo::System::new_with_specifics( - sysinfo::RefreshKind::nothing().with_cpu(sysinfo::CpuRefreshKind::everything()), - ); - name.push_str(s.cpus().iter().next().unwrap().brand()); -} - -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() { - if let Ok(len) = usize::try_from(metadata.len()) { - buffer.reserve_exact(len); - } - } - - 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() -} - -fn format_size(size: u64) -> String { - format!( - "{:.2}", - byte_unit::Byte::from_u64(size).get_appropriate_unit(byte_unit::UnitType::Binary) - ) -} diff --git a/pages/system/src/lib.rs b/pages/system/src/lib.rs deleted file mode 100644 index 2548b98..0000000 --- a/pages/system/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -pub mod about;