From ba7d5398ae24221d671a8b28ce53f7286b9ed7c5 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:27:00 +0200 Subject: [PATCH 01/22] meta: remove GitLab CI configuration and issue templates --- .gitlab-ci.yml | 99 ------------------------------ .gitlab/issue_templates/default.md | 32 ---------- 2 files changed, 131 deletions(-) delete mode 100644 .gitlab-ci.yml delete mode 100644 .gitlab/issue_templates/default.md diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index a126c85e..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,99 +0,0 @@ -image: rust:latest - -# Use shallow clone to reduce checkout size -variables: - GIT_DEPTH: "1" - # Put cargo home and target under project dir so we can clean them easily - CARGO_HOME: "$CI_PROJECT_DIR/.cargo" - CARGO_TARGET_DIR: "$CI_PROJECT_DIR/ci-target" - GIT_SUBMODULE_STRATEGY: normal - -# Cache only cargo registries/git metadata to speed dependency fetches. -# Avoid caching compiled `target` artifacts which are large and easily fill disk. -.rust_cache: &rust_cache - cache: - key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG" - paths: - - .cargo/registry - - .cargo/git - -before_script: - - df -h - - echo "Cleaning stale targets to free space if present" - - rm -rf "$CI_PROJECT_DIR/target" "$CI_PROJECT_DIR/ci-target" || true - - apt-get update -qq && apt-get install -y -qq libudev-dev libgtk-3-dev grep llvm clang libclang-dev libsdl2-dev libsdl2-gfx-dev - -stages: - - format - - check - - test - - release - - deploy - -format: - except: - - tags - <<: *rust_cache - script: - - rustup component add rustfmt || true - - cargo fmt --check - after_script: - - du -sh "$CI_PROJECT_DIR/ci-target" || true - - rm -rf "$CI_PROJECT_DIR/ci-target" || true - -check: - except: - - tags - <<: *rust_cache - script: - - rustup component add clippy || true - - cargo check --locked --workspace - # deny currently catches too much - #- cargo install cargo-deny && cargo deny - - cargo install cargo-cranky && cargo cranky - after_script: - - rm -rf "$CI_PROJECT_DIR/ci-target" || true - -test: - except: - - tags - <<: *rust_cache - script: - - mkdir -p .git/hooks > /dev/null - - cargo test --locked --all - after_script: - - rm -rf "$CI_PROJECT_DIR/ci-target" || true - -release: - only: - - tags - <<: *rust_cache - script: - - cargo install cargo-vendor-filterer - - cargo fetch - - make FROZEN=1 && make vendor - artifacts: - paths: - - vendor_asusctl*.tar.xz - - cargo-config - expire_in: 1 week - after_script: - - rm -rf vendor vendor_asusctl*.tar.xz "$CI_PROJECT_DIR/ci-target" || true - -pages: - stage: deploy - only: - - tags - <<: *rust_cache - script: - - cargo doc --locked --document-private-items --no-deps --workspace - - rm -rf public - - mkdir public - - cp -R ci-target/doc/* public - - if [ -f extra/index.html ]; then cp extra/index.html public; else echo "no extra/index.html to copy"; fi - artifacts: - paths: - - public - expire_in: 1 week - after_script: - - rm -rf "$CI_PROJECT_DIR/ci-target" || true diff --git a/.gitlab/issue_templates/default.md b/.gitlab/issue_templates/default.md deleted file mode 100644 index d53e4e74..00000000 --- a/.gitlab/issue_templates/default.md +++ /dev/null @@ -1,32 +0,0 @@ -## Issue description - -(** I can not support distros which are outdated by default. This includes Ubuntu at least 50% of the time, and definitely includes Mint. **) -(Summarize the bug encountered) - -## Steps to reproduce - -(How can the issue be reproduced) - -## What is the current bug behavior? - -(What actually happens) - -## What is the expected correct behavior? - -(What you should see instead) - -## Relevant logs and/or screenshots - -(run `journalctl -b -u asusd > ~/asusd.log` and attach `~/asusd.log`) - -(Paste any relevant logs - use code blocks (```) to format console output, logs, and code, as -it's very hard to read otherwise.) - -## System details - -- Distro: -- Kernel: (`uname -r`) -- Desktop: -- Xorg or wayland: ?? - -/label ~bug ~reproducable ~needs-investigation From 26541ed164c08be04d7af272e5e337468c590b79 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:27:04 +0200 Subject: [PATCH 02/22] chore: remove supergfxctl dependency from packaging files --- distro-packaging/PKGBUILD | 1 - distro-packaging/asusctl.install | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/distro-packaging/PKGBUILD b/distro-packaging/PKGBUILD index 13f68997..7daf1180 100644 --- a/distro-packaging/PKGBUILD +++ b/distro-packaging/PKGBUILD @@ -16,7 +16,6 @@ depends=('libusb' 'udev') optdepends=( 'ASUS-WMI-FAN-CONTROL: custom fan curve support' 'linux-rog: deprecated name for custom fan curve capability' - 'supergfxctl: graphics swithing for iGPU + dGPU laptops' ) makedepends=('git' 'rust' 'llvm' 'clang' 'at-spi2-core' 'cairo' 'gtk3') provides=() diff --git a/distro-packaging/asusctl.install b/distro-packaging/asusctl.install index bc78c55f..dee5515f 100644 --- a/distro-packaging/asusctl.install +++ b/distro-packaging/asusctl.install @@ -4,10 +4,9 @@ post_upgrade() { if [[ $2 == 3* ]]; then echo -e "\033[0;34m" echo "-> Upgrading from asusctl 3.X.X" - echo "-> Enabling power-profiles-daemon and supergfxd services" + echo "-> Enabling power-profiles-daemon service" echo -e "\033[0m" systemctl enable --now power-profiles-daemon.service - systemctl enable --now supergfxd.service if [[ -f "/etc/asusd/asusd.conf" ]]; then echo -e "\033[0;33m" From 539587e0c019b85917bf77de924c09b00ec72556 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:27:09 +0200 Subject: [PATCH 03/22] refactor: remove unsafe blocks in rog-aura, rog-profiles, and rog-platform --- rog-aura/src/effects/mod.rs | 9 ++++----- rog-platform/src/platform.rs | 8 +++----- rog-profiles/src/fan_curve_set.rs | 19 ++----------------- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/rog-aura/src/effects/mod.rs b/rog-aura/src/effects/mod.rs index a9f7f6dd..c5900d09 100644 --- a/rog-aura/src/effects/mod.rs +++ b/rog-aura/src/effects/mod.rs @@ -15,8 +15,9 @@ pub use static_::*; use crate::keyboard::{AuraLaptopUsbPackets, KeyLayout, LedCode, LedUsbPackets}; use crate::Colour; +use std::sync::atomic::{AtomicUsize, Ordering}; // static mut RNDINDEX: usize = 0; -static mut PRNDINDEX: usize = 0; +static PRNDINDEX: AtomicUsize = AtomicUsize::new(0); /// Pseudo random table ripped straight out of room4doom pub const RNDTABLE: [i32; 256] = [ @@ -36,10 +37,8 @@ pub const RNDTABLE: [i32; 256] = [ ]; pub fn p_random() -> i32 { - unsafe { - PRNDINDEX = (PRNDINDEX + 1) & 0xff; - RNDTABLE[PRNDINDEX] - } + let idx = (PRNDINDEX.fetch_add(1, Ordering::Relaxed) + 1) & 0xff; + RNDTABLE[idx] } pub trait InputForEffect { diff --git a/rog-platform/src/platform.rs b/rog-platform/src/platform.rs index 0dbdc724..5b44838b 100644 --- a/rog-platform/src/platform.rs +++ b/rog-platform/src/platform.rs @@ -70,11 +70,9 @@ impl RogPlatform { impl Default for RogPlatform { fn default() -> Self { - unsafe { - Self { - path: PathBuf::from_str("/this_shouldNeVErr_exisid").unwrap_unchecked(), - pp_path: PathBuf::from_str("/this_shouldNeVErr_exisid").unwrap_unchecked(), - } + Self { + path: PathBuf::new(), + pp_path: PathBuf::new(), } } } diff --git a/rog-profiles/src/fan_curve_set.rs b/rog-profiles/src/fan_curve_set.rs index 79fd6990..c754c910 100644 --- a/rog-profiles/src/fan_curve_set.rs +++ b/rog-profiles/src/fan_curve_set.rs @@ -7,27 +7,12 @@ use zbus::zvariant::Type; use crate::error::ProfileError; use crate::FanCurvePU; -fn set_sysfs_name(string: &mut [u8], fan: char, index: usize) { - string[3] = fan as u8; - string[15] = char::from_digit(index as u32 + 1, 10).unwrap() as u8; -} - pub(crate) fn pwm_str(fan: char, index: usize) -> String { - // The char 'X' is replaced via indexing - let mut string = "pwmX_auto_pointX_pwm".to_owned(); - unsafe { - set_sysfs_name(string.as_bytes_mut(), fan, index); - } - string + format!("pwm{fan}_auto_point{}_pwm", index + 1) } pub(crate) fn temp_str(fan: char, index: usize) -> String { - // The char 'X' is replaced via indexing - let mut string = "pwmX_auto_pointX_temp".to_owned(); - unsafe { - set_sysfs_name(string.as_bytes_mut(), fan, index); - } - string + format!("pwm{fan}_auto_point{}_temp", index + 1) } #[cfg_attr(feature = "dbus", derive(Type))] From 883ebbc6582b01a64ec78341ae91f7e95ac2fbeb Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:27:14 +0200 Subject: [PATCH 04/22] fix: prevent string slicing panics in Colour::from_str --- rog-aura/src/builtin_modes.rs | 2 +- rog-scsi/src/builtin_modes.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rog-aura/src/builtin_modes.rs b/rog-aura/src/builtin_modes.rs index 477adca7..80e397a5 100644 --- a/rog-aura/src/builtin_modes.rs +++ b/rog-aura/src/builtin_modes.rs @@ -95,7 +95,7 @@ impl FromStr for Colour { type Err = Error; fn from_str(s: &str) -> Result { - if s.len() < 6 { + if s.len() < 6 || !s.chars().take(6).all(|c| c.is_ascii_hexdigit()) { return Err(Error::ParseColour); } let r = u8::from_str_radix(&s[0..2], 16).or(Err(Error::ParseColour))?; diff --git a/rog-scsi/src/builtin_modes.rs b/rog-scsi/src/builtin_modes.rs index f1c95a80..fb2ed22e 100644 --- a/rog-scsi/src/builtin_modes.rs +++ b/rog-scsi/src/builtin_modes.rs @@ -26,7 +26,7 @@ impl FromStr for Colour { type Err = Error; fn from_str(s: &str) -> Result { - if s.len() < 6 { + if s.len() < 6 || !s.chars().take(6).all(|c| c.is_ascii_hexdigit()) { return Err(Error::ParseColour); } let r = u8::from_str_radix(&s[0..2], 16).or(Err(Error::ParseColour))?; From 0ebd7c6c6676651a87dff47ea952ad43bdc3e960 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:27:18 +0200 Subject: [PATCH 05/22] fix(rog-aura): improve LED config error handling and power state conversion safety --- rog-aura/src/aura_detection.rs | 49 +++++++++++++++++----------------- rog-aura/src/keyboard/power.rs | 45 ++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/rog-aura/src/aura_detection.rs b/rog-aura/src/aura_detection.rs index db558653..81acb13a 100644 --- a/rog-aura/src/aura_detection.rs +++ b/rog-aura/src/aura_detection.rs @@ -147,15 +147,19 @@ impl LedSupportFile { if file.is_empty() { warn!("{} is empty", ASUS_LED_MODE_CONF); } else { - let mut tmp: LedSupportFile = ron::from_str(&file) - .map_err(|e| error!("{e}")) - .unwrap_or_else(|_| panic!("Could not deserialise {}", ASUS_LED_MODE_CONF)); - data.0.append(&mut tmp.0); - loaded = true; - info!( - "Loaded default LED support data from {}", - ASUS_LED_MODE_CONF - ); + match ron::from_str::(&file) { + Ok(mut tmp) => { + data.0.append(&mut tmp.0); + loaded = true; + info!( + "Loaded default LED support data from {}", + ASUS_LED_MODE_CONF + ); + } + Err(e) => { + error!("Could not deserialise {}: {}", ASUS_LED_MODE_CONF, e); + } + } } } data.0.sort_by(|a, b| a.device_name.cmp(&b.device_name)); @@ -227,13 +231,13 @@ mod tests { } #[test] - fn check_data_file_parse() { + fn check_data_file_parse() -> Result<(), Box> { let mut data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); data.push("data/aura_support.ron"); - let buf = std::fs::read_to_string(&data).unwrap(); + let buf = std::fs::read_to_string(&data)?; - let tmp = ron::from_str::(&buf).unwrap(); + let tmp = ron::from_str::(&buf)?; // Ensure the data is sorted let mut tmp_sort = tmp.clone(); @@ -243,15 +247,13 @@ mod tests { model.basic_modes.sort_by_key(|a| *a as u8); } if tmp != tmp_sort { - let sorted = - ron::ser::to_string_pretty(&tmp_sort, PrettyConfig::new().depth_limit(2)).unwrap(); + let sorted = ron::ser::to_string_pretty(&tmp_sort, PrettyConfig::new().depth_limit(2))?; let mut file = OpenOptions::new() .write(true) .create(true) .truncate(true) - .open(&data) - .unwrap(); - file.write_all(sorted.as_bytes()).unwrap(); + .open(&data)?; + file.write_all(sorted.as_bytes())?; panic!( "aura_support.ron not sorted, should be {sorted}. File rewritten with correct \ order, run test again" @@ -259,20 +261,18 @@ mod tests { } let my_config = PrettyConfig::new().depth_limit(2); - println!( - "RON: {}", - ron::ser::to_string_pretty(&tmp, my_config).unwrap() - ); + println!("RON: {}", ron::ser::to_string_pretty(&tmp, my_config)?); + Ok(()) } #[test] - fn find_data_file_groups() { + fn find_data_file_groups() -> Result<(), Box> { let mut data = PathBuf::from(env!("CARGO_MANIFEST_DIR")); data.push("data/aura_support.ron"); - let buf = std::fs::read_to_string(&data).unwrap(); + let buf = std::fs::read_to_string(&data)?; - let tmp = ron::from_str::(&buf).unwrap(); + let tmp = ron::from_str::(&buf)?; let mut modes: HashMap, Vec> = HashMap::new(); @@ -284,6 +284,7 @@ mod tests { } } dbg!(modes); + Ok(()) // let my_config = PrettyConfig::new().depth_limit(2); // println!( diff --git a/rog-aura/src/keyboard/power.rs b/rog-aura/src/keyboard/power.rs index 1d55f0f8..eb8aa24b 100644 --- a/rog-aura/src/keyboard/power.rs +++ b/rog-aura/src/keyboard/power.rs @@ -218,8 +218,12 @@ impl LaptopAuraPower { AuraDeviceType::LaptopKeyboardTuf => Self { states: vec![AuraPowerState::default_for(PowerZones::Keyboard)], }, - AuraDeviceType::ScsiExtDisk => todo!(), - AuraDeviceType::AnimeOrSlash => todo!(), + AuraDeviceType::ScsiExtDisk | AuraDeviceType::AnimeOrSlash => { + unreachable!( + "Power state initialization is unreachable for non-keyboard device type: {:?}", + aura_type + ); + } } } @@ -238,22 +242,29 @@ impl LaptopAuraPower { match aura_type { AuraDeviceType::LaptopKeyboard2021 | AuraDeviceType::Ally => self.new_to_bytes(), AuraDeviceType::LaptopKeyboardPre2021 => { - if self.states.len() == 1 { - self.states - .first() - .cloned() - .unwrap_or_default() - .old_to_bytes() + if self.states.is_empty() { + vec![ + 0x00, 0x00, 0x00, 0x00, + ] + } else if self.states.len() == 1 { + self.states[0].old_to_bytes() } else { let mut bytes: Vec> = self.states.iter().map(|s| s.old_to_bytes()).collect(); - let mut b = bytes.pop().unwrap(); - for i in bytes { - for (i, n) in i.iter().enumerate() { - b[i] |= n; + if let Some(mut b) = bytes.pop() { + for i in bytes { + for (idx, n) in i.iter().enumerate() { + if idx < b.len() { + b[idx] |= n; + } + } } + b + } else { + vec![ + 0x00, 0x00, 0x00, 0x00, + ] } - b } } AuraDeviceType::LaptopKeyboardTuf => self @@ -266,8 +277,12 @@ impl LaptopAuraPower { warn!("Trying to create bytes for an unknown device"); self.new_to_bytes() } - AuraDeviceType::ScsiExtDisk => todo!("scsi disk not implemented yet"), - AuraDeviceType::AnimeOrSlash => todo!("anime/slash not implemented yet"), + AuraDeviceType::ScsiExtDisk | AuraDeviceType::AnimeOrSlash => { + unreachable!( + "Power state bytes calculation is unreachable for non-keyboard device type: {:?}", + aura_type + ); + } } } } From 32cf328ae0a8c0db0deaddc0167cfbc12d0a7077 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:27:22 +0200 Subject: [PATCH 06/22] fix(rog-profiles): eliminate panics, out-of-bounds access, and add warning logs in sysfs fan curve parsing --- rog-profiles/src/fan_curve_set.rs | 37 ++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/rog-profiles/src/fan_curve_set.rs b/rog-profiles/src/fan_curve_set.rs index c754c910..f2f3c190 100644 --- a/rog-profiles/src/fan_curve_set.rs +++ b/rog-profiles/src/fan_curve_set.rs @@ -1,4 +1,4 @@ -use log::{error, trace}; +use log::{error, trace, warn}; use serde::{Deserialize, Serialize}; use udev::Device; #[cfg(feature = "dbus")] @@ -123,10 +123,26 @@ impl CurveData { fn set_val_from_attr(tmp: &str, device: &Device, buf: &mut [u8; 8]) { if let Some(n) = tmp.chars().nth(15) { - let i = n.to_digit(10).unwrap() as usize; - let d = device.attribute_value(tmp).unwrap(); - let d: u8 = d.to_string_lossy().parse().unwrap(); - buf[i - 1] = d; + if let Some(digit) = n.to_digit(10) { + let i = digit as usize; + if (1..=8).contains(&i) { + if let Some(val_str) = device.attribute_value(tmp) { + if let Ok(d) = val_str.to_string_lossy().trim().parse::() { + buf[i - 1] = d; + } else { + warn!("Failed to parse attribute {tmp} value into u8"); + } + } else { + warn!("Attribute {tmp} value missing on device"); + } + } else { + warn!("Point index {i} out of expected range 1..=8 for attribute {tmp}"); + } + } else { + warn!("Character at position 15 in attribute {tmp} was not a digit"); + } + } else { + warn!("Attribute name {tmp} too short to extract point index at pos 15"); } } @@ -177,10 +193,9 @@ mod tests { use super::*; #[test] - fn curve_data_from_str_to_str() { + fn curve_data_from_str_to_str() -> Result<(), Box> { let mut curve = - CurveData::from_str("30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%") - .unwrap(); + CurveData::from_str("30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%")?; curve.enabled = true; assert_eq!(curve.fan, FanCurvePU::CPU); assert_eq!(curve.temp, [30, 49, 59, 69, 79, 89, 99, 109]); @@ -196,14 +211,16 @@ mod tests { let curve = CurveData::from_str("30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%"); assert!(curve.is_err()); + Ok(()) } #[test] - fn curve_data_from_str_simple() { - let curve = CurveData::from_str("30:1,49:2,59:3,69:4,79:31,89:49,99:56,109:58").unwrap(); + fn curve_data_from_str_simple() -> Result<(), Box> { + let curve = CurveData::from_str("30:1,49:2,59:3,69:4,79:31,89:49,99:56,109:58")?; assert_eq!(curve.fan, FanCurvePU::CPU); assert_eq!(curve.temp, [30, 49, 59, 69, 79, 89, 99, 109]); assert_eq!(curve.pwm, [1, 2, 3, 4, 31, 49, 56, 58]); + Ok(()) } #[test] From ca58bc4076b9d2d9e48a6efcc25d0ece4fc50620 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:27:25 +0200 Subject: [PATCH 07/22] fix(rog-platform): improve CPU EPP optional handling, PCI slots check, and remove unwrap calls --- .../examples/ally-gamepad-calibration.rs | 6 +-- .../examples/ally-gamepad-mode-changes.rs | 6 +-- .../examples/ally-set-qam-secondary.rs | 6 +-- rog-platform/src/asus_armoury.rs | 5 +- rog-platform/src/cpu.rs | 49 ++++++++++++------- rog-platform/src/gpu_pci.rs | 3 ++ rog-platform/src/power.rs | 43 +++++++--------- 7 files changed, 64 insertions(+), 54 deletions(-) diff --git a/rog-platform/examples/ally-gamepad-calibration.rs b/rog-platform/examples/ally-gamepad-calibration.rs index b716cdf6..6076ffa6 100644 --- a/rog-platform/examples/ally-gamepad-calibration.rs +++ b/rog-platform/examples/ally-gamepad-calibration.rs @@ -13,11 +13,9 @@ pub fn main() -> Result<(), Box> { Err(err) => println!("Looked for keyboard controller: {err}"), } - if usb_node.is_none() { + let Some(node) = usb_node else { return Err("RogError::NoAuraKeyboard".into()); - } - - let node = usb_node.unwrap(); + }; let mut packet: [u8; 64] = [ 0x5a, 0xd1, 0x0d, 0x0e, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/rog-platform/examples/ally-gamepad-mode-changes.rs b/rog-platform/examples/ally-gamepad-mode-changes.rs index acc63455..58b781dd 100644 --- a/rog-platform/examples/ally-gamepad-mode-changes.rs +++ b/rog-platform/examples/ally-gamepad-mode-changes.rs @@ -15,11 +15,9 @@ pub fn main() -> Result<(), Box> { Err(err) => println!("Looked for keyboard controller: {err}"), } - if usb_node.is_none() { + let Some(node) = usb_node else { return Err("RogError::NoAuraKeyboard".into()); - } - - let node = usb_node.unwrap(); + }; // node.write_bytes(&[0x5a, 0xd1, 0x0a, 0x01])?; // TODO: need to CHECK println!("Set mouse mode for 10 seconds"); diff --git a/rog-platform/examples/ally-set-qam-secondary.rs b/rog-platform/examples/ally-set-qam-secondary.rs index c8fce187..16a2585a 100644 --- a/rog-platform/examples/ally-set-qam-secondary.rs +++ b/rog-platform/examples/ally-set-qam-secondary.rs @@ -13,11 +13,9 @@ pub fn main() -> Result<(), Box> { Err(err) => println!("Looked for keyboard controller: {err}"), } - if usb_node.is_none() { + let Some(node) = usb_node else { return Err("RogError::NoAuraKeyboard".into()); - } - - let node = usb_node.unwrap(); + }; // node.write_bytes(&[0x5a, 0xd1, 0x0a, 0x01])?; // TODO: need to CHECK println!("Set mouse mode for 10 seconds"); diff --git a/rog-platform/src/asus_armoury.rs b/rog-platform/src/asus_armoury.rs index 1265b518..c415163d 100644 --- a/rog-platform/src/asus_armoury.rs +++ b/rog-platform/src/asus_armoury.rs @@ -219,7 +219,10 @@ impl FirmwareAttributes { if let Ok(dir) = read_dir(BASE_DIR) { for entry in dir.flatten() { let base_path = entry.path(); - let name = base_path.file_name().unwrap().to_string_lossy().to_string(); + let Some(name_os) = base_path.file_name() else { + continue; + }; + let name = name_os.to_string_lossy().to_string(); if name == "pending_reboot" { continue; } diff --git a/rog-platform/src/cpu.rs b/rog-platform/src/cpu.rs index 04cfca5b..08059215 100644 --- a/rog-platform/src/cpu.rs +++ b/rog-platform/src/cpu.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use log::{info, warn}; +use log::{debug, info, warn}; use serde::{Deserialize, Serialize}; use zbus::zvariant::{OwnedValue, Type, Value}; @@ -56,17 +56,16 @@ impl CPUControl { Some(g) => info!("{ATTR_GOVERNOR}: {g:?}"), None => return Err(PlatformError::CPU(format!("{ATTR_GOVERNOR} not found"))), } + // EPP attributes (energy_performance_available_preferences and energy_performance_preference) + // are optional depending on the cpufreq driver and kernel version. Missing EPP attributes + // are logged as warnings rather than hard errors to support broader hardware configurations. match device.attribute_value(ATTR_AVAILABLE_EPP) { Some(g) => info!("{ATTR_AVAILABLE_EPP}: {g:?}"), - None => { - return Err(PlatformError::CPU(format!( - "{ATTR_AVAILABLE_EPP} not found" - ))) - } + None => warn!("{ATTR_AVAILABLE_EPP} not found (EPP controls unsupported)"), } match device.attribute_value(ATTR_EPP) { Some(g) => info!("{ATTR_EPP}: {g:?}"), - None => return Err(PlatformError::CPU(format!("{ATTR_EPP} not found"))), + None => warn!("{ATTR_EPP} not found (EPP preference setting unsupported)"), } supported = true; } @@ -124,18 +123,33 @@ impl CPUControl { } } + /// Returns available EPP (Energy Performance Preference) options. + /// EPP sysfs attributes are optional as not all CPU scaling drivers expose them. pub fn get_available_epp(&self) -> Result> { if let Some(path) = self.paths.first() { - read_attr_string(&to_device(path)?, ATTR_AVAILABLE_EPP) - .map(|s| s.split_whitespace().map(|s| s.into()).collect()) - // TODO: check cpu are sync + match read_attr_string(&to_device(path)?, ATTR_AVAILABLE_EPP) { + Ok(s) => Ok(s.split_whitespace().map(|s| s.into()).collect()), + Err(err) => { + debug!("Reading {ATTR_AVAILABLE_EPP} failed or attribute not present: {err}"); + Err(err) + } + } } else { Err(PlatformError::CPU("No CPU's?".to_string())) } } + /// Sets EPP (Energy Performance Preference) for all CPUs. + /// Checks available preferences first; if unsupported or invalid, returns an error early. pub fn set_epp(&self, epp: CPUEPP) -> Result<()> { - if !self.get_available_epp()?.contains(&epp) { + let available = match self.get_available_epp() { + Ok(avail) => avail, + Err(err) => { + debug!("EPP preference setting unsupported on this system: {err}"); + return Err(err); + } + }; + if !available.contains(&epp) { return Err(PlatformError::CPU(format!("{epp:?} is not available"))); } for path in &self.paths { @@ -392,20 +406,20 @@ mod tests { #[test] #[ignore = "Can't run this in a docker image"] - fn check_cpu() { - let cpu = CPUControl::new().unwrap(); - assert_eq!(cpu.get_governor().unwrap(), CPUGovernor::Powersave); + fn check_cpu() -> std::result::Result<(), Box> { + let cpu = CPUControl::new()?; + assert_eq!(cpu.get_governor()?, CPUGovernor::Powersave); assert_eq!( - cpu.get_available_governors().unwrap(), + cpu.get_available_governors()?, vec![ CPUGovernor::Performance, CPUGovernor::Powersave ] ); - assert_eq!(cpu.get_epp().unwrap(), CPUEPP::BalancePower); + assert_eq!(cpu.get_epp()?, CPUEPP::BalancePower); assert_eq!( - cpu.get_available_epp().unwrap(), + cpu.get_available_epp()?, vec![ CPUEPP::Default, CPUEPP::Performance, @@ -414,5 +428,6 @@ mod tests { CPUEPP::Power, ] ); + Ok(()) } } diff --git a/rog-platform/src/gpu_pci.rs b/rog-platform/src/gpu_pci.rs index 34a09e5d..882afd80 100644 --- a/rog-platform/src/gpu_pci.rs +++ b/rog-platform/src/gpu_pci.rs @@ -408,6 +408,9 @@ pub fn find_connected_displays(gpu_path: &Path) -> Result> { pub fn find_slot_power(address: &str) -> Result { let mut buf = Vec::new(); let path = PathBuf::from(SLOTS); + if !path.exists() { + return Err(PlatformError::NotSupported); + } for path in path.read_dir().map_err(PlatformError::Io)? { let path = path.map_err(PlatformError::Io)?.path(); diff --git a/rog-platform/src/power.rs b/rog-platform/src/power.rs index a114921b..ea53d3ac 100644 --- a/rog-platform/src/power.rs +++ b/rog-platform/src/power.rs @@ -361,17 +361,17 @@ mod tests { use std::path::PathBuf; #[test] - fn test_battery_methods() { + fn test_battery_methods() -> std::result::Result<(), Box> { let temp_dir = std::env::temp_dir().join("fake_battery"); - fs::create_dir_all(&temp_dir).unwrap(); + fs::create_dir_all(&temp_dir)?; // Write fake files - fs::write(temp_dir.join("cycle_count"), "42\n").unwrap(); - fs::write(temp_dir.join("energy_full"), "80000000\n").unwrap(); - fs::write(temp_dir.join("energy_full_design"), "100000000\n").unwrap(); - fs::write(temp_dir.join("energy_now"), "45000000\n").unwrap(); - fs::write(temp_dir.join("power_now"), "15000000\n").unwrap(); - fs::write(temp_dir.join("status"), "Discharging\n").unwrap(); + fs::write(temp_dir.join("cycle_count"), "42\n")?; + fs::write(temp_dir.join("energy_full"), "80000000\n")?; + fs::write(temp_dir.join("energy_full_design"), "100000000\n")?; + fs::write(temp_dir.join("energy_now"), "45000000\n")?; + fs::write(temp_dir.join("power_now"), "15000000\n")?; + fs::write(temp_dir.join("status"), "Discharging\n")?; let power = AsusPower { mains: PathBuf::new(), @@ -380,25 +380,20 @@ mod tests { }; assert!(power.has_battery()); - assert_eq!(power.get_battery_cycle_count().unwrap(), 42); - assert_eq!(power.get_battery_health().unwrap(), 80); - assert_eq!(power.get_battery_power_consumption().unwrap(), 15.0); - assert_eq!(power.get_battery_status().unwrap(), "Discharging"); - assert_eq!(power.get_battery_remaining_energy_wh().unwrap(), 45.0); - assert_eq!(power.get_battery_full_energy_wh().unwrap(), 80.0); - assert_eq!( - power.get_battery_time_estimate().unwrap(), - Some((false, 3, 0)) - ); + assert_eq!(power.get_battery_cycle_count()?, 42); + assert_eq!(power.get_battery_health()?, 80); + assert_eq!(power.get_battery_power_consumption()?, 15.0); + assert_eq!(power.get_battery_status()?, "Discharging"); + assert_eq!(power.get_battery_remaining_energy_wh()?, 45.0); + assert_eq!(power.get_battery_full_energy_wh()?, 80.0); + assert_eq!(power.get_battery_time_estimate()?, Some((false, 3, 0))); // Test charging estimation - fs::write(temp_dir.join("status"), "Charging\n").unwrap(); - assert_eq!( - power.get_battery_time_estimate().unwrap(), - Some((true, 2, 20)) - ); + fs::write(temp_dir.join("status"), "Charging\n")?; + assert_eq!(power.get_battery_time_estimate()?, Some((true, 2, 20))); // Clean up - fs::remove_dir_all(&temp_dir).ok(); + let _ = fs::remove_dir_all(temp_dir); + Ok(()) } } From 3192d877255671981149b72998e25ea4a3f0aab6 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:27:29 +0200 Subject: [PATCH 08/22] fix(config-traits): implement atomic file writes and recursive directory creation --- config-traits/src/lib.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/config-traits/src/lib.rs b/config-traits/src/lib.rs index 144361a0..c5b1bd29 100644 --- a/config-traits/src/lib.rs +++ b/config-traits/src/lib.rs @@ -5,7 +5,7 @@ //! //! The end canonical file format is `.ron` as this supports rust types well -use std::fs::{self, create_dir, File, OpenOptions}; +use std::fs::{self, File, OpenOptions}; use std::io::{Read, Write}; use std::path::PathBuf; @@ -34,7 +34,7 @@ where fn file_path(&self) -> PathBuf { let mut config = Self::config_dir(); if !config.exists() { - create_dir(config.as_path()) + fs::create_dir_all(config.as_path()) .unwrap_or_else(|e| panic!("Could not create {:?} {e}", Self::config_dir())); } config.push(self.file_name()); @@ -123,25 +123,37 @@ where /// Write the config file data to pretty ron format fn write(&self) { - let mut file = match File::create(self.file_path()) { + let path = self.file_path(); + let ron = match ron::ser::to_string_pretty(&self, PrettyConfig::new().depth_limit(4)) { Ok(data) => data, Err(e) => { - error!( - "Couldn't overwrite config {:?}, error: {e}", - self.file_path() - ); + error!("Parse {:?} to RON failed, error: {e}", path); return; } }; - let ron = match ron::ser::to_string_pretty(&self, PrettyConfig::new().depth_limit(4)) { + + let tmp_path = path.with_extension("ron.tmp"); + let mut file = match File::create(&tmp_path) { Ok(data) => data, Err(e) => { - error!("Parse {:?} to RON failed, error: {e}", self.file_path()); + error!("Couldn't create temp config {:?}, error: {e}", tmp_path); return; } }; - file.write_all(ron.as_bytes()) - .unwrap_or_else(|err| error!("Could not write config: {}", err)); + + if let Err(e) = file.write_all(ron.as_bytes()) { + error!("Could not write temp config {:?}: {e}", tmp_path); + let _ = fs::remove_file(&tmp_path); + return; + } + + if let Err(e) = fs::rename(&tmp_path, &path) { + error!( + "Could not rename temp config {:?} to {:?}: {e}", + tmp_path, path + ); + let _ = fs::remove_file(&tmp_path); + } } /// Renames the existing file to `-old` From 95f2b669c72ff29199d79fbf3beb2ee41a40997d Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:33:29 +0200 Subject: [PATCH 09/22] fix(rog-control-center): handle missing tray assets, D-Bus errors, and eliminate unwrap calls --- rog-control-center/build.rs | 13 +++--- rog-control-center/src/config.rs | 4 +- rog-control-center/src/main.rs | 19 +++++---- rog-control-center/src/notify.rs | 22 +++++----- rog-control-center/src/tray.rs | 57 +++++++++++++++++--------- rog-control-center/src/zbus_proxies.rs | 14 +++---- 6 files changed, 75 insertions(+), 54 deletions(-) diff --git a/rog-control-center/build.rs b/rog-control-center/build.rs index 862ec444..8887c316 100644 --- a/rog-control-center/build.rs +++ b/rog-control-center/build.rs @@ -1,24 +1,23 @@ use std::path::PathBuf; -use std::str::FromStr; use slint_build::CompilerConfiguration; -fn main() { +fn main() -> Result<(), Box> { // write_locales(); let root = env!("CARGO_MANIFEST_DIR"); - let mut main = PathBuf::from_str(root).unwrap(); + let mut main = PathBuf::from(root); main.push("ui/main_window.slint"); - let mut include = PathBuf::from_str(root).unwrap(); + let mut include = PathBuf::from(root); include.push("ui"); - slint_build::print_rustc_flags().unwrap(); + slint_build::print_rustc_flags()?; slint_build::compile_with_config( main, CompilerConfiguration::new() // .embed_resources(EmbedResourcesKind::EmbedFiles) .with_include_paths(vec![include]) .with_style("fluent".into()), - ) - .unwrap(); + )?; + Ok(()) } diff --git a/rog-control-center/src/config.rs b/rog-control-center/src/config.rs index 67f00ed1..67253f4c 100644 --- a/rog-control-center/src/config.rs +++ b/rog-control-center/src/config.rs @@ -1,4 +1,4 @@ -use std::fs::create_dir; +use std::fs::create_dir_all; use config_traits::{StdConfig, StdConfigLoad1}; use serde::{Deserialize, Serialize}; @@ -63,7 +63,7 @@ impl StdConfig for Config { path.push(CFG_DIR); if !path.exists() { - create_dir(path.clone()) + create_dir_all(path.clone()) .map_err(|e| log::error!("Could not create config dir: {e}")) .ok(); log::info!("Created {path:?}"); diff --git a/rog-control-center/src/main.rs b/rog-control-center/src/main.rs index 0c685acf..1e922de9 100644 --- a/rog-control-center/src/main.rs +++ b/rog-control-center/src/main.rs @@ -8,7 +8,7 @@ use std::time::Duration; use config_traits::{StdConfig, StdConfigLoad1}; use dmi_id::DMIID; use gumdrop::Options; -use log::{debug, info, warn, LevelFilter}; +use log::{debug, error, info, warn, LevelFilter}; use rog_control_center::cli_options::CliStart; use rog_control_center::config::Config; use rog_control_center::error::Result; @@ -74,12 +74,13 @@ async fn main() -> Result<()> { let self_version = env!("CARGO_PKG_VERSION"); let zbus_con = zbus::blocking::Connection::system()?; let platform_proxy = rog_dbus::zbus_platform::PlatformProxyBlocking::new(&zbus_con)?; - let asusd_version = platform_proxy - .version() - .map_err(|e| { - println!("Could not get asusd version: {e:?}\nIs asusd.service running?"); - }) - .unwrap(); + let asusd_version = match platform_proxy.version() { + Ok(v) => v, + Err(e) => { + eprintln!("Could not get asusd version: {e:?}\nIs asusd.service running?"); + std::process::exit(1); + } + }; if asusd_version != self_version { println!("Version mismatch: asusctl = {self_version}, asusd = {asusd_version}"); // return Ok(()); @@ -287,7 +288,9 @@ async fn main() -> Result<()> { } }); - slint::run_event_loop_until_quit().unwrap(); + if let Err(e) = slint::run_event_loop_until_quit() { + error!("Slint event loop error: {e:?}"); + } // Restore the outer Tokio context before awaiting a task owned by the // application runtime, then stop that runtime only after the portal // session has been closed. diff --git a/rog-control-center/src/notify.rs b/rog-control-center/src/notify.rs index fb8320a3..1a814863 100644 --- a/rog-control-center/src/notify.rs +++ b/rog-control-center/src/notify.rs @@ -67,10 +67,11 @@ fn start_dpu_status_mon(config: Arc>) { } // Required check because status cycles through // active/unknown/suspended - do_gpu_status_notif("dGPU status changed:", &status) - .show() - .unwrap() - .on_close(|_| ()); + if let Err(e) = + do_gpu_status_notif("dGPU status changed:", &status).show() + { + warn!("Could not show dGPU status notification: {e}"); + } debug!("dGPU status changed: {:?}", status); } last_status = status; @@ -93,12 +94,13 @@ pub fn start_notifications( // Setup the AC/BAT commands that will run on power status change let config_copy = config.clone(); let blocking = rt.spawn_blocking(move || { - let power = AsusPower::new() - .map_err(|e| { - error!("AsusPower: {e}"); - e - }) - .unwrap(); + let power = match AsusPower::new() { + Ok(p) => p, + Err(e) => { + error!("AsusPower failed to initialize: {e}"); + return; + } + }; let mut last_state = power.get_online().unwrap_or_default(); loop { diff --git a/rog-control-center/src/tray.rs b/rog-control-center/src/tray.rs index 084e0e63..c8ceee03 100644 --- a/rog-control-center/src/tray.rs +++ b/rog-control-center/src/tray.rs @@ -3,8 +3,6 @@ //! The tray icon color reflects the GPU power status, sourced from asusd's //! D-Bus interface (`xyz.ljones.Gpu`). -use std::fs::OpenOptions; -use std::io::Read; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, OnceLock}; @@ -33,16 +31,30 @@ static ICONS: OnceLock = OnceLock::new(); fn read_icon(file: &Path) -> Icon { let mut path = PathBuf::from(TRAY_ICON_PATH); path.push(file); - let mut file = OpenOptions::new() - .read(true) - .open(&path) - .unwrap_or_else(|_| panic!("Missing icon: {:?}", path)); - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes).unwrap(); - - let mut img = image::load_from_memory_with_format(&bytes, image::ImageFormat::Png) - .expect("icon not found") - .to_rgba8(); + let bytes = match std::fs::read(&path) { + Ok(b) => b, + Err(e) => { + log::warn!("Could not read icon {:?}: {e}, using fallback", path); + return Icon { + width: 16, + height: 16, + data: vec![255; 16 * 16 * 4], + }; + } + }; + + let mut img = match image::load_from_memory_with_format(&bytes, image::ImageFormat::Png) { + Ok(i) => i.to_rgba8(), + Err(e) => { + log::warn!("Could not decode icon {:?}: {e}, using fallback", path); + return Icon { + width: 16, + height: 16, + data: vec![255; 16 * 16 * 4], + }; + } + }; + for image::Rgba(pixel) in img.pixels_mut() { // (╯°□°)╯︵ ┻━┻ *pixel = u32::from_be_bytes(*pixel).rotate_right(8).to_be_bytes(); @@ -168,7 +180,13 @@ pub fn init_tray( }); // Connect to asusd's GPU interface on the system bus - let sys_con = zbus::blocking::Connection::system().unwrap(); + let sys_con = match zbus::blocking::Connection::system() { + Ok(c) => c, + Err(e) => { + warn!("Could not connect to system bus: {e}"); + return; + } + }; let gpu_proxy = match GpuStatusProxyBlocking::new(&sys_con) { Ok(p) => p, Err(e) => { @@ -176,12 +194,13 @@ pub fn init_tray( "Could not connect to asusd GPU interface: {e}. \ Is asusd running?" ); - let icons = ICONS.get().unwrap(); - tray.update(|tray: &mut AsusTray| { - tray.current_icon = icons.rog_red.clone(); - tray.current_title = "ROG: GPU status unavailable".to_string(); - }) - .await; + if let Some(icons) = ICONS.get() { + tray.update(|tray: &mut AsusTray| { + tray.current_icon = icons.rog_red.clone(); + tray.current_title = "ROG: GPU status unavailable".to_string(); + }) + .await; + } return; } }; diff --git a/rog-control-center/src/zbus_proxies.rs b/rog-control-center/src/zbus_proxies.rs index 4090ff2a..bd99b887 100644 --- a/rog-control-center/src/zbus_proxies.rs +++ b/rog-control-center/src/zbus_proxies.rs @@ -94,9 +94,9 @@ pub fn find_iface(iface_name: &str) -> Result, Box + From>, { - let conn = Connection::system().unwrap(); - let f = fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").unwrap(); - let interfaces = f.get_managed_objects().unwrap(); + let conn = Connection::system()?; + let f = fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/")?; + let interfaces = f.get_managed_objects()?; let mut paths = Vec::new(); for v in interfaces.iter() { // let o: Vec = v.1.keys().map(|e| @@ -132,11 +132,9 @@ pub async fn find_iface_async(iface_name: &str) -> Result, Box + From>, { - let conn = zbus::Connection::system().await.unwrap(); - let f = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/") - .await - .unwrap(); - let interfaces = f.get_managed_objects().await.unwrap(); + let conn = zbus::Connection::system().await?; + let f = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?; + let interfaces = f.get_managed_objects().await?; let mut paths = Vec::new(); for v in interfaces.iter() { // let o: Vec = v.1.keys().map(|e| From 95fe629650bb05125a283028998be6f99f973eb8 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:33:34 +0200 Subject: [PATCH 10/22] fix(asusd): replace unwrap panics with error handling in daemon and inotify watchers --- asusd/src/aura_manager.rs | 88 ++++++++++++++++++++------------------ asusd/src/ctrl_platform.rs | 24 +++++++++-- asusd/src/lib.rs | 40 +++++++++-------- 3 files changed, 90 insertions(+), 62 deletions(-) diff --git a/asusd/src/aura_manager.rs b/asusd/src/aura_manager.rs index 6d7fe524..798441e1 100644 --- a/asusd/src/aura_manager.rs +++ b/asusd/src/aura_manager.rs @@ -154,12 +154,13 @@ impl DeviceManager { let path = dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_slash()); let ctrl = SlashZbus::new(slash); - ctrl.start_tasks(connection, path.clone()).await.unwrap(); - devices.push(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: Some(hid_key.clone()), - }); + if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + hid_key: Some(hid_key.clone()), + }); + } } } // ANIME MATRIX DEVICE @@ -173,12 +174,13 @@ impl DeviceManager { let path = dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_anime()); let ctrl = AniMeZbus::new(anime); - ctrl.start_tasks(connection, path.clone()).await.unwrap(); - devices.push(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: Some(hid_key.clone()), - }); + if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + hid_key: Some(hid_key.clone()), + }); + } } } // AURA LAPTOP DEVICE @@ -192,12 +194,13 @@ impl DeviceManager { let path = dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_tuf()); let ctrl = AuraZbus::new(aura); - ctrl.start_tasks(connection, path.clone()).await.unwrap(); - devices.push(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: Some(hid_key), - }); + if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + hid_key: Some(hid_key), + }); + } } } } else { @@ -265,17 +268,18 @@ impl DeviceManager { .property_value("ID_MODEL_ID") .unwrap_or_default() .to_string_lossy(); - if let Ok(dev_type) = - DeviceHandle::maybe_scsi(dev_node.as_os_str().to_str().unwrap(), &prod_id).await - { - if let DeviceHandle::Scsi(scsi) = dev_type.clone() { - let ctrl = ScsiZbus::new(scsi); - ctrl.start_tasks(connection, path.clone()).await.unwrap(); - return Some(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: None, - }); + if let Some(dev_str) = dev_node.as_os_str().to_str() { + if let Ok(dev_type) = DeviceHandle::maybe_scsi(dev_str, &prod_id).await { + if let DeviceHandle::Scsi(scsi) = dev_type.clone() { + let ctrl = ScsiZbus::new(scsi); + if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + return Some(AsusDevice { + device: dev_type, + dbus_path: path, + hid_key: None, + }); + } + } } } } @@ -352,12 +356,13 @@ impl DeviceManager { if let DeviceHandle::Slash(slash) = dev_type.clone() { let path = dbus_path_for_slash(); let ctrl = SlashZbus::new(slash); - ctrl.start_tasks(connection, path.clone()).await.unwrap(); - devices.push(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: None, - }); + if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + hid_key: None, + }); + } } } else { info!("Tested device was not Slash"); @@ -403,12 +408,13 @@ impl DeviceManager { if let DeviceHandle::Aura(aura) = dev_type.clone() { let path = dbus_path_for_tuf(); let ctrl = AuraZbus::new(aura); - ctrl.start_tasks(connection, path.clone()).await.unwrap(); - devices.push(AsusDevice { - device: dev_type, - dbus_path: path, - hid_key: None, - }); + if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + devices.push(AsusDevice { + device: dev_type, + dbus_path: path, + hid_key: None, + }); + } } } } diff --git a/asusd/src/ctrl_platform.rs b/asusd/src/ctrl_platform.rs index 5be5cadb..3a9f2e24 100644 --- a/asusd/src/ctrl_platform.rs +++ b/asusd/src/ctrl_platform.rs @@ -91,7 +91,14 @@ impl CtrlPlatform { let mut buffer = [0; 32]; loop { // vi and vim do stupid shit causing the file watch to be removed - let inotify = inotify::Inotify::init().unwrap(); + let inotify = match inotify::Inotify::init() { + Ok(i) => i, + Err(e) => { + error!("Failed to initialize inotify for config watch: {e}"); + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + continue; + } + }; inotify .watches() .add( @@ -109,7 +116,14 @@ impl CtrlPlatform { } }) .ok(); - let mut events = inotify.into_event_stream(&mut buffer).unwrap(); + let mut events = match inotify.into_event_stream(&mut buffer) { + Ok(s) => s, + Err(e) => { + error!("Failed to create event stream for config watch: {e}"); + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + continue; + } + }; while let Some(ev) = events.next().await { if let Ok(ev) = ev { @@ -124,10 +138,12 @@ impl CtrlPlatform { let res = config1.lock().await.read_new(); if let Some(new_cfg) = res { - inotify_self + if let Err(e) = inotify_self .reload_and_notify(&signal_context, new_cfg) .await - .unwrap(); + { + error!("Failed to reload and notify config: {e:?}"); + } } } } diff --git a/asusd/src/lib.rs b/asusd/src/lib.rs index 19d0549a..070c0b44 100644 --- a/asusd/src/lib.rs +++ b/asusd/src/lib.rs @@ -79,19 +79,23 @@ macro_rules! task_watch_item { Ok(watch) => { tokio::spawn(async move { let mut buffer = [0; 32]; - watch.into_event_stream(&mut buffer).unwrap().for_each(|_| async { - if let Ok(value) = ctrl.$name() { // get new value from zbus method - if ctrl.config.lock().await.$name != value { - log::debug!("{} was changed to {} externally", $name_str, value); - concat_idents::concat_idents!(notif_fn = $name, _changed { - ctrl.notif_fn(&signal_ctxt).await.ok(); - }); - let mut lock = ctrl.config.lock().await; - lock.$name = value; - lock.write(); + if let Ok(stream) = watch.into_event_stream(&mut buffer) { + stream.for_each(|_| async { + if let Ok(value) = ctrl.$name() { // get new value from zbus method + if ctrl.config.lock().await.$name != value { + log::debug!("{} was changed to {} externally", $name_str, value); + concat_idents::concat_idents!(notif_fn = $name, _changed { + ctrl.notif_fn(&signal_ctxt).await.ok(); + }); + let mut lock = ctrl.config.lock().await; + lock.$name = value; + lock.write(); + } } - } - }).await; + }).await; + } else { + log::error!("Failed to create event stream for {}", $name_str); + } }); } Err(e) => info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e), @@ -119,11 +123,13 @@ macro_rules! task_watch_item_notify { Ok(watch) => { tokio::spawn(async move { let mut buffer = [0; 32]; - watch.into_event_stream(&mut buffer).unwrap().for_each(|_| async { - concat_idents::concat_idents!(notif_fn = $name, _changed { - ctrl.notif_fn(&signal_ctxt).await.ok(); - }); - }).await; + if let Ok(stream) = watch.into_event_stream(&mut buffer) { + stream.for_each(|_| async { + concat_idents::concat_idents!(notif_fn = $name, _changed { + ctrl.notif_fn(&signal_ctxt).await.ok(); + }); + }).await; + } }); } Err(e) => info!("inotify watch failed: {}. You can ignore this if your device does not support the feature", e), From 7498baac31f2feac5fc822503562fb643b9fd8ba Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sat, 25 Jul 2026 18:33:38 +0200 Subject: [PATCH 11/22] fix(asusctl): improve CLI robustness under missing or empty services and eliminate unwrap calls --- asusctl/examples/anime-led-scan.rs | 16 +++--- asusctl/examples/anime-outline.rs | 2 +- asusctl/src/main.rs | 84 ++++++++++++++++++++---------- rog-platform/src/platform.rs | 3 +- simulators/src/simulator.rs | 14 +++-- 5 files changed, 74 insertions(+), 45 deletions(-) diff --git a/asusctl/examples/anime-led-scan.rs b/asusctl/examples/anime-led-scan.rs index c4ff9b94..48311dcb 100644 --- a/asusctl/examples/anime-led-scan.rs +++ b/asusctl/examples/anime-led-scan.rs @@ -347,13 +347,15 @@ fn main() { } "a" => { println!("Auto-scan mode (0 to {})...", scan_len - 1); - let delay = std::time::Duration::from_millis(10); - for idx in current_index..scan_len { - write_single_led(&proxy, anime_type, idx, brightness); - print!("\rIndex: {} / {} ", idx, scan_len - 1); - io::stdout().flush().unwrap(); - std::thread::sleep(delay); - current_index = idx; + if scan_len > current_index { + let delay = std::time::Duration::from_millis(10); + for idx in current_index..scan_len { + write_single_led(&proxy, anime_type, idx, brightness); + print!("\rIndex: {} / {} ", idx, scan_len - 1); + io::stdout().flush().unwrap(); + std::thread::sleep(delay); + } + current_index = scan_len - 1; } println!(); println!("Auto-scan complete. Current index: {}", current_index); diff --git a/asusctl/examples/anime-outline.rs b/asusctl/examples/anime-outline.rs index 8a9bbd44..a06f7898 100644 --- a/asusctl/examples/anime-outline.rs +++ b/asusctl/examples/anime-outline.rs @@ -127,7 +127,7 @@ fn main() { *n = 250; } matrix.data_mut()[1244] = 100; // end - println!("{:?}", &matrix); + println!("{:?}", matrix); proxy.write(matrix).unwrap(); } diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index a16bdfdb..d0a01233 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -56,7 +56,13 @@ fn main() { let parsed: CliStart = argh::from_env(); - let conn = Connection::system().unwrap(); + let conn = match Connection::system() { + Ok(c) => c, + Err(e) => { + error!("Could not connect to D-Bus system bus: {e}\nIs dbus-daemon running?"); + return; + } + }; if let Ok(platform_proxy) = PlatformProxyBlocking::new(&conn).map_err(|e| { check_service("asusd"); println!("\nError: {e}\n"); @@ -146,9 +152,9 @@ fn find_iface(iface_name: &str) -> Result, Box> where T: ProxyImpl<'static> + From>, { - let conn = zbus::blocking::Connection::system().unwrap(); - let f = zbus::blocking::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").unwrap(); - let interfaces = f.get_managed_objects().unwrap(); + let conn = zbus::blocking::Connection::system()?; + let f = zbus::blocking::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/")?; + let interfaces = f.get_managed_objects()?; let mut paths = Vec::new(); for v in interfaces.iter() { // let o: Vec = v.1.keys().map(|e| @@ -254,14 +260,18 @@ fn handle_info( ); if let Ok(aura) = find_iface::("xyz.ljones.Aura") { // TODO: multiple RGB check - let bright = aura.first().unwrap().supported_brightness()?; - let modes = aura.first().unwrap().supported_basic_modes()?; - let zones = aura.first().unwrap().supported_basic_zones()?; - let power = aura.first().unwrap().supported_power_zones()?; - println!("Supported Keyboard Brightness:\n{:#?}", bright); - println!("Supported Aura Modes:\n{:#?}", modes); - println!("Supported Aura Zones:\n{:#?}", zones); - println!("Supported Aura Power Zones:\n{:#?}", power); + if let Some(first_aura) = aura.first() { + let bright = first_aura.supported_brightness()?; + let modes = first_aura.supported_basic_modes()?; + let zones = first_aura.supported_basic_zones()?; + let power = first_aura.supported_power_zones()?; + println!("Supported Keyboard Brightness:\n{:#?}", bright); + println!("Supported Aura Modes:\n{:#?}", modes); + println!("Supported Aura Zones:\n{:#?}", zones); + println!("Supported Aura Power Zones:\n{:#?}", power); + } else { + println!("No aura interface found"); + } } else { println!("No aura interface found"); } @@ -555,7 +565,7 @@ fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box> { if let Some(mode) = cmd.mode { dbg!(mode as u8); - scsi.set_led_mode(mode).unwrap(); + scsi.set_led_mode(mode)?; } let mut mode = scsi.led_mode_data()?; @@ -612,10 +622,12 @@ fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box("xyz.ljones.Aura") { - let modes = aura.first().unwrap().supported_basic_modes()?; - println!("Available modes:"); - for m in modes { - println!(" {:?}", m); + if let Some(first_aura) = aura.first() { + let modes = first_aura.supported_basic_modes()?; + println!("Available modes:"); + for m in modes { + println!(" {:?}", m); + } } } return Ok(()); @@ -630,23 +642,31 @@ fn handle_led_mode(mode: &LedModeCommand) -> Result<(), Box= modes.len() { - pos = 0; + if let Some(pos) = modes.iter().position(|m| *m == mode) { + let next_pos = if pos + 1 >= modes.len() { 0 } else { pos + 1 }; + if let Some(&target_mode) = modes.get(next_pos) { + aura.set_led_mode(target_mode)?; + } + } else if let Some(&first) = modes.first() { + aura.set_led_mode(first)?; } - aura.set_led_mode(modes[pos])?; } } else if mode.prev_mode { for aura in aura { let mode = aura.led_mode()?; let modes = aura.supported_basic_modes()?; - let mut pos = modes.iter().position(|m| *m == mode).unwrap(); - if pos == 0 { - pos = modes.len() - 1; - } else { - pos -= 1; + if let Some(pos) = modes.iter().position(|m| *m == mode) { + let prev_pos = if pos == 0 { + modes.len().saturating_sub(1) + } else { + pos - 1 + }; + if let Some(&target_mode) = modes.get(prev_pos) { + aura.set_led_mode(target_mode)?; + } + } else if let Some(&last) = modes.last() { + aura.set_led_mode(last)?; } - aura.set_led_mode(modes[pos])?; } } else if let Some(mode) = mode.command.as_ref() { for aura in aura { @@ -1003,17 +1023,23 @@ fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box { + let mut found = false; if let Ok(attrs) = find_iface::("xyz.ljones.AsusArmoury") { for attr in attrs.iter() { let name = attr.name()?; if <&str>::from(name) == g.property { print_firmware_attr(attr)?; + found = true; } } } + if !found { + return Err(format!("Firmware attribute '{}' not found", g.property).into()); + } Ok(()) } ArmourySubCommand::Set(s) => { + let mut found = false; if let Ok(attrs) = find_iface::("xyz.ljones.AsusArmoury") { for attr in attrs.iter() { let name = attr.name()?; @@ -1025,9 +1051,13 @@ fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box Result<(), Box> { } let anime_type = AnimeType::from_str(&args[1])?; - let sdl_context = sdl2::init().unwrap(); - let video_subsystem = sdl_context.video().unwrap(); + let sdl_context = sdl2::init().map_err(|e| e.to_string())?; + let video_subsystem = sdl_context.video().map_err(|e| e.to_string())?; let window = video_subsystem .window("rust-sdl2 demo", 1260, 760) .position_centered() .build() - .unwrap(); + .map_err(|e| e.to_string())?; - let mut canvas = window.into_canvas().build().unwrap(); + let mut canvas = window.into_canvas().build().map_err(|e| e.to_string())?; let mut dev = VirtAnimeMatrix::new(anime_type); canvas.set_draw_color(Color::RGB(0, 0, 0)); canvas.clear(); - let mut event_pump = sdl_context.event_pump().unwrap(); + let mut event_pump = sdl_context.event_pump().map_err(|e| e.to_string())?; 'running: loop { dev.read(); // it's blocking, and damned hard to sync with arc/mutex // let one = dev.buffer[0..7] != USB_PREFIX2; @@ -170,9 +170,7 @@ fn main() -> Result<(), Box> { } + row.3 * w; let y = y_count as i32 * h - y_offset * h; - canvas - .fill_rect(Rect::new(x, y, w as u32, h as u32)) - .unwrap(); + let _ = canvas.fill_rect(Rect::new(x, y, w as u32, h as u32)); } } } From 8b9b70d70c51ded1da6db7be258760ce3cf38728 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sun, 26 Jul 2026 10:49:32 +0200 Subject: [PATCH 12/22] refactor: replace external sg git dependency with native rog-scsi implementation --- Cargo.toml | 2 - rog-scsi/Cargo.toml | 3 +- rog-scsi/src/builtin_modes.rs | 3 +- rog-scsi/src/lib.rs | 1 + rog-scsi/src/scsi.rs | 16 +- rog-scsi/src/sg.rs | 339 ++++++++++++++++++++++++++++++++++ 6 files changed, 351 insertions(+), 13 deletions(-) create mode 100644 rog-scsi/src/sg.rs diff --git a/Cargo.toml b/Cargo.toml index 7a9fe7d9..fce19c26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,8 +78,6 @@ argh = "^0.1" uhid-virt = "^0.0.8" sdl2 = { version = "^0.37", default-features = false } -sg = { git = "https://github.com/flukejones/sg-rs.git" } - [profile.release] lto = "thin" debug = false diff --git a/rog-scsi/Cargo.toml b/rog-scsi/Cargo.toml index b5822992..9653f5d4 100644 --- a/rog-scsi/Cargo.toml +++ b/rog-scsi/Cargo.toml @@ -15,7 +15,8 @@ default = ["dbus", "ron"] dbus = ["zbus"] [dependencies] -sg.workspace = true +libc = "^0.2" +nix = { version = "^0.29", features = ["uio"] } serde.workspace = true zbus = { workspace = true, optional = true } diff --git a/rog-scsi/src/builtin_modes.rs b/rog-scsi/src/builtin_modes.rs index fb2ed22e..78f9f432 100644 --- a/rog-scsi/src/builtin_modes.rs +++ b/rog-scsi/src/builtin_modes.rs @@ -7,6 +7,7 @@ use zbus::zvariant::{OwnedValue, Type, Value}; use crate::error::Error; use crate::scsi::{apply_task, dir_task, mode_task, rgb_task, save_task, speed_task}; +use crate::sg::Task; #[cfg_attr(feature = "dbus", derive(Type, Value, OwnedValue))] #[derive(Debug, Clone, PartialEq, Eq, Copy, Deserialize, Serialize)] @@ -366,7 +367,7 @@ impl Display for AuraEffect { } } -impl From<&AuraEffect> for Vec { +impl From<&AuraEffect> for Vec { fn from(effect: &AuraEffect) -> Self { let mut tasks = Vec::new(); diff --git a/rog-scsi/src/lib.rs b/rog-scsi/src/lib.rs index 85765a33..1fc38249 100644 --- a/rog-scsi/src/lib.rs +++ b/rog-scsi/src/lib.rs @@ -1,6 +1,7 @@ mod builtin_modes; mod error; mod scsi; +pub mod sg; pub use builtin_modes::*; pub use error::*; diff --git a/rog-scsi/src/scsi.rs b/rog-scsi/src/scsi.rs index 61fa8062..0ae6af71 100644 --- a/rog-scsi/src/scsi.rs +++ b/rog-scsi/src/scsi.rs @@ -1,6 +1,4 @@ -extern crate sg; - -pub use sg::Task; +use crate::sg::{Direction, Task}; static ENE_APPLY_VAL: u8 = 0x01; // Value for Apply Changes Register static ENE_SAVE_VAL: u8 = 0xaa; @@ -37,7 +35,7 @@ fn data(reg: u32, arg_count: u8) -> [u8; 16] { pub(crate) fn rgb_task(led: u32, rgb: &[u8; 3]) -> Task { let mut task = Task::new(); task.set_cdb(data(led * 3 + ENE_REG_COLORS_EFFECT_V2, 3).as_slice()); - task.set_data(rgb, sg::Direction::ToDevice); + task.set_data(rgb, Direction::ToDevice); task } @@ -45,7 +43,7 @@ pub(crate) fn rgb_task(led: u32, rgb: &[u8; 3]) -> Task { pub(crate) fn mode_task(mode: u8) -> Task { let mut task = Task::new(); task.set_cdb(data(ENE_REG_MODE, 1).as_slice()); - task.set_data(&[mode.min(13)], sg::Direction::ToDevice); + task.set_data(&[mode.min(13)], Direction::ToDevice); task } @@ -53,7 +51,7 @@ pub(crate) fn mode_task(mode: u8) -> Task { pub(crate) fn speed_task(speed: u8) -> Task { let mut task = Task::new(); task.set_cdb(data(ENE_REG_SPEED, 1).as_slice()); - task.set_data(&[speed.min(4)], sg::Direction::ToDevice); + task.set_data(&[speed.min(4)], Direction::ToDevice); task } @@ -61,20 +59,20 @@ pub(crate) fn speed_task(speed: u8) -> Task { pub(crate) fn dir_task(mode: u8) -> Task { let mut task = Task::new(); task.set_cdb(data(ENE_REG_DIRECTION, 1).as_slice()); - task.set_data(&[mode.min(1)], sg::Direction::ToDevice); + task.set_data(&[mode.min(1)], Direction::ToDevice); task } pub(crate) fn apply_task() -> Task { let mut task = Task::new(); task.set_cdb(data(ENE_REG_APPLY, 1).as_slice()); - task.set_data(&[ENE_APPLY_VAL], sg::Direction::ToDevice); + task.set_data(&[ENE_APPLY_VAL], Direction::ToDevice); task } pub(crate) fn save_task() -> Task { let mut task = Task::new(); task.set_cdb(data(ENE_REG_APPLY, 1).as_slice()); - task.set_data(&[ENE_SAVE_VAL], sg::Direction::ToDevice); + task.set_data(&[ENE_SAVE_VAL], Direction::ToDevice); task } diff --git a/rog-scsi/src/sg.rs b/rog-scsi/src/sg.rs new file mode 100644 index 00000000..7009b440 --- /dev/null +++ b/rog-scsi/src/sg.rs @@ -0,0 +1,339 @@ +use std::ffi::c_void; +use std::fs::{File, OpenOptions}; +use std::io::{self, IoSlice, IoSliceMut}; +use std::os::unix::fs::OpenOptionsExt; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::path::Path; +use std::time::Duration; + +pub const SG_DXFER_NONE: i32 = -1; +pub const SG_DXFER_TO_DEV: i32 = -2; +pub const SG_DXFER_FROM_DEV: i32 = -3; +pub const SG_DXFER_TO_FROM_DEV: i32 = -4; + +pub const SG_INFO_OK_MASK: u32 = 0x1; +pub const SG_INFO_OK: u32 = 0x0; +pub const SG_MAX_QUEUE: usize = 16; +pub const SG_IO: u64 = 0x2285; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct SgIoHdr { + pub interface_id: std::os::raw::c_int, + pub dxfer_direction: std::os::raw::c_int, + pub cmd_len: u8, + pub mx_sb_len: u8, + pub iovec_count: u16, + pub dxfer_len: u32, + pub dxferp: *mut c_void, + pub cmdp: *mut u8, + pub sbp: *mut u8, + pub timeout: u32, + pub flags: u32, + pub pack_id: std::os::raw::c_int, + pub usr_ptr: *mut c_void, + pub status: u8, + pub masked_status: u8, + pub msg_status: u8, + pub sb_len_wr: u8, + pub host_status: u16, + pub driver_status: u16, + pub resid: i32, + pub duration: u32, + pub info: u32, +} + +impl Default for SgIoHdr { + fn default() -> Self { + Self { + interface_id: b'S' as std::os::raw::c_int, + dxfer_direction: SG_DXFER_NONE, + cmd_len: 0, + mx_sb_len: 0, + iovec_count: 0, + dxfer_len: 0, + dxferp: std::ptr::null_mut(), + cmdp: std::ptr::null_mut(), + sbp: std::ptr::null_mut(), + timeout: 0, + flags: 0, + pack_id: 0, + usr_ptr: std::ptr::null_mut(), + status: 0, + masked_status: 0, + msg_status: 0, + sb_len_wr: 0, + host_status: 0, + driver_status: 0, + resid: 0, + duration: 0, + info: 0, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Direction { + None, + ToDevice, + FromDevice, + ToFromDevice, +} + +impl Direction { + fn to_underlying(self) -> std::os::raw::c_int { + match self { + Direction::None => SG_DXFER_NONE, + Direction::ToDevice => SG_DXFER_TO_DEV, + Direction::FromDevice => SG_DXFER_FROM_DEV, + Direction::ToFromDevice => SG_DXFER_TO_FROM_DEV, + } + } +} + +#[derive(Clone, Debug)] +pub struct Task { + inner: SgIoHdr, + cmd: Vec, + data: Vec, + sense: Vec, +} + +impl Default for Task { + fn default() -> Self { + Self::new() + } +} + +// SAFETY: Task manages its internal buffers and SgIoHdr raw pointers. The raw pointers +// are updated prior to any ioctl execution to point directly to owned heap vectors, making +// Send and Sync safe across thread boundaries. +unsafe impl Send for Task {} +unsafe impl Sync for Task {} + +impl Task { + pub fn new() -> Self { + Task { + inner: SgIoHdr::default(), + cmd: Vec::new(), + data: Vec::new(), + sense: Vec::new(), + } + } + + /// Prepares raw internal pointers in SgIoHdr to match current buffer memory addresses. + pub fn sync_pointers(&mut self) { + if !self.cmd.is_empty() { + self.inner.cmdp = self.cmd.as_mut_ptr(); + self.inner.cmd_len = self.cmd.len() as u8; + } else { + self.inner.cmdp = std::ptr::null_mut(); + self.inner.cmd_len = 0; + } + + if !self.data.is_empty() { + self.inner.dxferp = self.data.as_mut_ptr() as *mut c_void; + self.inner.dxfer_len = self.data.len() as u32; + } else { + self.inner.dxferp = std::ptr::null_mut(); + self.inner.dxfer_len = 0; + } + + if !self.sense.is_empty() { + self.inner.sbp = self.sense.as_mut_ptr(); + self.inner.mx_sb_len = self.sense.len() as u8; + } else { + self.inner.sbp = std::ptr::null_mut(); + self.inner.mx_sb_len = 0; + } + } + + pub fn set_cdb(&mut self, buf: &[u8]) -> &mut Self { + self.cmd = buf.to_vec(); + self.sync_pointers(); + self + } + + pub fn cdb(&self) -> &[u8] { + &self.cmd + } + + pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self { + self.inner.timeout = timeout.as_millis() as u32; + self + } + + pub fn timeout(&self) -> Duration { + Duration::from_millis(u64::from(self.inner.timeout)) + } + + pub fn set_data(&mut self, buf: &[u8], direction: Direction) -> &mut Self { + self.data = buf.to_vec(); + self.inner.dxfer_direction = direction.to_underlying(); + self.sync_pointers(); + self + } + + pub fn data(&self) -> &[u8] { + &self.data + } + + pub fn data_mut(&mut self) -> &mut [u8] { + &mut self.data + } + + pub fn set_sense_buffer(&mut self, buf: &[u8]) -> &mut Self { + self.sense = buf.to_vec(); + self.sync_pointers(); + self + } + + pub fn sense_buffer(&self) -> &[u8] { + &self.sense + } + + pub fn set_flags(&mut self, flags: u32) -> &mut Self { + self.inner.flags = flags; + self + } + + pub fn flags(&self) -> u32 { + self.inner.flags + } + + pub fn duration(&self) -> u32 { + self.inner.duration + } + + pub fn residual_data(&self) -> i32 { + self.inner.resid + } + + pub fn status(&self) -> u8 { + self.inner.status + } + + pub fn host_status(&self) -> u16 { + self.inner.host_status + } + + pub fn driver_status(&self) -> u16 { + self.inner.driver_status + } + + pub fn ok(&self) -> bool { + (self.inner.info & SG_INFO_OK_MASK) == SG_INFO_OK + } +} + +pub struct Device(File); + +impl Device { + pub fn open>(path: P) -> io::Result { + Ok(Device( + OpenOptions::new() + .read(true) + .write(true) + .custom_flags(libc::O_NONBLOCK) + .open(path)?, + )) + } + + /// Sends multiple tasks to the SCSI device queue. + pub fn send(&self, tasks: &mut [Task]) -> io::Result { + if tasks.is_empty() { + return Ok(0); + } + + for task in tasks.iter_mut() { + task.sync_pointers(); + } + + // SAFETY: SgIoHdr is a repr(C) struct with standard byte layout. We create a read-only + // slice over the byte buffer of each SgIoHdr for writev. + let iovecs: Vec = tasks + .iter() + .map(|task| { + IoSlice::new(unsafe { + std::slice::from_raw_parts( + &task.inner as *const SgIoHdr as *const u8, + std::mem::size_of::(), + ) + }) + }) + .collect(); + + loop { + match nix::sys::uio::writev(&self.0, &iovecs[..tasks.len()]) { + Ok(n) => break Ok(n / std::mem::size_of::()), + Err(nix::errno::Errno::EINTR) => {} + Err(e) => break Err(e.into()), + } + } + } + + /// Receives completed tasks from the SCSI device queue. + pub fn receive(&self, tasks: &mut Vec) -> io::Result { + let mut hdrs = [SgIoHdr::default(); SG_MAX_QUEUE]; + + // SAFETY: SgIoHdr is a repr(C) struct. We create a mutable byte slice over each header + // buffer to allow readv to populate header data. + let mut iovecs: Vec = hdrs + .iter_mut() + .map(|hdr| { + IoSliceMut::new(unsafe { + std::slice::from_raw_parts_mut( + hdr as *mut SgIoHdr as *mut u8, + std::mem::size_of::(), + ) + }) + }) + .collect(); + + let bytes_read = loop { + match nix::sys::uio::readv(&self.0, iovecs.as_mut_slice()) { + Ok(n) => break n, + Err(e) if e == nix::errno::Errno::EINTR || e == nix::errno::Errno::EAGAIN => {} + Err(e) => return Err(e.into()), + } + }; + + if bytes_read == 0 { + return Ok(0); + } + + let tasks_read = bytes_read / std::mem::size_of::(); + for hdr in hdrs.iter().take(tasks_read) { + let mut task = Task::new(); + task.inner = *hdr; + tasks.push(task); + } + Ok(tasks_read) + } + + /// Performs a synchronous SCSI IO operation via ioctl. + pub fn perform(&self, task: &Task) -> io::Result<()> { + let mut task_copy = task.clone(); + task_copy.sync_pointers(); + + #[cfg(target_env = "musl")] + let request = SG_IO as i32; + #[cfg(not(target_env = "musl"))] + let request: u64 = SG_IO; + + // SAFETY: The raw file descriptor is open and valid, and task_copy has valid synced + // buffer pointers matching the initialized memory of task_copy.inner. + let ret = unsafe { libc::ioctl(self.0.as_raw_fd(), request, &task_copy.inner) }; + if ret == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } +} + +impl AsRawFd for Device { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} From 081b4a37fc62d7c17f749092c3bfe254029a270f Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sun, 26 Jul 2026 10:57:14 +0200 Subject: [PATCH 13/22] refactor: centralize external crate dependencies into workspace.dependencies --- Cargo.toml | 5 +++++ rog-aura/Cargo.toml | 2 +- rog-control-center/Cargo.toml | 12 ++++++------ rog-platform/Cargo.toml | 2 +- rog-scsi/Cargo.toml | 6 +++--- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fce19c26..3b540b8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -77,6 +77,11 @@ slint-build = "^1.17.1" argh = "^0.1" uhid-virt = "^0.0.8" sdl2 = { version = "^0.37", default-features = false } +libc = "^0.2" +nix = { version = "^0.29", features = ["uio"] } +nvml-wrapper = "^0.12.0" +ashpd = { version = "^0.13", default-features = false, features = ["async-io", "global_shortcuts"] } +console-subscriber = "^0.4" [profile.release] lto = "thin" diff --git a/rog-aura/Cargo.toml b/rog-aura/Cargo.toml index 76dd4abf..eb4a089a 100644 --- a/rog-aura/Cargo.toml +++ b/rog-aura/Cargo.toml @@ -23,5 +23,5 @@ dmi_id = { path = "../dmi-id" } # cli and logging log.workspace = true -ron = { version = "*", optional = true } +ron = { workspace = true, optional = true } thiserror.workspace = true diff --git a/rog-control-center/Cargo.toml b/rog-control-center/Cargo.toml index 9e572661..d927a761 100644 --- a/rog-control-center/Cargo.toml +++ b/rog-control-center/Cargo.toml @@ -17,11 +17,11 @@ tokio-debug = ["console-subscriber"] rog_ally = [] [dependencies] -console-subscriber = { version = "^0.4", optional = true } +console-subscriber = { workspace = true, optional = true } -ksni = { version = "0.3", default-features = false, features = ["async-io"] } -image = "0.25.5" -ashpd = { version = "0.13", default-features = false, features = ["async-io", "global_shortcuts"] } +ksni.workspace = true +image.workspace = true +ashpd.workspace = true asusd = { path = "../asusd" } config-traits = { path = "../config-traits" } @@ -46,8 +46,8 @@ concat-idents.workspace = true futures-util.workspace = true thiserror.workspace = true udev.workspace = true -serde_json = "1.0" -nvml-wrapper = "0.12.0" +serde_json.workspace = true +nvml-wrapper.workspace = true [dependencies.slint] git = "https://github.com/slint-ui/slint.git" diff --git a/rog-platform/Cargo.toml b/rog-platform/Cargo.toml index 5fe95f41..a25737fd 100644 --- a/rog-platform/Cargo.toml +++ b/rog-platform/Cargo.toml @@ -17,7 +17,7 @@ udev.workspace = true inotify.workspace = true rusb.workspace = true thiserror.workspace = true -nvml-wrapper = "0.12.0" +nvml-wrapper.workspace = true [dev-dependencies] serde_json = { workspace = true, features = ["preserve_order"] } diff --git a/rog-scsi/Cargo.toml b/rog-scsi/Cargo.toml index 9653f5d4..99992933 100644 --- a/rog-scsi/Cargo.toml +++ b/rog-scsi/Cargo.toml @@ -15,12 +15,12 @@ default = ["dbus", "ron"] dbus = ["zbus"] [dependencies] -libc = "^0.2" -nix = { version = "^0.29", features = ["uio"] } +libc.workspace = true +nix.workspace = true serde.workspace = true zbus = { workspace = true, optional = true } # cli and logging -ron = { version = "*", optional = true } +ron = { workspace = true, optional = true } thiserror.workspace = true From c2a30278c935d105963a9d4298e9a8b01adc3cf9 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sun, 26 Jul 2026 10:58:37 +0200 Subject: [PATCH 14/22] style: sort workspace dependencies alphabetically in Cargo.toml --- Cargo.toml | 66 +++++++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b540b8b..189885f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,53 +35,47 @@ members = [ default-members = ["asusctl", "asusd", "asus-shutdown", "asusd-user", "rog-control-center"] [workspace.dependencies] -tokio = { version = "^1.39.0", default-features = false, features = [ - "macros", - "sync", - "time", - "rt", - "rt-multi-thread", -] } +argh = "^0.1" +ashpd = { version = "^0.13", default-features = false, features = ["async-io", "global_shortcuts"] } concat-idents = "^1.1" +console-subscriber = "^0.4" dirs = "^4.0" -mio = "^0.8.11" - -futures-util = "^0.3.31" -zbus = "^5.13.1" -logind-zbus = { version = "^5.2.0" } - -serde = { version = "^1.0", features = ["serde_derive"] } -serde_json = "^1.0" -ron = "*" - -log = "^0.4" env_logger = "^0.10.0" -thiserror = "^2" - +futures-util = "^0.3.31" +gif = "^0.12" glam = { version = "^0.22", features = ["serde"] } gumdrop = "^0.8" -udev = { version = "^0.8", features = ["mio"] } -rusb = "^0.9" +image = "^0.25.5" inotify = "^0.10" - -png_pong = "^0.8" -pix = "^0.13" -gif = "^0.12" - -notify-rust = { version = "^4.11.5", features = ["z", "async"] } ksni = { version = "^0.3", default-features = false, features = ["async-io"] } -image = "^0.25.5" +libc = "^0.2" +log = "^0.4" +logind-zbus = { version = "^5.2.0" } +mio = "^0.8.11" +nix = { version = "^0.29", features = ["uio"] } +notify-rust = { version = "^4.11.5", features = ["z", "async"] } +nvml-wrapper = "^0.12.0" +pix = "^0.13" png = "^0.17" +png_pong = "^0.8" +ron = "*" +rusb = "^0.9" +sdl2 = { version = "^0.37", default-features = false } +serde = { version = "^1.0", features = ["serde_derive"] } +serde_json = "^1.0" slint = { version = "^1.17.1", features = ["gettext"] } slint-build = "^1.17.1" -argh = "^0.1" +thiserror = "^2" +tokio = { version = "^1.39.0", default-features = false, features = [ + "macros", + "sync", + "time", + "rt", + "rt-multi-thread", +] } +udev = { version = "^0.8", features = ["mio"] } uhid-virt = "^0.0.8" -sdl2 = { version = "^0.37", default-features = false } -libc = "^0.2" -nix = { version = "^0.29", features = ["uio"] } -nvml-wrapper = "^0.12.0" -ashpd = { version = "^0.13", default-features = false, features = ["async-io", "global_shortcuts"] } -console-subscriber = "^0.4" +zbus = "^5.13.1" [profile.release] lto = "thin" From 50321e7b29e0c6b770dd3e4508af77a985a7596e Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sun, 26 Jul 2026 10:59:57 +0200 Subject: [PATCH 15/22] chore: pin ron workspace dependency version to ^0.12.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 189885f1..a6a6fe4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ nvml-wrapper = "^0.12.0" pix = "^0.13" png = "^0.17" png_pong = "^0.8" -ron = "*" +ron = "^0.12.2" rusb = "^0.9" sdl2 = { version = "^0.37", default-features = false } serde = { version = "^1.0", features = ["serde_derive"] } From b2eae94d6fdecf6482f0a0fc63d1c510caa4e6e5 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sun, 26 Jul 2026 11:37:31 +0200 Subject: [PATCH 16/22] fix: remove leftover dbg! macros from production code --- asusctl/src/main.rs | 1 - rog-control-center/src/ui/setup_fans.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index d0a01233..b8000aec 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -564,7 +564,6 @@ fn handle_scsi(cmd: &ScsiCommand) -> Result<(), Box> { } if let Some(mode) = cmd.mode { - dbg!(mode as u8); scsi.set_led_mode(mode)?; } diff --git a/rog-control-center/src/ui/setup_fans.rs b/rog-control-center/src/ui/setup_fans.rs index 5ee7f647..b12f29d5 100644 --- a/rog-control-center/src/ui/setup_fans.rs +++ b/rog-control-center/src/ui/setup_fans.rs @@ -240,8 +240,6 @@ fn fan_data_for(fan: FanType, enabled: bool, data: Vec) -> CurveData { pwm[i] = n.y as u8; } - dbg!(&fan, enabled); - CurveData { fan: fan.into(), pwm, From d5dae9c6c9e47e26a42e5f0e72933b6ca54e277c Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sun, 26 Jul 2026 11:38:43 +0200 Subject: [PATCH 17/22] fix(rog-control-center): eliminate unwrap panics in D-Bus calls and Slint UI callbacks --- rog-control-center/src/ui/mod.rs | 38 ++++++---- rog-control-center/src/ui/setup_system.rs | 89 ++++++++++++++++------- 2 files changed, 85 insertions(+), 42 deletions(-) diff --git a/rog-control-center/src/ui/mod.rs b/rog-control-center/src/ui/mod.rs index 8c892551..fc140e75 100644 --- a/rog-control-center/src/ui/mod.rs +++ b/rog-control-center/src/ui/mod.rs @@ -84,14 +84,19 @@ pub fn show_toast( Ok(_) => { let delayed_handle = handle.clone(); let delayed_text = success.clone(); - slint::invoke_from_event_loop(move || handle.unwrap().invoke_show_toast(success)).ok(); + slint::invoke_from_event_loop(move || { + if let Some(h) = handle.upgrade() { + h.invoke_show_toast(success); + } + }) + .ok(); tokio::spawn(async move { tokio::time::sleep(Duration::from_secs(5)).await; if TOAST_SEQ.load(Ordering::SeqCst) == seq { slint::invoke_from_event_loop(move || { - delayed_handle - .unwrap() - .invoke_clear_toast_if_matches(delayed_text) + if let Some(h) = delayed_handle.upgrade() { + h.invoke_clear_toast_if_matches(delayed_text); + } }) .ok(); } @@ -102,16 +107,18 @@ pub fn show_toast( let delayed_text = fail.clone(); slint::invoke_from_event_loop(move || { log::warn!("{fail}: {e}"); - handle.unwrap().invoke_show_toast(fail) + if let Some(h) = handle.upgrade() { + h.invoke_show_toast(fail); + } }) .ok(); tokio::spawn(async move { tokio::time::sleep(Duration::from_secs(5)).await; if TOAST_SEQ.load(Ordering::SeqCst) == seq { slint::invoke_from_event_loop(move || { - delayed_handle - .unwrap() - .invoke_clear_toast_if_matches(delayed_text) + if let Some(h) = delayed_handle.upgrade() { + h.invoke_clear_toast_if_matches(delayed_text); + } }) .ok(); } @@ -129,15 +136,12 @@ pub fn setup_window( slint::set_xdg_app_id(crate::APP_ID) .map_err(|e| warn!("Couldn't set application ID: {e:?}")) .ok(); - let ui = MainWindow::new() - .map_err(|e| warn!("Couldn't create main window: {e:?}")) - .unwrap(); + let ui = MainWindow::new().expect("Couldn't create main window"); // propagate TUF flag to the UI so the sidebar can swap logo branding ui.set_is_tuf(is_tuf); - ui.window() - .show() - .map_err(|e| warn!("Couldn't show main window: {e:?}")) - .unwrap(); + if let Err(e) = ui.window().show() { + warn!("Couldn't show main window: {e:?}"); + } let available = list_iface_blocking().unwrap_or_default(); ui.set_sidebar_items_avilable( @@ -157,7 +161,9 @@ pub fn setup_window( ); ui.on_exit_app(move || { - slint::quit_event_loop().unwrap(); + if let Err(e) = slint::quit_event_loop() { + log::warn!("Failed to quit event loop: {e:?}"); + } }); setup_app_settings_page(&ui, config.clone(), shortcuts); diff --git a/rog-control-center/src/ui/setup_system.rs b/rog-control-center/src/ui/setup_system.rs index 93bcfb92..0f15035f 100644 --- a/rog-control-center/src/ui/setup_system.rs +++ b/rog-control-center/src/ui/setup_system.rs @@ -223,9 +223,27 @@ macro_rules! init_minmax_property { let proxy_copy = $attr.clone(); let handle_copy = $handle.as_weak(); tokio::spawn(async move { - let min = proxy_copy.min_value().await.unwrap(); - let max = proxy_copy.max_value().await.unwrap(); - let current = proxy_copy.current_value().await.unwrap() as f32; + let min = match proxy_copy.min_value().await { + Ok(m) => m, + Err(e) => { + log::error!("Failed to read min value for property {}: {}", stringify!($property), e); + return; + } + }; + let max = match proxy_copy.max_value().await { + Ok(m) => m, + Err(e) => { + log::error!("Failed to read max value for property {}: {}", stringify!($property), e); + return; + } + }; + let current = match proxy_copy.current_value().await { + Ok(c) => c as f32, + Err(e) => { + log::error!("Failed to read current value for property {}: {}", stringify!($property), e); + return; + } + }; handle_copy .upgrade_in_event_loop(move |handle| { concat_idents!(setter = set_, $property { @@ -350,9 +368,27 @@ macro_rules! setup_minmax_external { while let Some(e) = x.next().await { if let Ok(_) = e.get().await { debug!("receive_platform_profile_changed, getting new {}", stringify!(attr)); - let min = proxy_copy.min_value().await.unwrap(); - let max = proxy_copy.max_value().await.unwrap(); - let current = proxy_copy.current_value().await.unwrap() as f32; + let min = match proxy_copy.min_value().await { + Ok(m) => m, + Err(e) => { + log::error!("Failed to get min value on profile change: {e}"); + continue; + } + }; + let max = match proxy_copy.max_value().await { + Ok(m) => m, + Err(e) => { + log::error!("Failed to get max value on profile change: {e}"); + continue; + } + }; + let current = match proxy_copy.current_value().await { + Ok(c) => c as f32, + Err(e) => { + log::error!("Failed to get current value on profile change: {e}"); + continue; + } + }; handle_copy .upgrade_in_event_loop(move |handle| { concat_idents!(setter = set_, $property { @@ -390,26 +426,27 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc>) tokio::spawn(async move { // Create the connections/proxies here to prevent future delays in process - let conn = zbus::Connection::system() - .await - .map_err(|e| { - log::error!("Failed to connect to system bus: {}", e); - }) - .unwrap(); - let platform = PlatformProxy::builder(&conn) - .build() - .await - .map_err(|e| { - log::error!("Failed to create platform proxy: {}", e); - }) - .unwrap(); - let backlight = BacklightProxy::builder(&conn) - .build() - .await - .map_err(|e| { - log::error!("Failed to create backlight proxy: {}", e); - }) - .unwrap(); + let conn = match zbus::Connection::system().await { + Ok(c) => c, + Err(e) => { + log::error!("Failed to connect to system bus: {e}"); + return; + } + }; + let platform = match PlatformProxy::builder(&conn).build().await { + Ok(p) => p, + Err(e) => { + log::error!("Failed to create platform proxy: {e}"); + return; + } + }; + let backlight = match BacklightProxy::builder(&conn).build().await { + Ok(b) => b, + Err(e) => { + log::error!("Failed to create backlight proxy: {e}"); + return; + } + }; debug!("Setting up system page profile callbacks"); set_ui_props_async!( From 646230fe85a470f1b091e3bedaf8bdaf26c7b663 Mon Sep 17 00:00:00 2001 From: Marco Scardovi Date: Sun, 26 Jul 2026 11:39:54 +0200 Subject: [PATCH 18/22] fix(asusd): replace panics with graceful error handling and fallbacks --- asusd/src/aura_laptop/config.rs | 5 +++-- rog-control-center/src/main.rs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/asusd/src/aura_laptop/config.rs b/asusd/src/aura_laptop/config.rs index 69fc3ae8..6ac6e61b 100644 --- a/asusd/src/aura_laptop/config.rs +++ b/asusd/src/aura_laptop/config.rs @@ -34,12 +34,13 @@ pub struct AuraConfig { impl StdConfig for AuraConfig { fn new() -> Self { - panic!("This should not be used"); + Self::default() } fn file_name(&self) -> String { if self.config_name.is_empty() { - panic!("Config file name should not be empty"); + log::warn!("Config file name is empty, falling back to aura.ron"); + return "aura.ron".to_string(); } self.config_name.to_owned() } diff --git a/rog-control-center/src/main.rs b/rog-control-center/src/main.rs index 1e922de9..57795a06 100644 --- a/rog-control-center/src/main.rs +++ b/rog-control-center/src/main.rs @@ -116,7 +116,8 @@ async fn main() -> Result<()> { let cli_parsed = match CliStart::parse_args_default(&args) { Ok(p) => p, Err(err) => { - panic!("source {}", err); + eprintln!("Error parsing command line arguments: {err}"); + return Ok(()); } }; From 82a8fafecd3f614290de6a764f393a58e44252ee Mon Sep 17 00:00:00 2001 From: Ahmed Yaseen Date: Sun, 26 Jul 2026 19:31:58 +0500 Subject: [PATCH 19/22] fix(rog-scsi): return SG_IO results via &mut Task and drop unsound send/receive --- Cargo.toml | 1 - asusd/src/aura_scsi/mod.rs | 4 +- rog-scsi/Cargo.toml | 1 - rog-scsi/src/sg.rs | 93 +++++--------------------------------- 4 files changed, 13 insertions(+), 86 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a6a6fe4d..07891e17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ libc = "^0.2" log = "^0.4" logind-zbus = { version = "^5.2.0" } mio = "^0.8.11" -nix = { version = "^0.29", features = ["uio"] } notify-rust = { version = "^4.11.5", features = ["z", "async"] } nvml-wrapper = "^0.12.0" pix = "^0.13" diff --git a/asusd/src/aura_scsi/mod.rs b/asusd/src/aura_scsi/mod.rs index c87575de..69e7c7f9 100644 --- a/asusd/src/aura_scsi/mod.rs +++ b/asusd/src/aura_scsi/mod.rs @@ -25,8 +25,8 @@ impl ScsiAura { } pub async fn write_effect(&self, effect: &AuraEffect) -> Result<(), RogError> { - let tasks: Vec = effect.into(); - for task in &tasks { + let mut tasks: Vec = effect.into(); + for task in &mut tasks { self.device.lock().await.perform(task).ok(); } Ok(()) diff --git a/rog-scsi/Cargo.toml b/rog-scsi/Cargo.toml index 99992933..cafb9f1d 100644 --- a/rog-scsi/Cargo.toml +++ b/rog-scsi/Cargo.toml @@ -16,7 +16,6 @@ dbus = ["zbus"] [dependencies] libc.workspace = true -nix.workspace = true serde.workspace = true zbus = { workspace = true, optional = true } diff --git a/rog-scsi/src/sg.rs b/rog-scsi/src/sg.rs index 7009b440..219bcd5a 100644 --- a/rog-scsi/src/sg.rs +++ b/rog-scsi/src/sg.rs @@ -1,6 +1,6 @@ use std::ffi::c_void; use std::fs::{File, OpenOptions}; -use std::io::{self, IoSlice, IoSliceMut}; +use std::io; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::Path; @@ -13,7 +13,6 @@ pub const SG_DXFER_TO_FROM_DEV: i32 = -4; pub const SG_INFO_OK_MASK: u32 = 0x1; pub const SG_INFO_OK: u32 = 0x0; -pub const SG_MAX_QUEUE: usize = 16; pub const SG_IO: u64 = 0x2285; #[repr(C)] @@ -122,7 +121,7 @@ impl Task { } /// Prepares raw internal pointers in SgIoHdr to match current buffer memory addresses. - pub fn sync_pointers(&mut self) { + fn sync_pointers(&mut self) { if !self.cmd.is_empty() { self.inner.cmdp = self.cmd.as_mut_ptr(); self.inner.cmd_len = self.cmd.len() as u8; @@ -239,91 +238,21 @@ impl Device { )) } - /// Sends multiple tasks to the SCSI device queue. - pub fn send(&self, tasks: &mut [Task]) -> io::Result { - if tasks.is_empty() { - return Ok(0); - } - - for task in tasks.iter_mut() { - task.sync_pointers(); - } - - // SAFETY: SgIoHdr is a repr(C) struct with standard byte layout. We create a read-only - // slice over the byte buffer of each SgIoHdr for writev. - let iovecs: Vec = tasks - .iter() - .map(|task| { - IoSlice::new(unsafe { - std::slice::from_raw_parts( - &task.inner as *const SgIoHdr as *const u8, - std::mem::size_of::(), - ) - }) - }) - .collect(); - - loop { - match nix::sys::uio::writev(&self.0, &iovecs[..tasks.len()]) { - Ok(n) => break Ok(n / std::mem::size_of::()), - Err(nix::errno::Errno::EINTR) => {} - Err(e) => break Err(e.into()), - } - } - } - - /// Receives completed tasks from the SCSI device queue. - pub fn receive(&self, tasks: &mut Vec) -> io::Result { - let mut hdrs = [SgIoHdr::default(); SG_MAX_QUEUE]; - - // SAFETY: SgIoHdr is a repr(C) struct. We create a mutable byte slice over each header - // buffer to allow readv to populate header data. - let mut iovecs: Vec = hdrs - .iter_mut() - .map(|hdr| { - IoSliceMut::new(unsafe { - std::slice::from_raw_parts_mut( - hdr as *mut SgIoHdr as *mut u8, - std::mem::size_of::(), - ) - }) - }) - .collect(); - - let bytes_read = loop { - match nix::sys::uio::readv(&self.0, iovecs.as_mut_slice()) { - Ok(n) => break n, - Err(e) if e == nix::errno::Errno::EINTR || e == nix::errno::Errno::EAGAIN => {} - Err(e) => return Err(e.into()), - } - }; - - if bytes_read == 0 { - return Ok(0); - } - - let tasks_read = bytes_read / std::mem::size_of::(); - for hdr in hdrs.iter().take(tasks_read) { - let mut task = Task::new(); - task.inner = *hdr; - tasks.push(task); - } - Ok(tasks_read) - } - - /// Performs a synchronous SCSI IO operation via ioctl. - pub fn perform(&self, task: &Task) -> io::Result<()> { - let mut task_copy = task.clone(); - task_copy.sync_pointers(); + /// Performs a synchronous SCSI IO operation via ioctl. On success the kernel + /// has written command status, sense data and any FromDevice payload back + /// into `task`, so results can be read via its accessors. + pub fn perform(&self, task: &mut Task) -> io::Result<()> { + task.sync_pointers(); #[cfg(target_env = "musl")] let request = SG_IO as i32; #[cfg(not(target_env = "musl"))] let request: u64 = SG_IO; - // SAFETY: The raw file descriptor is open and valid, and task_copy has valid synced - // buffer pointers matching the initialized memory of task_copy.inner. - let ret = unsafe { libc::ioctl(self.0.as_raw_fd(), request, &task_copy.inner) }; + // SAFETY: The raw file descriptor is open and valid, and task has valid synced + // pointers into its own buffers, which stay alive for the duration of the + // synchronous ioctl. + let ret = unsafe { libc::ioctl(self.0.as_raw_fd(), request, &mut task.inner) }; if ret == -1 { Err(io::Error::last_os_error()) } else { From adc11a041e3346086d64fb90d78d0afb39268f2d Mon Sep 17 00:00:00 2001 From: Ahmed Yaseen Date: Sun, 26 Jul 2026 19:32:53 +0500 Subject: [PATCH 20/22] fix(asusd): log start_tasks failures instead of silently skipping devices --- asusd/src/aura_manager.rs | 58 +++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/asusd/src/aura_manager.rs b/asusd/src/aura_manager.rs index 798441e1..d7c83d93 100644 --- a/asusd/src/aura_manager.rs +++ b/asusd/src/aura_manager.rs @@ -154,7 +154,14 @@ impl DeviceManager { let path = dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_slash()); let ctrl = SlashZbus::new(slash); - if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + if ctrl + .start_tasks(connection, path.clone()) + .await + .map_err(|e| { + error!("Failed to start Slash tasks: {e:?}, not adding this device") + }) + .is_ok() + { devices.push(AsusDevice { device: dev_type, dbus_path: path, @@ -174,7 +181,14 @@ impl DeviceManager { let path = dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_anime()); let ctrl = AniMeZbus::new(anime); - if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + if ctrl + .start_tasks(connection, path.clone()) + .await + .map_err(|e| { + error!("Failed to start AniMe tasks: {e:?}, not adding this device") + }) + .is_ok() + { devices.push(AsusDevice { device: dev_type, dbus_path: path, @@ -194,7 +208,14 @@ impl DeviceManager { let path = dbus_path_for_dev(&usb_device).unwrap_or(dbus_path_for_tuf()); let ctrl = AuraZbus::new(aura); - if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + if ctrl + .start_tasks(connection, path.clone()) + .await + .map_err(|e| { + error!("Failed to start Aura tasks: {e:?}, not adding this device") + }) + .is_ok() + { devices.push(AsusDevice { device: dev_type, dbus_path: path, @@ -272,7 +293,16 @@ impl DeviceManager { if let Ok(dev_type) = DeviceHandle::maybe_scsi(dev_str, &prod_id).await { if let DeviceHandle::Scsi(scsi) = dev_type.clone() { let ctrl = ScsiZbus::new(scsi); - if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + if ctrl + .start_tasks(connection, path.clone()) + .await + .map_err(|e| { + error!( + "Failed to start SCSI tasks: {e:?}, not adding this device" + ) + }) + .is_ok() + { return Some(AsusDevice { device: dev_type, dbus_path: path, @@ -356,7 +386,14 @@ impl DeviceManager { if let DeviceHandle::Slash(slash) = dev_type.clone() { let path = dbus_path_for_slash(); let ctrl = SlashZbus::new(slash); - if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + if ctrl + .start_tasks(connection, path.clone()) + .await + .map_err(|e| { + error!("Failed to start Slash tasks: {e:?}, not adding this device") + }) + .is_ok() + { devices.push(AsusDevice { device: dev_type, dbus_path: path, @@ -408,7 +445,16 @@ impl DeviceManager { if let DeviceHandle::Aura(aura) = dev_type.clone() { let path = dbus_path_for_tuf(); let ctrl = AuraZbus::new(aura); - if ctrl.start_tasks(connection, path.clone()).await.is_ok() { + if ctrl + .start_tasks(connection, path.clone()) + .await + .map_err(|e| { + error!( + "Failed to start TUF Aura tasks: {e:?}, not adding this device" + ) + }) + .is_ok() + { devices.push(AsusDevice { device: dev_type, dbus_path: path, From 07f015d0cd23002677bf904d44cbc9e60862ba1a Mon Sep 17 00:00:00 2001 From: Ahmed Yaseen Date: Sun, 26 Jul 2026 19:33:21 +0500 Subject: [PATCH 21/22] fix(rog-control-center): exit non-zero when CLI args fail to parse --- rog-control-center/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rog-control-center/src/main.rs b/rog-control-center/src/main.rs index 57795a06..d36ceb9d 100644 --- a/rog-control-center/src/main.rs +++ b/rog-control-center/src/main.rs @@ -117,7 +117,7 @@ async fn main() -> Result<()> { Ok(p) => p, Err(err) => { eprintln!("Error parsing command line arguments: {err}"); - return Ok(()); + std::process::exit(1); } }; From fb440492f0cb8dbe6faa341495a22cc090cf1b7f Mon Sep 17 00:00:00 2001 From: Ahmed Yaseen Date: Sun, 26 Jul 2026 19:35:00 +0500 Subject: [PATCH 22/22] fix(asusctl): report unreachable asusd separately from a missing firmware attribute --- asusctl/src/main.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index b8000aec..d4cbef1b 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -1023,13 +1023,13 @@ fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box { let mut found = false; - if let Ok(attrs) = find_iface::("xyz.ljones.AsusArmoury") { - for attr in attrs.iter() { - let name = attr.name()?; - if <&str>::from(name) == g.property { - print_firmware_attr(attr)?; - found = true; - } + let attrs = find_iface::("xyz.ljones.AsusArmoury") + .map_err(|e| format!("Could not reach asusd armoury interface: {e}"))?; + for attr in attrs.iter() { + let name = attr.name()?; + if <&str>::from(name) == g.property { + print_firmware_attr(attr)?; + found = true; } } if !found { @@ -1039,19 +1039,19 @@ fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box { let mut found = false; - if let Ok(attrs) = find_iface::("xyz.ljones.AsusArmoury") { - for attr in attrs.iter() { - let name = attr.name()?; - if <&str>::from(name) == s.property { - let mut value: i32 = s.value; - if value == -1 { - info!("Setting to default"); - value = attr.default_value()?; - } - attr.set_current_value(value)?; - print_firmware_attr(attr)?; - found = true; + let attrs = find_iface::("xyz.ljones.AsusArmoury") + .map_err(|e| format!("Could not reach asusd armoury interface: {e}"))?; + for attr in attrs.iter() { + let name = attr.name()?; + if <&str>::from(name) == s.property { + let mut value: i32 = s.value; + if value == -1 { + info!("Setting to default"); + value = attr.default_value()?; } + attr.set_current_value(value)?; + print_firmware_attr(attr)?; + found = true; } } if !found {