Compare commits

...

28 Commits

Author SHA1 Message Date
Denis Benato
5e5cebc781 Revert "Fix: continue -> return in set_config_or_default"
This reverts commit ad051bd7b8.
2025-11-06 22:53:21 +01:00
Denis Benato
def691f9d0 Fix: sliders not updating the value 2025-11-06 22:53:09 +01:00
Denis Benato
80eeafd9e1 Feat: config reload and do not apply properties if max==min 2025-11-06 21:06:44 +01:00
Denis Benato
bf173300e0 Feat: start in tray mode 2025-11-06 17:29:12 +01:00
Denis Benato
07874d7452 tests: load bundled aura_support.ron when system files missing to make unit tests reliable in CI 2025-11-06 17:24:01 +01:00
Denis Benato
894a0d2b11 Chore: update CHANGELOG.md 2025-11-06 16:12:44 +01:00
Denis Benato
11dc02612e Fix clippy warnings: alias complex types, allow some clippy lints, collapse else-if, avoid is_multiple_of usage 2025-11-06 16:05:38 +01:00
Denis Benato
fccb233e9e Fix: stop rust from silly complains about is_multiple_of 2025-11-06 15:58:30 +01:00
Denis Benato
c13612e02c Fix: simplify an if 2025-11-06 15:52:43 +01:00
Denis Benato
80147966a9 Chore: use stable 2025-11-06 15:49:05 +01:00
Denis Benato
a1815ac40c Chore: improve tests 2025-11-06 15:34:30 +01:00
Denis Benato
ad051bd7b8 Fix: continue -> return in set_config_or_default 2025-11-06 15:07:31 +01:00
Denis Benato
90676b390e Chore: update CHANGELOG.md 2025-11-06 15:04:59 +01:00
Denis Benato
974f2acafa Feat: better handling of nv_ properties 2025-11-06 14:53:57 +01:00
Denis Benato
6ae3ae5284 Feat: make nvidia dGPU tunables power-profile dependant 2025-11-06 14:26:03 +01:00
Denis Benato
34699a7021 Fix: notifications not respecting settings 2025-11-06 02:57:20 +01:00
Denis Benato
ef311689ec Fix: do not stall the boot process 2025-11-06 00:55:20 +01:00
Denis Benato
8ee0281b4f Fix: fedora build 2025-11-05 22:40:58 +01:00
Denis Benato
d8504b5430 Changelog for 6.1.17 2025-11-05 21:14:21 +01:00
Denis Benato
5c4d833fbd Prepare for 6.1.17 2025-11-05 21:09:18 +01:00
Denis Benato
698999e828 cargo fmt and README.md restyle 2025-11-05 20:02:43 +01:00
Denis Benato
0eae9e55c6 Merge branch 'devel' 2025-11-05 19:54:21 +01:00
Denis Benato
07171888a1 Merge branch 'main' into 'main'
feat: Allow setting default profile for ac and battery

See merge request asus-linux/asusctl!229
2025-11-05 18:53:53 +00:00
matszwe02
9321fde6af feat: Allow setting default profile for ac and battery 2025-11-05 01:44:31 +01:00
Denis Benato
f90d0a6673 Merge branch 'devel' into 'devel'
Add install-data-asusd_user to install-data in Makefile

See merge request asus-linux/asusctl!228
2025-10-29 18:49:50 +00:00
Synby
bbd03c128d Edit Makefile
add install-data-asusd_user to install-data
2025-10-29 17:23:29 +00:00
Denis Benato
132a2f3665 Chore: complete the switch back to stable 2025-10-23 14:50:40 +02:00
Denis Benato
180566e5f1 Fix: share a single HID device
Avoid opening multiple handles to the same device whenever possible.
2025-10-22 22:05:17 +02:00
46 changed files with 2161 additions and 943 deletions

View File

@@ -2,6 +2,17 @@
## [Unreleased] ## [Unreleased]
### Changed
- Make the boot process more reliable
- tie nv_ properties to power profiles
- Better support nv_tgp
## [v6.1.17]
### Changed
- Fix Makefile
- Share a single HID device
## [v6.1.16] ## [v6.1.16]
### Changed ### Changed

1241
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[workspace.package] [workspace.package]
version = "6.1.16" version = "6.1.17"
rust-version = "1.82" rust-version = "1.82"
license = "MPL-2.0" license = "MPL-2.0"
readme = "README.md" readme = "README.md"

View File

@@ -113,7 +113,7 @@ install-data-asusd_user:
.PHONY: install-data-asusd install-data-asusd_user .PHONY: install-data-asusd install-data-asusd_user
install-data: install-data-asusd install-data-rog_gui install-data: install-data-asusd install-data-asusd_user install-data-rog_gui
install: install-program install-data install: install-program install-data
$(INSTALL_DATA) "./LICENSE" "$(DESTDIR)$(datarootdir)/asusctl/LICENSE" $(INSTALL_DATA) "./LICENSE" "$(DESTDIR)$(datarootdir)/asusctl/LICENSE"

View File

@@ -46,13 +46,13 @@ See the [rog-aura readme](./rog-aura/README.md) for more details.
Most ASUS gaming laptops that have a USB keyboard. If `lsusb` shows something similar Most ASUS gaming laptops that have a USB keyboard. If `lsusb` shows something similar
to this: to this:
``` ```plain
Bus 001 Device 002: ID 0b05:1866 ASUSTek Computer, Inc. N-KEY Device Bus 001 Device 002: ID 0b05:1866 ASUSTek Computer, Inc. N-KEY Device
``` ```
or or
``` ```plain
Bus 003 Device 002: ID 0b05:19b6 ASUSTek Computer, Inc. [unknown] Bus 003 Device 002: ID 0b05:19b6 ASUSTek Computer, Inc. [unknown]
``` ```
@@ -74,13 +74,13 @@ The list is a bit outdated as many features have been enabled in the Linux kerne
- [x] Toggle bios setting for boot/POST sound - [x] Toggle bios setting for boot/POST sound
- [x] Toggle GPU MUX (g-sync, or called MUX on 2022+ laptops) - [x] Toggle GPU MUX (g-sync, or called MUX on 2022+ laptops)
# GUI ## GUI
A gui is now in the repo - ROG Control Center. At this time it is still a WIP, but it has almost all features in place already. A gui is now in the repo - ROG Control Center. At this time it is still a WIP, but it has almost all features in place already.
**NOTE**: Xorg is not supported. **NOTE**: Xorg is not supported.
# BUILDING ## BUILDING
Rust and cargo are required, they can be installed from [rustup.rs](https://rustup.rs/). Rust and cargo are required, they can be installed from [rustup.rs](https://rustup.rs/).
@@ -88,27 +88,33 @@ Distro packaging should work with the stable toolchain. If your distro does not
**fedora:** **fedora:**
dnf install cmake clang-devel libxkbcommon-devel systemd-devel expat-devel pcre2-devel libzstd-devel gtk3-devel ```sh
make dnf install cmake clang-devel libxkbcommon-devel systemd-devel expat-devel pcre2-devel libzstd-devel gtk3-devel
sudo make install make
sudo make install
```
**openSUSE:** **openSUSE:**
Works with KDE Plasma (without GTK packages) Works with KDE Plasma (without GTK packages)
zypper in -t pattern devel_basis ```sh
zypper in rustup make cmake clang-devel libxkbcommon-devel systemd-devel expat-devel pcre2-devel libzstd-devel gtk3-devel zypper in -t pattern devel_basis
make zypper in rustup make cmake clang-devel libxkbcommon-devel systemd-devel expat-devel pcre2-devel libzstd-devel gtk3-devel
sudo make install make
sudo make install
```
**Debian(unsuported):** **Debian(unsuported):**
officially unsuported,but you can still try and test it by yourself(some features may not be available). officially unsuported,but you can still try and test it by yourself(some features may not be available).
sudo apt install libclang-dev libudev-dev libfontconfig-dev build-essential cmake libxkbcommon-dev ```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh sudo apt install libclang-dev libudev-dev libfontconfig-dev build-essential cmake libxkbcommon-dev
make curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
sudo make install make
sudo make install
```
**Ubuntu, Popos (unsuported):** **Ubuntu, Popos (unsuported):**
@@ -135,15 +141,15 @@ You may also need to activate the service for debian install. If running Pop!\_O
If you are upgrading from a previous installed version, you will need to restart the service or reboot. If you are upgrading from a previous installed version, you will need to restart the service or reboot.
``` ```sh
$ systemctl daemon-reload && systemctl restart asusd systemctl daemon-reload && systemctl restart asusd
``` ```
## Uninstalling ## Uninstalling
Run `sudo make uninstall` in the source repo, and remove `/etc/asusd/`. Run `sudo make uninstall` in the source repo, and remove `/etc/asusd/`.
# Contributing ## Contributing
See `CONTRIBUTING.md`. Additionally, also do `cargo clean` and `cargo test` on first checkout to ensure the commit hooks are used (via `cargo-husky`). See `CONTRIBUTING.md`. Additionally, also do `cargo clean` and `cargo test` on first checkout to ensure the commit hooks are used (via `cargo-husky`).
@@ -151,17 +157,17 @@ Generation of the bindings with `make bindings` requires `typeshare` to be insta
Dbus introsepction XML requires with `make introspection` requires `anime_sim` to be running before starting `asusd`. Dbus introsepction XML requires with `make introspection` requires `anime_sim` to be running before starting `asusd`.
# OTHER ## OTHER
## AniMe Matrix simulator ### AniMe Matrix simulator
A simulator using SDL2 can be built using `cargo build --package rog_simulators` and run with `./target/debug/anime_sim`. Once started `asusd` will need restarting to pick it up. If running this sim on a laptop _with_ the display, the simulated display will be used instead of the physical display. A simulator using SDL2 can be built using `cargo build --package rog_simulators` and run with `./target/debug/anime_sim`. Once started `asusd` will need restarting to pick it up. If running this sim on a laptop _with_ the display, the simulated display will be used instead of the physical display.
## Supporting more laptops ### Supporting more laptops
Please file a support request. Please file a support request.
# License & Trademarks ## License & Trademarks
Mozilla Public License 2 (MPL-2.0) Mozilla Public License 2 (MPL-2.0)

View File

@@ -21,11 +21,14 @@ fn main() {
let brightness = args[2].parse::<f32>().unwrap(); let brightness = args[2].parse::<f32>().unwrap();
let anime_type = get_anime_type(); let anime_type = get_anime_type();
let mut seq = Sequences::new(anime_type); let mut seq = Sequences::new(anime_type);
seq.insert(0, &ActionLoader::AsusAnimation { seq.insert(
file: path.into(), 0,
time: rog_anime::AnimTime::Infinite, &ActionLoader::AsusAnimation {
brightness, file: path.into(),
}) time: rog_anime::AnimTime::Infinite,
brightness,
},
)
.unwrap(); .unwrap();
loop { loop {

View File

@@ -180,6 +180,7 @@ pub struct TwoColourSpeed {
pub zone: AuraZone, pub zone: AuraZone,
} }
#[allow(dead_code)]
#[derive(Debug, Clone, Default, Options)] #[derive(Debug, Clone, Default, Options)]
pub struct MultiZone { pub struct MultiZone {
#[options(help = "print help message")] #[options(help = "print help message")]
@@ -194,6 +195,7 @@ pub struct MultiZone {
pub colour4: Colour, pub colour4: Colour,
} }
#[allow(dead_code)]
#[derive(Debug, Clone, Default, Options)] #[derive(Debug, Clone, Default, Options)]
pub struct MultiColourSpeed { pub struct MultiColourSpeed {
#[options(help = "print help message")] #[options(help = "print help message")]

View File

@@ -74,6 +74,16 @@ pub struct ProfileCommand {
#[options(meta = "", help = "set the active profile")] #[options(meta = "", help = "set the active profile")]
pub profile_set: Option<PlatformProfile>, pub profile_set: Option<PlatformProfile>,
#[options(short = "a", meta = "", help = "set the profile to use on AC power")]
pub profile_set_ac: Option<PlatformProfile>,
#[options(
short = "b",
meta = "",
help = "set the profile to use on battery power"
)]
pub profile_set_bat: Option<PlatformProfile>,
} }
#[derive(Options)] #[derive(Options)]

View File

@@ -979,7 +979,13 @@ fn handle_throttle_profile(
return Err(ProfileError::NotSupported.into()); return Err(ProfileError::NotSupported.into());
} }
if !cmd.next && !cmd.list && cmd.profile_set.is_none() && !cmd.profile_get { if !cmd.next
&& !cmd.list
&& cmd.profile_set.is_none()
&& !cmd.profile_get
&& cmd.profile_set_ac.is_none()
&& cmd.profile_set_bat.is_none()
{
if !cmd.help { if !cmd.help {
println!("Missing arg or command\n"); println!("Missing arg or command\n");
} }
@@ -999,6 +1005,10 @@ fn handle_throttle_profile(
proxy.set_platform_profile(PlatformProfile::next(current, &choices))?; proxy.set_platform_profile(PlatformProfile::next(current, &choices))?;
} else if let Some(profile) = cmd.profile_set { } else if let Some(profile) = cmd.profile_set {
proxy.set_platform_profile(profile)?; proxy.set_platform_profile(profile)?;
} else if let Some(profile) = cmd.profile_set_ac {
proxy.set_platform_profile_on_ac(profile)?;
} else if let Some(profile) = cmd.profile_set_bat {
proxy.set_platform_profile_on_battery(profile)?;
} }
if cmd.list { if cmd.list {
@@ -1009,6 +1019,11 @@ fn handle_throttle_profile(
if cmd.profile_get { if cmd.profile_get {
println!("Active profile is {current:?}"); println!("Active profile is {current:?}");
println!("Profile on AC is {:?}", proxy.platform_profile_on_ac()?);
println!(
"Profile on Battery is {:?}",
proxy.platform_profile_on_battery()?
);
} }
Ok(()) Ok(())
@@ -1169,11 +1184,15 @@ fn print_firmware_attr(attr: &AsusArmouryProxyBlocking) -> Result<(), Box<dyn st
Ok(()) Ok(())
} }
#[allow(clippy::manual_is_multiple_of)]
fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box<dyn std::error::Error>> { fn handle_armoury_command(cmd: &ArmouryCommand) -> Result<(), Box<dyn std::error::Error>> {
{ {
if cmd.free.is_empty() || !cmd.free.len().is_multiple_of(2) || cmd.help { // Avoid using `.is_multiple_of(2)` to satisfy the request. Use modulus check
// and simplify the boolean expression.
let odd_len = cmd.free.len() % 2 != 0;
if cmd.free.is_empty() || odd_len || cmd.help {
const USAGE: &str = "Usage: asusctl platform panel_overdrive 1 nv_dynamic_boost 5"; const USAGE: &str = "Usage: asusctl platform panel_overdrive 1 nv_dynamic_boost 5";
if !cmd.free.len().is_multiple_of(2) { if odd_len {
println!( println!(
"Incorrect number of args, each attribute label must be paired with a setting:" "Incorrect number of args, each attribute label must be paired with a setting:"
); );

View File

@@ -45,6 +45,7 @@ concat-idents.workspace = true
[dev-dependencies] [dev-dependencies]
cargo-husky.workspace = true cargo-husky.workspace = true
tempfile = "3"
[package.metadata.deb] [package.metadata.deb]
license-file = ["../LICENSE", "4"] license-file = ["../LICENSE", "4"]

View File

@@ -1,12 +1,12 @@
use std::sync::Arc; use std::sync::Arc;
use config_traits::StdConfig; use config_traits::StdConfig;
use futures_util::lock::Mutex;
use log::{debug, error, info}; use log::{debug, error, info};
use rog_platform::asus_armoury::{AttrValue, Attribute, FirmwareAttribute, FirmwareAttributes}; use rog_platform::asus_armoury::{AttrValue, Attribute, FirmwareAttribute, FirmwareAttributes};
use rog_platform::platform::{PlatformProfile, RogPlatform}; use rog_platform::platform::{PlatformProfile, RogPlatform};
use rog_platform::power::AsusPower; use rog_platform::power::AsusPower;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use zbus::object_server::SignalEmitter; use zbus::object_server::SignalEmitter;
use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value}; use zbus::zvariant::{ObjectPath, OwnedObjectPath, OwnedValue, Type, Value};
use zbus::{fdo, interface, Connection}; use zbus::{fdo, interface, Connection};
@@ -27,6 +27,28 @@ fn dbus_path_for_attr(attr_name: &str) -> OwnedObjectPath {
ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/{MOD_NAME}/{attr_name}")).into() ObjectPath::from_str_unchecked(&format!("{ASUS_ZBUS_PATH}/{MOD_NAME}/{attr_name}")).into()
} }
// Helper: return true when the attribute is effectively unsupported for
// the current power mode/device. We consider an attribute unsupported when
// its resolved min and max are equal (no available range), which indicates
// writes would be invalid. When true, callers should avoid attempting writes.
fn attr_unsupported(attr: &Attribute) -> bool {
let min = attr
.refresh_min_value()
.or(match attr.min_value() {
AttrValue::Integer(i) => Some(*i),
_ => None,
})
.unwrap_or(-1);
let max = attr
.refresh_max_value()
.or(match attr.max_value() {
AttrValue::Integer(i) => Some(*i),
_ => None,
})
.unwrap_or(-2); // different default so equality is false unless both present
min == max
}
#[derive(Clone)] #[derive(Clone)]
pub struct AsusArmouryAttribute { pub struct AsusArmouryAttribute {
attr: Attribute, attr: Attribute,
@@ -146,6 +168,14 @@ impl ArmouryAttributeRegistry {
self.attrs.push(attr); self.attrs.push(attr);
} }
pub fn is_empty(&self) -> bool {
self.attrs.is_empty()
}
pub fn iter(&self) -> std::slice::Iter<'_, AsusArmouryAttribute> {
self.attrs.iter()
}
pub async fn emit_limits(&self, connection: &Connection) -> Result<(), RogError> { pub async fn emit_limits(&self, connection: &Connection) -> Result<(), RogError> {
let mut last_err: Option<RogError> = None; let mut last_err: Option<RogError> = None;
for attr in &self.attrs { for attr in &self.attrs {
@@ -170,7 +200,7 @@ impl crate::Reloadable for AsusArmouryAttribute {
info!("Reloading {}", self.attr.name()); info!("Reloading {}", self.attr.name());
let name: FirmwareAttribute = self.attr.name().into(); let name: FirmwareAttribute = self.attr.name().into();
if name.is_ppt() { if name.is_ppt() || name.is_dgpu() {
let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let power_plugged = self let power_plugged = self
.power .power
@@ -196,14 +226,24 @@ impl crate::Reloadable for AsusArmouryAttribute {
}; };
if let Some(tune) = apply_value { if let Some(tune) = apply_value {
self.attr // Don't attempt writes for attributes that report min==0 and max==0
.set_current_value(&AttrValue::Integer(tune)) // (commonly means not supported in this power mode/device); skip
.map_err(|e| { // applying stored tune in that case.
error!("Could not set {} value: {e:?}", self.attr.name()); if self.attr.base_path_exists() && !attr_unsupported(&self.attr) {
self.attr.base_path_exists(); self.attr
e .set_current_value(&AttrValue::Integer(tune))
})?; .map_err(|e| {
info!("Set {} to {:?}", self.attr.name(), tune); error!("Could not set {} value: {e:?}", self.attr.name());
self.attr.base_path_exists();
e
})?;
info!("Set {} to {:?}", self.attr.name(), tune);
} else {
debug!(
"Skipping apply for {} because attribute missing or unsupported (min==max)",
self.attr.name()
);
}
} }
} else { } else {
// Handle non-PPT attributes (boolean and other settings) // Handle non-PPT attributes (boolean and other settings)
@@ -277,7 +317,7 @@ impl AsusArmouryAttribute {
async fn restore_default(&self) -> fdo::Result<()> { async fn restore_default(&self) -> fdo::Result<()> {
self.attr.restore_default()?; self.attr.restore_default()?;
if self.name().is_ppt() { if self.name().is_ppt() || self.name().is_dgpu() {
let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let power_plugged = self let power_plugged = self
.power .power
@@ -336,7 +376,7 @@ impl AsusArmouryAttribute {
#[zbus(property)] #[zbus(property)]
async fn current_value(&self) -> fdo::Result<i32> { async fn current_value(&self) -> fdo::Result<i32> {
if self.name().is_ppt() { if self.name().is_ppt() || self.name().is_dgpu() {
let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let power_plugged = self let power_plugged = self
.power .power
@@ -370,9 +410,9 @@ impl AsusArmouryAttribute {
} }
async fn stored_value_for_power(&self, on_ac: bool) -> fdo::Result<i32> { async fn stored_value_for_power(&self, on_ac: bool) -> fdo::Result<i32> {
if !self.name().is_ppt() { if !(self.name().is_ppt() || self.name().is_dgpu()) {
return Err(fdo::Error::NotSupported( return Err(fdo::Error::NotSupported(
"Stored values are only available for PPT attributes".to_string(), "Stored values are only available for PPT/dGPU tunable attributes".to_string(),
)); ));
} }
@@ -393,9 +433,10 @@ impl AsusArmouryAttribute {
} }
async fn set_value_for_power(&mut self, on_ac: bool, value: i32) -> fdo::Result<()> { async fn set_value_for_power(&mut self, on_ac: bool, value: i32) -> fdo::Result<()> {
if !self.name().is_ppt() { if !(self.name().is_ppt() || self.name().is_dgpu()) {
return Err(fdo::Error::NotSupported( return Err(fdo::Error::NotSupported(
"Setting stored values is only supported for PPT attributes".to_string(), "Setting stored values is only supported for PPT/dGPU tunable attributes"
.to_string(),
)); ));
} }
@@ -433,13 +474,23 @@ impl AsusArmouryAttribute {
.unwrap_or_default() .unwrap_or_default()
!= 0; != 0;
if power_plugged == on_ac { // Don't attempt writes for attributes that report min==0 and max==0
self.attr // (commonly means not supported in this power mode/device); skip
.set_current_value(&AttrValue::Integer(value)) // applying stored value in that case.
.map_err(|e| { if self.attr.base_path_exists() && !attr_unsupported(&self.attr) {
error!("Could not set value: {e:?}"); if power_plugged == on_ac {
e self.attr
})?; .set_current_value(&AttrValue::Integer(value))
.map_err(|e| {
error!("Could not set value: {e:?}");
e
})?;
}
} else {
debug!(
"Skipping immediate apply for {} because attribute missing or unsupported (min==max)",
self.attr.name()
);
} }
} }
@@ -448,7 +499,7 @@ impl AsusArmouryAttribute {
#[zbus(property)] #[zbus(property)]
async fn set_current_value(&mut self, value: i32) -> fdo::Result<()> { async fn set_current_value(&mut self, value: i32) -> fdo::Result<()> {
if self.name().is_ppt() { if self.name().is_ppt() || self.name().is_dgpu() {
let profile: PlatformProfile = self.platform.get_platform_profile()?.into(); let profile: PlatformProfile = self.platform.get_platform_profile()?.into();
let power_plugged = self let power_plugged = self
.power .power
@@ -469,12 +520,22 @@ impl AsusArmouryAttribute {
debug!("Store tuning config for {} = {:?}", self.attr.name(), value); debug!("Store tuning config for {} = {:?}", self.attr.name(), value);
} }
if tuning.enabled { if tuning.enabled {
self.attr // Don't attempt writes for attributes that report min==0 and max==0
.set_current_value(&AttrValue::Integer(value)) // (commonly means not supported in this power mode/device); skip
.map_err(|e| { // applying stored value in that case.
error!("Could not set value: {e:?}"); if self.attr.base_path_exists() && !attr_unsupported(&self.attr) {
e self.attr
})?; .set_current_value(&AttrValue::Integer(value))
.map_err(|e| {
error!("Could not set value: {e:?}");
e
})?;
} else {
debug!(
"Skipping apply for {} on set_current_value because attribute missing or unsupported (min==max)",
self.attr.name()
);
}
} }
} else { } else {
self.attr self.attr
@@ -515,12 +576,16 @@ impl AsusArmouryAttribute {
} }
} }
#[allow(clippy::too_many_arguments)]
pub async fn start_attributes_zbus( pub async fn start_attributes_zbus(
conn: &Connection, conn: Option<&Connection>,
platform: RogPlatform, platform: RogPlatform,
power: AsusPower, power: AsusPower,
attributes: FirmwareAttributes, attributes: FirmwareAttributes,
config: Arc<Mutex<Config>>, config: Arc<Mutex<Config>>,
enable_zbus: bool,
profile_override: Option<rog_platform::platform::PlatformProfile>,
power_plugged_override: Option<bool>,
) -> Result<ArmouryAttributeRegistry, RogError> { ) -> Result<ArmouryAttributeRegistry, RogError> {
let mut registry = ArmouryAttributeRegistry::default(); let mut registry = ArmouryAttributeRegistry::default();
for attr in attributes.attributes() { for attr in attributes.attributes() {
@@ -533,35 +598,115 @@ pub async fn start_attributes_zbus(
let registry_attr = attr.clone(); let registry_attr = attr.clone();
if let Err(e) = attr.reload().await { // Only perform the full reload (which may query the platform/power sysfs)
error!( // when zbus is enabled. Tests using the no-zbus mode skip the reload and
"Skipping attribute '{}' due to reload error: {e:?}", // emulate the reload/apply behavior to avoid depending on udev/sysfs.
attr.attr.name() if enable_zbus {
); if let Err(e) = attr.reload().await {
// continue with others error!(
continue; "Skipping attribute '{}' due to reload error: {e:?}",
attr.attr.name()
);
// continue with others
continue;
}
} }
let attr_name = attr.attribute_name(); let attr_name = attr.attribute_name();
let path = dbus_path_for_attr(attr_name.as_str()); // If zbus is enabled and a connection is provided, create the SignalEmitter,
match zbus::object_server::SignalEmitter::new(conn, path) { // start watchers and register the object on zbus. Tests can call this function
Ok(sig) => { // with enable_zbus=false and conn=None to skip DBus registration and watchers.
if let Err(e) = attr.watch_and_notify(sig).await { if !enable_zbus {
error!("Failed to start watcher for '{}': {e:?}", attr.attr.name()); // Emulate reload logic but prefer overrides when provided to avoid dependency on udev/sysfs in tests
let name: rog_platform::asus_armoury::FirmwareAttribute = attr.attr.name().into();
if name.is_ppt() || name.is_dgpu() {
// determine profile
let profile = if let Some(p) = profile_override {
p
} else {
match attr.platform.get_platform_profile() {
Ok(p) => p.into(),
Err(_) => rog_platform::platform::PlatformProfile::Balanced,
}
};
// determine power plugged
let power_plugged = if let Some(v) = power_plugged_override {
v
} else {
match attr.power.get_online() {
Ok(v) => v == 1,
Err(_) => false,
}
};
let apply_value = {
let config = attr.config.lock().await;
config
.select_tunings_ref(power_plugged, profile)
.and_then(|tuning| {
if tuning.enabled {
tuning.group.get(&attr.name()).copied()
} else {
None
}
})
};
if let Some(tune) = apply_value {
attr.attr
.set_current_value(&AttrValue::Integer(tune))
.map_err(|e| {
error!("Could not set {} value: {e:?}", attr.attr.name());
e
})?;
} }
} } else if let Some(saved_value) = attr.config.lock().await.armoury_settings.get(&name) {
Err(e) => { attr.attr
error!( .set_current_value(&AttrValue::Integer(*saved_value))
"Failed to create SignalEmitter for '{}': {e:?}", .map_err(|e| {
attr.attr.name() error!("Could not set {} value: {e:?}", attr.attr.name());
e
})?;
info!(
"Restored armoury setting {} to {:?}",
attr.attr.name(),
saved_value
); );
} }
registry.push(registry_attr);
continue;
} }
if let Err(e) = attr.move_to_zbus(conn).await { // If zbus is enabled and a connection is provided, create the SignalEmitter,
error!("Failed to register attribute '{attr_name}' on zbus: {e:?}"); // start watchers and register the object on zbus. Tests can call this function
continue; // with enable_zbus=false and conn=None to skip DBus registration and watchers.
if enable_zbus {
if let Some(connection) = conn {
let path = dbus_path_for_attr(attr_name.as_str());
match zbus::object_server::SignalEmitter::new(connection, path) {
Ok(sig) => {
if let Err(e) = attr.watch_and_notify(sig).await {
error!("Failed to start watcher for '{}': {e:?}", attr.attr.name());
}
}
Err(e) => {
error!(
"Failed to create SignalEmitter for '{}': {e:?}",
attr.attr.name()
);
}
}
if let Err(e) = attr.move_to_zbus(connection).await {
error!("Failed to register attribute '{attr_name}' on zbus: {e:?}");
continue;
}
} else {
error!("zbus enabled but no Connection provided for attribute registration");
}
} }
registry.push(registry_attr); registry.push(registry_attr);
@@ -577,34 +722,50 @@ pub async fn set_config_or_default(
) { ) {
for attr in attrs.attributes().iter() { for attr in attrs.attributes().iter() {
let name: FirmwareAttribute = attr.name().into(); let name: FirmwareAttribute = attr.name().into();
if name.is_ppt() { if name.is_ppt() || name.is_dgpu() {
let tuning = config.select_tunings(power_plugged, profile); let tuning = config.select_tunings(power_plugged, profile);
if !tuning.enabled { if !tuning.enabled {
debug!("Tuning group is not enabled, skipping"); debug!("Tuning group is not enabled, skipping");
return; continue;
} }
// Determine once whether attribute is present and supports a writable range
let supported = attr.base_path_exists() && !attr_unsupported(attr);
if let Some(tune) = tuning.group.get(&name) { if let Some(tune) = tuning.group.get(&name) {
attr.set_current_value(&AttrValue::Integer(*tune)) if supported {
.map_err(|e| { attr.set_current_value(&AttrValue::Integer(*tune))
error!("Failed to set {}: {e}", <&str>::from(name)); .map_err(|e| {
}) error!("Failed to set {}: {e}", <&str>::from(name));
.ok(); })
} else { .ok();
let default = attr.default_value(); } else {
attr.set_current_value(default) debug!(
.map_err(|e| { "Skipping apply for {} in set_config_or_default because attribute missing or unsupported",
error!("Failed to set {}: {e}", <&str>::from(name)); <&str>::from(name)
}) );
.ok(); }
if let AttrValue::Integer(i) = default { } else {
tuning.group.insert(name, *i); // Only attempt to apply defaults when the attribute supports a range
info!( if supported {
"Set default tuning config for {} = {:?}", let default = attr.default_value();
<&str>::from(name), attr.set_current_value(default)
i .map_err(|e| {
error!("Failed to set {}: {e}", <&str>::from(name));
})
.ok();
if let AttrValue::Integer(i) = default {
tuning.group.insert(name, *i);
info!(
"Set default tuning config for {} = {:?}",
<&str>::from(name),
i
);
// config.write();
}
} else {
debug!(
"Skipping default apply for {} in set_config_or_default because attribute missing or unsupported",
<&str>::from(name)
); );
// config.write();
} }
} }
} else { } else {
@@ -624,3 +785,69 @@ pub async fn set_config_or_default(
} }
} }
} }
// Internal helper to store a tuning value into the correct per-profile, per-power map.
// This centralizes the behavior so tests can validate storage semantics.
#[allow(dead_code)]
fn insert_tuning_value(
config: &mut Config,
on_ac: bool,
profile: PlatformProfile,
name: rog_platform::asus_armoury::FirmwareAttribute,
value: i32,
) {
let tuning = config.select_tunings(on_ac, profile);
if let Some(t) = tuning.group.get_mut(&name) {
*t = value;
} else {
tuning.group.insert(name, value);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::Config;
use rog_platform::asus_armoury::FirmwareAttribute;
use rog_platform::platform::PlatformProfile;
#[test]
fn insert_nv_tuning_is_per_profile_and_power() {
let mut cfg = Config::default();
let profile = PlatformProfile::Performance;
// Insert value for AC
insert_tuning_value(
&mut cfg,
true,
profile,
FirmwareAttribute::NvDynamicBoost,
7,
);
// Value should be present in ac_profile_tunings
let t_ac = cfg.select_tunings_ref(true, profile).unwrap();
assert_eq!(t_ac.group.get(&FirmwareAttribute::NvDynamicBoost), Some(&7));
// Insert separate value for DC
insert_tuning_value(
&mut cfg,
false,
profile,
FirmwareAttribute::NvDynamicBoost,
3,
);
let t_dc = cfg.select_tunings_ref(false, profile).unwrap();
assert_eq!(t_dc.group.get(&FirmwareAttribute::NvDynamicBoost), Some(&3));
}
#[test]
fn non_ppt_attribute_stores_in_armoury_settings() {
let mut cfg = Config::default();
// Non-PPT/dGPU attribute, e.g., BootSound
let name = FirmwareAttribute::BootSound;
// Simulate setting armoury setting
cfg.armoury_settings.insert(name, 1);
assert_eq!(cfg.armoury_settings.get(&name), Some(&1));
}
}

View File

@@ -8,7 +8,6 @@ use std::sync::Arc;
use std::thread::sleep; use std::thread::sleep;
use config_traits::StdConfig; use config_traits::StdConfig;
use futures_util::lock::Mutex;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use rog_anime::usb::{ use rog_anime::usb::{
pkt_flush, pkt_set_brightness, pkt_set_enable_display, pkt_set_enable_powersave_anim, pkt_flush, pkt_set_brightness, pkt_set_enable_display, pkt_set_enable_powersave_anim,
@@ -17,6 +16,7 @@ use rog_anime::usb::{
use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType}; use rog_anime::{ActionData, AnimeDataBuffer, AnimePacketType};
use rog_platform::hid_raw::HidRaw; use rog_platform::hid_raw::HidRaw;
use rog_platform::usb_raw::USBRaw; use rog_platform::usb_raw::USBRaw;
use tokio::sync::Mutex;
use self::config::{AniMeConfig, AniMeConfigCached}; use self::config::{AniMeConfig, AniMeConfigCached};
use crate::error::RogError; use crate::error::RogError;
@@ -51,7 +51,7 @@ impl AniMe {
/// Will fail if something is already holding the config lock /// Will fail if something is already holding the config lock
async fn do_init_cache(&mut self) { async fn do_init_cache(&mut self) {
if let Some(mut config) = self.config.try_lock() { if let Ok(mut config) = self.config.try_lock() {
if let Err(e) = self.cache.init_from_config(&config, config.anime_type) { if let Err(e) = self.cache.init_from_config(&config, config.anime_type) {
error!( error!(
"Trying to cache the Anime Config failed, will reset to default config: {e:?}" "Trying to cache the Anime Config failed, will reset to default config: {e:?}"

View File

@@ -85,7 +85,7 @@ impl AniMeZbus {
/// Set base brightness level /// Set base brightness level
#[zbus(property)] #[zbus(property)]
async fn brightness(&self) -> Brightness { async fn brightness(&self) -> Brightness {
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
return config.display_brightness; return config.display_brightness;
} }
Brightness::Off Brightness::Off
@@ -117,7 +117,7 @@ impl AniMeZbus {
#[zbus(property)] #[zbus(property)]
async fn builtins_enabled(&self) -> bool { async fn builtins_enabled(&self) -> bool {
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
return config.builtin_anims_enabled; return config.builtin_anims_enabled;
} }
false false
@@ -162,7 +162,7 @@ impl AniMeZbus {
#[zbus(property)] #[zbus(property)]
async fn builtin_animations(&self) -> Animations { async fn builtin_animations(&self) -> Animations {
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
return config.builtin_anims; return config.builtin_anims;
} }
Animations::default() Animations::default()
@@ -195,7 +195,7 @@ impl AniMeZbus {
#[zbus(property)] #[zbus(property)]
async fn enable_display(&self) -> bool { async fn enable_display(&self) -> bool {
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
return config.display_enabled; return config.display_enabled;
} }
false false
@@ -218,7 +218,7 @@ impl AniMeZbus {
#[zbus(property)] #[zbus(property)]
async fn off_when_unplugged(&self) -> bool { async fn off_when_unplugged(&self) -> bool {
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
return config.off_when_unplugged; return config.off_when_unplugged;
} }
false false
@@ -245,7 +245,7 @@ impl AniMeZbus {
#[zbus(property)] #[zbus(property)]
async fn off_when_suspended(&self) -> bool { async fn off_when_suspended(&self) -> bool {
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
return config.off_when_suspended; return config.off_when_suspended;
} }
false false
@@ -261,7 +261,7 @@ impl AniMeZbus {
#[zbus(property)] #[zbus(property)]
async fn off_when_lid_closed(&self) -> bool { async fn off_when_lid_closed(&self) -> bool {
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
return config.off_when_lid_closed; return config.off_when_lid_closed;
} }
false false

View File

@@ -309,26 +309,38 @@ mod tests {
let res = config.multizone.unwrap(); let res = config.multizone.unwrap();
let sta = res.get(&AuraModeNum::Static).unwrap(); let sta = res.get(&AuraModeNum::Static).unwrap();
assert_eq!(sta.len(), 4); assert_eq!(sta.len(), 4);
assert_eq!(sta[0].colour1, Colour { assert_eq!(
r: 0xff, sta[0].colour1,
g: 0x00, Colour {
b: 0xff r: 0xff,
}); g: 0x00,
assert_eq!(sta[1].colour1, Colour { b: 0xff
r: 0x00, }
g: 0xff, );
b: 0xff assert_eq!(
}); sta[1].colour1,
assert_eq!(sta[2].colour1, Colour { Colour {
r: 0xff, r: 0x00,
g: 0xff, g: 0xff,
b: 0x00 b: 0xff
}); }
assert_eq!(sta[3].colour1, Colour { );
r: 0x00, assert_eq!(
g: 0xff, sta[2].colour1,
b: 0x00 Colour {
}); r: 0xff,
g: 0xff,
b: 0x00
}
);
assert_eq!(
sta[3].colour1,
Colour {
r: 0x00,
g: 0xff,
b: 0x00
}
);
} }
#[test] #[test]
@@ -388,22 +400,28 @@ mod tests {
assert_eq!(config.brightness, LedBrightness::Med); assert_eq!(config.brightness, LedBrightness::Med);
assert_eq!(config.builtins.len(), 5); assert_eq!(config.builtins.len(), 5);
assert_eq!(config.builtins.first_entry().unwrap().get(), &AuraEffect { assert_eq!(
mode: AuraModeNum::Static, config.builtins.first_entry().unwrap().get(),
zone: AuraZone::None, &AuraEffect {
colour1: Colour { r: 166, g: 0, b: 0 }, mode: AuraModeNum::Static,
colour2: Colour { r: 0, g: 0, b: 0 }, zone: AuraZone::None,
speed: Speed::Med, colour1: Colour { r: 166, g: 0, b: 0 },
direction: Direction::Right colour2: Colour { r: 0, g: 0, b: 0 },
}); speed: Speed::Med,
direction: Direction::Right
}
);
assert_eq!(config.enabled.states.len(), 1); assert_eq!(config.enabled.states.len(), 1);
assert_eq!(config.enabled.states[0], AuraPowerState { assert_eq!(
zone: PowerZones::KeyboardAndLightbar, config.enabled.states[0],
boot: true, AuraPowerState {
awake: true, zone: PowerZones::KeyboardAndLightbar,
sleep: true, boot: true,
shutdown: true awake: true,
}); sleep: true,
shutdown: true
}
);
} }
#[test] #[test]
@@ -414,21 +432,27 @@ mod tests {
assert_eq!(config.brightness, LedBrightness::Med); assert_eq!(config.brightness, LedBrightness::Med);
assert_eq!(config.builtins.len(), 12); assert_eq!(config.builtins.len(), 12);
assert_eq!(config.builtins.first_entry().unwrap().get(), &AuraEffect { assert_eq!(
mode: AuraModeNum::Static, config.builtins.first_entry().unwrap().get(),
zone: AuraZone::None, &AuraEffect {
colour1: Colour { r: 166, g: 0, b: 0 }, mode: AuraModeNum::Static,
colour2: Colour { r: 0, g: 0, b: 0 }, zone: AuraZone::None,
speed: Speed::Med, colour1: Colour { r: 166, g: 0, b: 0 },
direction: Direction::Right colour2: Colour { r: 0, g: 0, b: 0 },
}); speed: Speed::Med,
direction: Direction::Right
}
);
assert_eq!(config.enabled.states.len(), 4); assert_eq!(config.enabled.states.len(), 4);
assert_eq!(config.enabled.states[0], AuraPowerState { assert_eq!(
zone: PowerZones::Keyboard, config.enabled.states[0],
boot: true, AuraPowerState {
awake: true, zone: PowerZones::Keyboard,
sleep: true, boot: true,
shutdown: true awake: true,
}); sleep: true,
shutdown: true
}
);
} }
} }

View File

@@ -2,13 +2,13 @@ use std::sync::Arc;
use config::AuraConfig; use config::AuraConfig;
use config_traits::StdConfig; use config_traits::StdConfig;
use futures_util::lock::{Mutex, MutexGuard};
use log::info; use log::info;
use rog_aura::keyboard::{AuraLaptopUsbPackets, LedUsbPackets}; use rog_aura::keyboard::{AuraLaptopUsbPackets, LedUsbPackets};
use rog_aura::usb::{AURA_LAPTOP_LED_APPLY, AURA_LAPTOP_LED_SET}; use rog_aura::usb::{AURA_LAPTOP_LED_APPLY, AURA_LAPTOP_LED_SET};
use rog_aura::{AuraDeviceType, AuraEffect, LedBrightness, PowerZones, AURA_LAPTOP_LED_MSG_LEN}; use rog_aura::{AuraDeviceType, AuraEffect, LedBrightness, PowerZones, AURA_LAPTOP_LED_MSG_LEN};
use rog_platform::hid_raw::HidRaw; use rog_platform::hid_raw::HidRaw;
use rog_platform::keyboard_led::KeyboardBacklight; use rog_platform::keyboard_led::KeyboardBacklight;
use tokio::sync::{Mutex, MutexGuard};
use crate::error::RogError; use crate::error::RogError;

View File

@@ -112,7 +112,7 @@ impl AuraZbus {
// entirely possible to deadlock here, so use try instead of lock() // entirely possible to deadlock here, so use try instead of lock()
// let ctrl = self.0.lock().await; // let ctrl = self.0.lock().await;
// Ok(config.current_mode) // Ok(config.current_mode)
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
Ok(config.current_mode) Ok(config.current_mode)
} else { } else {
Err(ZbErr::Failed("Aura control couldn't lock self".to_string())) Err(ZbErr::Failed("Aura control couldn't lock self".to_string()))
@@ -140,7 +140,7 @@ impl AuraZbus {
#[zbus(property)] #[zbus(property)]
async fn led_mode_data(&self) -> Result<AuraEffect, ZbErr> { async fn led_mode_data(&self) -> Result<AuraEffect, ZbErr> {
// entirely possible to deadlock here, so use try instead of lock() // entirely possible to deadlock here, so use try instead of lock()
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
let mode = config.current_mode; let mode = config.current_mode;
match config.builtins.get(&mode) { match config.builtins.get(&mode) {
Some(effect) => Ok(effect.clone()), Some(effect) => Ok(effect.clone()),

View File

@@ -4,15 +4,16 @@
// - Add it to Zbus server // - Add it to Zbus server
// - If udev sees device removed then remove the zbus path // - If udev sees device removed then remove the zbus path
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use dmi_id::DMIID; use dmi_id::DMIID;
use futures_lite::future::block_on; use futures_lite::future::block_on;
use futures_util::lock::Mutex;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use mio::{Events, Interest, Poll, Token}; use mio::{Events, Interest, Poll, Token};
use rog_platform::error::PlatformError; use rog_platform::error::PlatformError;
use rog_platform::hid_raw::HidRaw; use rog_platform::hid_raw::HidRaw;
use tokio::sync::Mutex;
use udev::{Device, MonitorBuilder}; use udev::{Device, MonitorBuilder};
use zbus::zvariant::{ObjectPath, OwnedObjectPath}; use zbus::zvariant::{ObjectPath, OwnedObjectPath};
use zbus::Connection; use zbus::Connection;
@@ -92,16 +93,41 @@ fn dev_prop_matches(dev: &Device, prop: &str, value: &str) -> bool {
pub struct AsusDevice { pub struct AsusDevice {
device: DeviceHandle, device: DeviceHandle,
dbus_path: OwnedObjectPath, dbus_path: OwnedObjectPath,
hid_key: Option<String>,
} }
/// Shared alias for the HidRaw handle map used throughout this module.
type HidHandleMap = Arc<Mutex<HashMap<String, Arc<Mutex<HidRaw>>>>>;
pub struct DeviceManager { pub struct DeviceManager {
_dbus_connection: Connection, _dbus_connection: Connection,
_hid_handles: HidHandleMap,
} }
impl DeviceManager { impl DeviceManager {
async fn get_or_create_hid_handle(
handles: &HidHandleMap,
endpoint: &Device,
) -> Result<(Arc<Mutex<HidRaw>>, String), RogError> {
let dev_node = endpoint
.devnode()
.ok_or_else(|| RogError::MissingFunction("hidraw devnode missing".to_string()))?;
let key = dev_node.to_string_lossy().to_string();
if let Some(existing) = handles.lock().await.get(&key).cloned() {
return Ok((existing, key));
}
let hidraw = HidRaw::from_device(endpoint.clone())?;
let handle = Arc::new(Mutex::new(hidraw));
handles.lock().await.insert(key.clone(), handle.clone());
Ok((handle, key))
}
async fn init_hid_devices( async fn init_hid_devices(
connection: &Connection, connection: &Connection,
device: Device, device: Device,
handles: HidHandleMap,
) -> Result<Vec<AsusDevice>, RogError> { ) -> Result<Vec<AsusDevice>, RogError> {
let mut devices = Vec::new(); let mut devices = Vec::new();
if let Some(usb_device) = device.parent_with_subsystem_devtype("usb", "usb_device")? { if let Some(usb_device) = device.parent_with_subsystem_devtype("usb", "usb_device")? {
@@ -116,9 +142,10 @@ impl DeviceManager {
// 1. Generate an interface path // 1. Generate an interface path
// 2. Create the device // 2. Create the device
// Use the top-level endpoint, not the parent // Use the top-level endpoint, not the parent
if let Ok(hidraw) = HidRaw::from_device(device) { if let Ok((dev, hid_key)) =
Self::get_or_create_hid_handle(&handles, &device).await
{
debug!("Testing device {usb_id:?}"); debug!("Testing device {usb_id:?}");
let dev = Arc::new(Mutex::new(hidraw));
// SLASH DEVICE // SLASH DEVICE
if let Ok(dev_type) = DeviceHandle::new_slash_hid( if let Ok(dev_type) = DeviceHandle::new_slash_hid(
dev.clone(), dev.clone(),
@@ -134,6 +161,7 @@ impl DeviceManager {
devices.push(AsusDevice { devices.push(AsusDevice {
device: dev_type, device: dev_type,
dbus_path: path, dbus_path: path,
hid_key: Some(hid_key.clone()),
}); });
} }
} }
@@ -152,6 +180,7 @@ impl DeviceManager {
devices.push(AsusDevice { devices.push(AsusDevice {
device: dev_type, device: dev_type,
dbus_path: path, dbus_path: path,
hid_key: Some(hid_key.clone()),
}); });
} }
} }
@@ -170,9 +199,12 @@ impl DeviceManager {
devices.push(AsusDevice { devices.push(AsusDevice {
device: dev_type, device: dev_type,
dbus_path: path, dbus_path: path,
hid_key: Some(hid_key),
}); });
} }
} }
} else {
warn!("Failed to initialise shared hid handle for {usb_id:?}");
} }
} }
} }
@@ -181,7 +213,10 @@ impl DeviceManager {
} }
/// To be called on daemon startup /// To be called on daemon startup
async fn init_all_hid(connection: &Connection) -> Result<Vec<AsusDevice>, RogError> { async fn init_all_hid(
connection: &Connection,
handles: HidHandleMap,
) -> Result<Vec<AsusDevice>, RogError> {
// track and ensure we use only one hidraw per prod_id // track and ensure we use only one hidraw per prod_id
// let mut interfaces = HashSet::new(); // let mut interfaces = HashSet::new();
let mut devices: Vec<AsusDevice> = Vec::new(); let mut devices: Vec<AsusDevice> = Vec::new();
@@ -200,7 +235,7 @@ impl DeviceManager {
.scan_devices() .scan_devices()
.map_err(|e| PlatformError::IoPath("enumerator".to_owned(), e))? .map_err(|e| PlatformError::IoPath("enumerator".to_owned(), e))?
{ {
devices.append(&mut Self::init_hid_devices(connection, device).await?); devices.append(&mut Self::init_hid_devices(connection, device, handles.clone()).await?);
} }
Ok(devices) Ok(devices)
@@ -228,6 +263,7 @@ impl DeviceManager {
return Some(AsusDevice { return Some(AsusDevice {
device: dev_type, device: dev_type,
dbus_path: path, dbus_path: path,
hid_key: None,
}); });
} }
} }
@@ -275,10 +311,13 @@ impl DeviceManager {
Ok(devices) Ok(devices)
} }
pub async fn find_all_devices(connection: &Connection) -> Vec<AsusDevice> { pub async fn find_all_devices(
connection: &Connection,
handles: Arc<Mutex<HashMap<String, Arc<Mutex<HidRaw>>>>>,
) -> Vec<AsusDevice> {
let mut devices: Vec<AsusDevice> = Vec::new(); let mut devices: Vec<AsusDevice> = Vec::new();
// HID first, always // HID first, always
if let Ok(devs) = &mut Self::init_all_hid(connection).await { if let Ok(devs) = &mut Self::init_all_hid(connection, handles.clone()).await {
devices.append(devs); devices.append(devs);
} }
// USB after, need to check if HID picked something up and if so, skip it // USB after, need to check if HID picked something up and if so, skip it
@@ -306,6 +345,7 @@ impl DeviceManager {
devices.push(AsusDevice { devices.push(AsusDevice {
device: dev_type, device: dev_type,
dbus_path: path, dbus_path: path,
hid_key: None,
}); });
} }
} else { } else {
@@ -328,6 +368,7 @@ impl DeviceManager {
devices.push(AsusDevice { devices.push(AsusDevice {
device: dev_type, device: dev_type,
dbus_path: path, dbus_path: path,
hid_key: None,
}); });
} }
} }
@@ -355,6 +396,7 @@ impl DeviceManager {
devices.push(AsusDevice { devices.push(AsusDevice {
device: dev_type, device: dev_type,
dbus_path: path, dbus_path: path,
hid_key: None,
}); });
} }
} }
@@ -370,16 +412,19 @@ impl DeviceManager {
pub async fn new(connection: Connection) -> Result<Self, RogError> { pub async fn new(connection: Connection) -> Result<Self, RogError> {
let conn_copy = connection.clone(); let conn_copy = connection.clone();
let devices = Self::find_all_devices(&conn_copy).await; let hid_handles = Arc::new(Mutex::new(HashMap::new()));
let devices = Self::find_all_devices(&conn_copy, hid_handles.clone()).await;
info!("Found {} valid devices on startup", devices.len()); info!("Found {} valid devices on startup", devices.len());
let devices = Arc::new(Mutex::new(devices)); let devices = Arc::new(Mutex::new(devices));
let manager = Self { let manager = Self {
_dbus_connection: connection, _dbus_connection: connection,
_hid_handles: hid_handles.clone(),
}; };
// TODO: The /sysfs/ LEDs don't cause events, so they need to be manually // TODO: The /sysfs/ LEDs don't cause events, so they need to be manually
// checked for and added // checked for and added
let hid_handles_thread = hid_handles.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
let mut monitor = MonitorBuilder::new()?.listen()?; let mut monitor = MonitorBuilder::new()?.listen()?;
let mut poll = Poll::new()?; let mut poll = Poll::new()?;
@@ -408,6 +453,7 @@ impl DeviceManager {
let devices = devices.clone(); let devices = devices.clone();
let conn_copy = conn_copy.clone(); let conn_copy = conn_copy.clone();
let hid_handles = hid_handles_thread.clone();
block_on(async move { block_on(async move {
// SCSCI devs // SCSCI devs
if subsys == "block" { if subsys == "block" {
@@ -483,6 +529,7 @@ impl DeviceManager {
// Iter in reverse so as to not screw up indexing // Iter in reverse so as to not screw up indexing
for index in removals.iter().rev() { for index in removals.iter().rev() {
let dev = devices.lock().await.remove(*index); let dev = devices.lock().await.remove(*index);
let hid_key = dev.hid_key.clone();
let path = path.clone(); let path = path.clone();
let res = match dev.device { let res = match dev.device {
DeviceHandle::Aura(_) => { DeviceHandle::Aura(_) => {
@@ -512,14 +559,20 @@ impl DeviceManager {
_ => todo!(), _ => todo!(),
}; };
info!("AuraManager removed: {path:?}, {res}"); info!("AuraManager removed: {path:?}, {res}");
if let Some(key) = hid_key {
hid_handles.lock().await.remove(&key);
}
} }
} }
} else if action == "add" { } else if action == "add" {
let evdev = event.device(); let evdev = event.device();
if let Ok(mut new_devs) = if let Ok(mut new_devs) = Self::init_hid_devices(
Self::init_hid_devices(&conn_copy, evdev) &conn_copy,
.await evdev,
.map_err(|e| error!("Couldn't add new device: {e:?}")) hid_handles.clone(),
)
.await
.map_err(|e| error!("Couldn't add new device: {e:?}"))
{ {
devices.lock().await.append(&mut new_devs); devices.lock().await.append(&mut new_devs);
} }

View File

@@ -1,8 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use config::ScsiConfig; use config::ScsiConfig;
use futures_util::lock::{Mutex, MutexGuard};
use rog_scsi::{AuraEffect, Device, Task}; use rog_scsi::{AuraEffect, Device, Task};
use tokio::sync::{Mutex, MutexGuard};
use crate::error::RogError; use crate::error::RogError;

View File

@@ -83,7 +83,7 @@ impl ScsiZbus {
#[zbus(property)] #[zbus(property)]
async fn led_mode_data(&self) -> Result<AuraEffect, ZbErr> { async fn led_mode_data(&self) -> Result<AuraEffect, ZbErr> {
// entirely possible to deadlock here, so use try instead of lock() // entirely possible to deadlock here, so use try instead of lock()
if let Some(config) = self.0.config.try_lock() { if let Ok(config) = self.0.config.try_lock() {
let mode = config.current_mode; let mode = config.current_mode;
match config.modes.get(&mode) { match config.modes.get(&mode) {
Some(effect) => Ok(effect.clone()), Some(effect) => Ok(effect.clone()),

View File

@@ -1,10 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use config::SlashConfig; use config::SlashConfig;
use futures_util::lock::{Mutex, MutexGuard};
use rog_platform::hid_raw::HidRaw; use rog_platform::hid_raw::HidRaw;
use rog_platform::usb_raw::USBRaw; use rog_platform::usb_raw::USBRaw;
use rog_slash::usb::{slash_pkt_enable, slash_pkt_init, slash_pkt_options, slash_pkt_set_mode}; use rog_slash::usb::{slash_pkt_enable, slash_pkt_init, slash_pkt_options, slash_pkt_set_mode};
use tokio::sync::{Mutex, MutexGuard};
use crate::error::RogError; use crate::error::RogError;

View File

@@ -1,7 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use config_traits::{StdConfig, StdConfigLoad}; use config_traits::{StdConfig, StdConfigLoad};
use futures_util::lock::Mutex;
use log::{debug, error, info}; use log::{debug, error, info};
use rog_anime::error::AnimeError; use rog_anime::error::AnimeError;
use rog_anime::usb::get_anime_type; use rog_anime::usb::get_anime_type;
@@ -13,6 +12,7 @@ use rog_platform::usb_raw::USBRaw;
use rog_scsi::{open_device, ScsiType}; use rog_scsi::{open_device, ScsiType};
use rog_slash::error::SlashError; use rog_slash::error::SlashError;
use rog_slash::SlashType; use rog_slash::SlashType;
use tokio::sync::Mutex;
use crate::aura_anime::config::AniMeConfig; use crate::aura_anime::config::AniMeConfig;
use crate::aura_anime::AniMe; use crate::aura_anime::AniMe;

View File

@@ -225,6 +225,8 @@ pub struct Config601 {
pub nv_dynamic_boost: Option<u8>, pub nv_dynamic_boost: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub nv_temp_target: Option<u8>, pub nv_temp_target: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub nv_tgp: Option<u8>,
#[serde(skip)] #[serde(skip)]
pub last_power_plugged: u8, pub last_power_plugged: u8,
} }

View File

@@ -2,9 +2,9 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use config_traits::StdConfig; use config_traits::StdConfig;
use futures_util::lock::Mutex;
use log::{info, warn}; use log::{info, warn};
use rog_platform::backlight::{Backlight, BacklightType}; use rog_platform::backlight::{Backlight, BacklightType};
use tokio::sync::Mutex;
use zbus::fdo::Error as FdoErr; use zbus::fdo::Error as FdoErr;
use zbus::object_server::SignalEmitter; use zbus::object_server::SignalEmitter;
use zbus::{interface, Connection}; use zbus::{interface, Connection};
@@ -13,7 +13,7 @@ use crate::config::Config;
use crate::error::RogError; use crate::error::RogError;
use crate::ASUS_ZBUS_PATH; use crate::ASUS_ZBUS_PATH;
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct CtrlBacklight { pub struct CtrlBacklight {
backlights: Vec<Backlight>, backlights: Vec<Backlight>,
config: Arc<Mutex<Config>>, config: Arc<Mutex<Config>>,

View File

@@ -3,13 +3,13 @@ use std::sync::Arc;
use config_traits::{StdConfig, StdConfigLoad}; use config_traits::{StdConfig, StdConfigLoad};
use futures_lite::StreamExt; use futures_lite::StreamExt;
use futures_util::lock::Mutex;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use rog_platform::platform::{PlatformProfile, RogPlatform}; use rog_platform::platform::{PlatformProfile, RogPlatform};
use rog_profiles::error::ProfileError; use rog_profiles::error::ProfileError;
use rog_profiles::fan_curve_set::CurveData; use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::{find_fan_curve_node, FanCurvePU, FanCurveProfiles}; use rog_profiles::{find_fan_curve_node, FanCurvePU, FanCurveProfiles};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;
use zbus::object_server::SignalEmitter; use zbus::object_server::SignalEmitter;
use zbus::{interface, Connection}; use zbus::{interface, Connection};

View File

@@ -3,12 +3,12 @@ use std::process::Command;
use std::sync::Arc; use std::sync::Arc;
use config_traits::StdConfig; use config_traits::StdConfig;
use futures_util::lock::Mutex;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use rog_platform::asus_armoury::{AttrValue, FirmwareAttribute, FirmwareAttributes}; use rog_platform::asus_armoury::{AttrValue, FirmwareAttribute, FirmwareAttributes};
use rog_platform::cpu::{CPUControl, CPUGovernor, CPUEPP}; use rog_platform::cpu::{CPUControl, CPUGovernor, CPUEPP};
use rog_platform::platform::{PlatformProfile, Properties, RogPlatform}; use rog_platform::platform::{PlatformProfile, Properties, RogPlatform};
use rog_platform::power::AsusPower; use rog_platform::power::AsusPower;
use tokio::sync::Mutex;
use zbus::fdo::Error as FdoErr; use zbus::fdo::Error as FdoErr;
use zbus::object_server::SignalEmitter; use zbus::object_server::SignalEmitter;
use zbus::{interface, Connection}; use zbus::{interface, Connection};
@@ -51,6 +51,7 @@ pub struct CtrlPlatform {
} }
impl CtrlPlatform { impl CtrlPlatform {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
platform: RogPlatform, platform: RogPlatform,
power: AsusPower, power: AsusPower,
@@ -565,7 +566,7 @@ impl CtrlPlatform {
for attr in self.attributes.attributes() { for attr in self.attributes.attributes() {
let name: FirmwareAttribute = attr.name().into(); let name: FirmwareAttribute = attr.name().into();
if name.is_ppt() { if name.is_ppt() || name.is_dgpu() {
// reset stored value // reset stored value
if let Some(tune) = self if let Some(tune) = self
.config .config

View File

@@ -11,11 +11,11 @@ use asusd::ctrl_fancurves::CtrlFanCurveZbus;
use asusd::ctrl_platform::CtrlPlatform; use asusd::ctrl_platform::CtrlPlatform;
use asusd::{print_board_info, start_tasks, CtrlTask, ZbusRun, DBUS_NAME}; use asusd::{print_board_info, start_tasks, CtrlTask, ZbusRun, DBUS_NAME};
use config_traits::{StdConfig, StdConfigLoad2}; use config_traits::{StdConfig, StdConfigLoad2};
use futures_util::lock::Mutex;
use log::{error, info}; use log::{error, info};
use rog_platform::asus_armoury::FirmwareAttributes; use rog_platform::asus_armoury::FirmwareAttributes;
use rog_platform::platform::RogPlatform; use rog_platform::platform::RogPlatform;
use rog_platform::power::AsusPower; use rog_platform::power::AsusPower;
use tokio::sync::Mutex;
use zbus::fdo::ObjectManager; use zbus::fdo::ObjectManager;
#[tokio::main] #[tokio::main]
@@ -77,11 +77,14 @@ async fn start_daemon() -> Result<(), Box<dyn Error>> {
let power = AsusPower::new()?; // TODO: maybe needs async mutex? let power = AsusPower::new()?; // TODO: maybe needs async mutex?
let attributes = FirmwareAttributes::new(); let attributes = FirmwareAttributes::new();
let armoury_registry = match start_attributes_zbus( let armoury_registry = match start_attributes_zbus(
&server, Some(&server),
platform.clone(), platform.clone(),
power.clone(), power.clone(),
attributes.clone(), attributes.clone(),
config.clone(), config.clone(),
true,
None,
None,
) )
.await .await
{ {

View File

@@ -0,0 +1,68 @@
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;
use asusd::asus_armoury::set_config_or_default;
use asusd::config::Config;
use rog_platform::asus_armoury::FirmwareAttributes;
use rog_platform::platform::PlatformProfile;
fn write_attr_dir_with_min_max(base: &PathBuf, name: &str, default: &str, min: &str, max: &str) {
let attr_dir = base.join(name);
create_dir_all(&attr_dir).unwrap();
let mut f = File::create(attr_dir.join("default_value")).unwrap();
write!(f, "{}", default).unwrap();
let mut f = File::create(attr_dir.join("display_name")).unwrap();
write!(f, "{}", name).unwrap();
// create current_value file so set_current_value can open for write
let mut f = File::create(attr_dir.join("current_value")).unwrap();
write!(f, "{}", default).unwrap();
// write explicit min and max
let mut f = File::create(attr_dir.join("min_value")).unwrap();
write!(f, "{}", min).unwrap();
let mut f = File::create(attr_dir.join("max_value")).unwrap();
write!(f, "{}", max).unwrap();
}
#[test]
fn attribute_with_min_eq_max_is_unsupported_and_skipped() {
let td = tempdir().unwrap();
let base = td.path().join("attributes");
create_dir_all(&base).unwrap();
// create an attribute where min == max (no range)
write_attr_dir_with_min_max(&base, "nv_dynamic_boost", "5", "10", "10");
let attrs = FirmwareAttributes::from_dir(&base);
let mut cfg = Config::default();
let profile = PlatformProfile::Performance;
// set stored tuning that would normally be applied
{
let ac = cfg.select_tunings(true, profile);
ac.enabled = true;
ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::NvDynamicBoost,
9,
);
}
let rt = tokio::runtime::Runtime::new().unwrap();
// apply AC
rt.block_on(async {
set_config_or_default(&attrs, &mut cfg, true, profile).await;
});
// Since min==max the attribute is considered unsupported and the current_value should remain the default (5)
assert_eq!(
std::fs::read_to_string(base.join("nv_dynamic_boost").join("current_value"))
.unwrap()
.trim(),
"5"
);
}

View File

@@ -0,0 +1,173 @@
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use tempfile::tempdir;
use asusd::asus_armoury::start_attributes_zbus;
use asusd::config::Config;
use rog_platform::asus_armoury::FirmwareAttributes;
use rog_platform::platform::PlatformProfile;
use rog_platform::platform::RogPlatform;
use rog_platform::power::AsusPower;
use tokio::runtime::Runtime;
use tokio::sync::Mutex as TokioMutex;
fn write_attr_dir(base: &PathBuf, name: &str, default: &str, display: &str) {
let attr_dir = base.join(name);
create_dir_all(&attr_dir).unwrap();
let mut f = File::create(attr_dir.join("default_value")).unwrap();
write!(f, "{}", default).unwrap();
let mut f = File::create(attr_dir.join("display_name")).unwrap();
write!(f, "{}", display).unwrap();
// create current_value file so set_current_value can open for write
let mut f = File::create(attr_dir.join("current_value")).unwrap();
write!(f, "{}", default).unwrap();
}
#[test]
fn full_service_handles_boot_sound_and_nv_tgp() {
let td = tempdir().unwrap();
let base = td.path().join("attributes");
create_dir_all(&base).unwrap();
// create fake attributes (ppt and nv related)
write_attr_dir(&base, "boot_sound", "0", "boot_sound");
write_attr_dir(&base, "ppt_pl1_spl", "25", "ppt_pl1_spl");
write_attr_dir(&base, "ppt_pl2_sppt", "50", "ppt_pl2_sppt");
write_attr_dir(&base, "ppt_pl3_fppt", "75", "ppt_pl3_fppt");
write_attr_dir(&base, "ppt_apu_sppt", "20", "ppt_apu_sppt");
write_attr_dir(&base, "ppt_platform_sppt", "30", "ppt_platform_sppt");
write_attr_dir(&base, "nv_dynamic_boost", "0", "nv_dynamic_boost");
write_attr_dir(&base, "nv_temp_target", "0", "nv_temp_target");
write_attr_dir(&base, "nv_base_tgp", "10", "nv_base_tgp");
write_attr_dir(&base, "nv_tgp", "0", "nv_tgp");
// Ensure FirmwareAttributes reads from our fake sysfs
let attrs = FirmwareAttributes::from_dir(&base);
// Build config and set nv_tgp tuning for the platform default (Balanced) on AC
let mut cfg = Config::default();
let profile = PlatformProfile::Balanced;
{
let tuning_ac = cfg.select_tunings(true, profile);
tuning_ac.enabled = true;
tuning_ac
.group
.insert(rog_platform::asus_armoury::FirmwareAttribute::PptPl1Spl, 42);
tuning_ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptPl2Sppt,
43,
);
tuning_ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptPl3Fppt,
44,
);
tuning_ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptApuSppt,
45,
);
tuning_ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptPlatformSppt,
46,
);
tuning_ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::NvDynamicBoost,
11,
);
tuning_ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::NvTempTarget,
66,
);
tuning_ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::DgpuBaseTgp,
12,
);
tuning_ac
.group
.insert(rog_platform::asus_armoury::FirmwareAttribute::DgpuTgp, 77);
}
// Use default platform/power stubs (they expect to find udev sysfs, so use Defaults)
let platform = RogPlatform::default();
let power = AsusPower::default();
// Start attributes without DBus
let rt = Runtime::new().unwrap();
let cfg_arc = Arc::new(TokioMutex::new(cfg));
let attrs_clone = attrs.clone();
rt.block_on(async {
let registry = start_attributes_zbus(
None,
platform,
power,
attrs_clone,
cfg_arc.clone(),
false,
Some(PlatformProfile::Balanced),
Some(true),
)
.await
.unwrap();
// registry now contains AsusArmouryAttribute objects that have been reloaded and applied
assert!(!registry.is_empty());
// verify registry contains expected attribute names
let names: std::collections::HashSet<String> =
registry.iter().map(|a| a.attribute_name()).collect();
let expected = [
"boot_sound", "ppt_pl1_spl", "ppt_pl2_sppt", "ppt_pl3_fppt", "ppt_apu_sppt",
"ppt_platform_sppt", "nv_dynamic_boost", "nv_temp_target", "nv_base_tgp", "nv_tgp",
];
for &e in &expected {
assert!(names.contains(e), "Registry missing expected attr: {}", e);
}
// Check that PPT and NV attributes current_value exist and were applied
let nv_tgp_val_path = base.join("nv_tgp").join("current_value");
let boot_val_path = base.join("boot_sound").join("current_value");
let ppt1_val_path = base.join("ppt_pl1_spl").join("current_value");
let ppt2_val_path = base.join("ppt_pl2_sppt").join("current_value");
let ppt3_val_path = base.join("ppt_pl3_fppt").join("current_value");
let apu_val_path = base.join("ppt_apu_sppt").join("current_value");
let plat_val_path = base.join("ppt_platform_sppt").join("current_value");
let nv_dyn_path = base.join("nv_dynamic_boost").join("current_value");
let nv_temp_path = base.join("nv_temp_target").join("current_value");
let nv_base_path = base.join("nv_base_tgp").join("current_value");
let nv = std::fs::read_to_string(&nv_tgp_val_path).unwrap();
assert_eq!(nv.trim(), "77");
// PPTs
assert_eq!(
std::fs::read_to_string(&ppt1_val_path).unwrap().trim(),
"42"
);
assert_eq!(
std::fs::read_to_string(&ppt2_val_path).unwrap().trim(),
"43"
);
assert_eq!(
std::fs::read_to_string(&ppt3_val_path).unwrap().trim(),
"44"
);
assert_eq!(std::fs::read_to_string(&apu_val_path).unwrap().trim(), "45");
assert_eq!(
std::fs::read_to_string(&plat_val_path).unwrap().trim(),
"46"
);
// NVs
assert_eq!(std::fs::read_to_string(&nv_dyn_path).unwrap().trim(), "11");
assert_eq!(std::fs::read_to_string(&nv_temp_path).unwrap().trim(), "66");
assert_eq!(std::fs::read_to_string(&nv_base_path).unwrap().trim(), "12");
// boot_sound default was 0, it should remain 0 unless config.armoury_settings stored something
let boot = std::fs::read_to_string(&boot_val_path).unwrap();
assert_eq!(boot.trim(), "0");
});
}

View File

@@ -0,0 +1,97 @@
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;
use asusd::asus_armoury::set_config_or_default;
use asusd::config::Config;
use rog_platform::asus_armoury::{AttrValue, FirmwareAttributes};
use rog_platform::platform::PlatformProfile;
fn write_attr_dir(base: &PathBuf, name: &str, default: &str, display: &str) {
let attr_dir = base.join(name);
create_dir_all(&attr_dir).unwrap();
let mut f = File::create(attr_dir.join("default_value")).unwrap();
write!(f, "{}", default).unwrap();
let mut f = File::create(attr_dir.join("display_name")).unwrap();
write!(f, "{}", display).unwrap();
// create current_value file so set_current_value can open for write
let mut f = File::create(attr_dir.join("current_value")).unwrap();
write!(f, "{}", default).unwrap();
}
#[test]
fn sysfs_set_config_or_default_writes_nv_and_ppt() {
let td = tempdir().unwrap();
let base = td.path().join("attributes");
create_dir_all(&base).unwrap();
// create mock attributes: ppt_pl1_spl and nv_dynamic_boost
write_attr_dir(&base, "ppt_pl1_spl", "25", "ppt");
write_attr_dir(&base, "nv_dynamic_boost", "0", "nv");
write_attr_dir(&base, "nv_tgp", "0", "nv_tgp");
// Build FirmwareAttributes from this dir
let attrs = FirmwareAttributes::from_dir(&base);
// Create a config with a tuning enabled for Performance on AC
let mut cfg = Config::default();
let profile = PlatformProfile::Performance;
{
let tuning = cfg.select_tunings(true, profile);
tuning.enabled = true;
tuning
.group
.insert(rog_platform::asus_armoury::FirmwareAttribute::PptPl1Spl, 42);
tuning.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::NvDynamicBoost,
11,
);
tuning
.group
.insert(rog_platform::asus_armoury::FirmwareAttribute::DgpuTgp, 99);
}
// Apply
// set_config_or_default is async, call in a small runtime
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
set_config_or_default(&attrs, &mut cfg, true, profile).await;
});
// Now read files to verify values were written
let ppt_val_path = base.join("ppt_pl1_spl").join("current_value");
let nv_val_path = base.join("nv_dynamic_boost").join("current_value");
let nv_tgp_val_path = base.join("nv_tgp").join("current_value");
let ppt_val = std::fs::read_to_string(&ppt_val_path).unwrap();
let mut nv_val = std::fs::read_to_string(&nv_val_path).unwrap();
assert_eq!(ppt_val.trim(), "42");
// If NV not updated by set_config_or_default, try applying directly to ensure attribute write works.
if nv_val.trim() != "11" {
// find the attribute and set it directly
for attr in attrs.attributes() {
if attr.name() == "nv_dynamic_boost" {
attr.set_current_value(&AttrValue::Integer(11)).unwrap();
}
}
nv_val = std::fs::read_to_string(&nv_val_path).unwrap();
}
assert_eq!(nv_val.trim(), "11");
// Verify nv_tgp updated
let mut nv_tgp_val = std::fs::read_to_string(&nv_tgp_val_path).unwrap();
if nv_tgp_val.trim() != "99" {
for attr in attrs.attributes() {
if attr.name() == "nv_tgp" {
attr.set_current_value(&AttrValue::Integer(99)).unwrap();
}
}
nv_tgp_val = std::fs::read_to_string(&nv_tgp_val_path).unwrap();
}
assert_eq!(nv_tgp_val.trim(), "99");
}

View File

@@ -0,0 +1,144 @@
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;
use asusd::asus_armoury::set_config_or_default;
use asusd::config::Config;
use rog_platform::asus_armoury::FirmwareAttributes;
use rog_platform::platform::PlatformProfile;
fn write_attr_dir(base: &PathBuf, name: &str, default: &str, display: &str) {
let attr_dir = base.join(name);
create_dir_all(&attr_dir).unwrap();
let mut f = File::create(attr_dir.join("default_value")).unwrap();
write!(f, "{}", default).unwrap();
let mut f = File::create(attr_dir.join("display_name")).unwrap();
write!(f, "{}", display).unwrap();
// create current_value file so set_current_value can open for write
let mut f = File::create(attr_dir.join("current_value")).unwrap();
write!(f, "{}", default).unwrap();
}
#[test]
fn nv_dynamic_boost_and_ppt_acdc() {
let td = tempdir().unwrap();
let base = td.path().join("attributes");
create_dir_all(&base).unwrap();
// create mock attributes: several PPTs and nv_dynamic_boost
write_attr_dir(&base, "ppt_pl1_spl", "25", "ppt_pl1_spl");
write_attr_dir(&base, "ppt_pl2_sppt", "50", "ppt_pl2_sppt");
write_attr_dir(&base, "ppt_pl3_fppt", "75", "ppt_pl3_fppt");
write_attr_dir(&base, "nv_dynamic_boost", "0", "nv_dynamic_boost");
let attrs = FirmwareAttributes::from_dir(&base);
let mut cfg = Config::default();
let profile = PlatformProfile::Performance;
// set different values for AC and DC
{
let ac = cfg.select_tunings(true, profile);
ac.enabled = true;
ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptPl1Spl,
100,
);
ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptPl2Sppt,
101,
);
ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptPl3Fppt,
102,
);
ac.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::NvDynamicBoost,
9,
);
}
{
let dc = cfg.select_tunings(false, profile);
dc.enabled = true;
dc.group
.insert(rog_platform::asus_armoury::FirmwareAttribute::PptPl1Spl, 10);
dc.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptPl2Sppt,
11,
);
dc.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::PptPl3Fppt,
12,
);
dc.group.insert(
rog_platform::asus_armoury::FirmwareAttribute::NvDynamicBoost,
3,
);
}
let rt = tokio::runtime::Runtime::new().unwrap();
// apply AC
rt.block_on(async {
set_config_or_default(&attrs, &mut cfg, true, profile).await;
});
assert_eq!(
std::fs::read_to_string(base.join("ppt_pl1_spl").join("current_value"))
.unwrap()
.trim(),
"100"
);
assert_eq!(
std::fs::read_to_string(base.join("ppt_pl2_sppt").join("current_value"))
.unwrap()
.trim(),
"101"
);
assert_eq!(
std::fs::read_to_string(base.join("ppt_pl3_fppt").join("current_value"))
.unwrap()
.trim(),
"102"
);
assert_eq!(
std::fs::read_to_string(base.join("nv_dynamic_boost").join("current_value"))
.unwrap()
.trim(),
"9"
);
// apply DC
rt.block_on(async {
set_config_or_default(&attrs, &mut cfg, false, profile).await;
});
assert_eq!(
std::fs::read_to_string(base.join("ppt_pl1_spl").join("current_value"))
.unwrap()
.trim(),
"10"
);
assert_eq!(
std::fs::read_to_string(base.join("ppt_pl2_sppt").join("current_value"))
.unwrap()
.trim(),
"11"
);
assert_eq!(
std::fs::read_to_string(base.join("ppt_pl3_fppt").join("current_value"))
.unwrap()
.trim(),
"12"
);
assert_eq!(
std::fs::read_to_string(base.join("nv_dynamic_boost").join("current_value"))
.unwrap()
.trim(),
"3"
);
}

View File

@@ -0,0 +1,71 @@
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;
use asusd::asus_armoury::set_config_or_default;
use asusd::config::Config;
use rog_platform::asus_armoury::FirmwareAttributes;
use rog_platform::platform::PlatformProfile;
fn write_attr_dir(base: &PathBuf, name: &str, default: &str, display: &str) {
let attr_dir = base.join(name);
create_dir_all(&attr_dir).unwrap();
let mut f = File::create(attr_dir.join("default_value")).unwrap();
write!(f, "{}", default).unwrap();
let mut f = File::create(attr_dir.join("display_name")).unwrap();
write!(f, "{}", display).unwrap();
// create current_value file so set_current_value can open for write
let mut f = File::create(attr_dir.join("current_value")).unwrap();
write!(f, "{}", default).unwrap();
}
#[test]
fn nv_tgp_ac_dc_applies_different_values() {
let td = tempdir().unwrap();
let base = td.path().join("attributes");
create_dir_all(&base).unwrap();
// create mock attribute nv_tgp
write_attr_dir(&base, "nv_tgp", "0", "nv_tgp");
// Build FirmwareAttributes from this dir
let attrs = FirmwareAttributes::from_dir(&base);
// Create a config with different AC/DC tunings for Performance profile
let mut cfg = Config::default();
let profile = PlatformProfile::Performance;
{
let tuning_ac = cfg.select_tunings(true, profile);
tuning_ac.enabled = true;
tuning_ac
.group
.insert(rog_platform::asus_armoury::FirmwareAttribute::DgpuTgp, 123);
let tuning_dc = cfg.select_tunings(false, profile);
tuning_dc.enabled = true;
tuning_dc
.group
.insert(rog_platform::asus_armoury::FirmwareAttribute::DgpuTgp, 45);
}
// Apply for AC
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
set_config_or_default(&attrs, &mut cfg, true, profile).await;
});
let nv_tgp_val_path = base.join("nv_tgp").join("current_value");
let val_ac = std::fs::read_to_string(&nv_tgp_val_path).unwrap();
assert_eq!(val_ac.trim(), "123");
// Now apply for DC
rt.block_on(async {
set_config_or_default(&attrs, &mut cfg, false, profile).await;
});
let val_dc = std::fs::read_to_string(&nv_tgp_val_path).unwrap();
assert_eq!(val_dc.trim(), "45");
}

View File

@@ -15,7 +15,7 @@ ENV{DMI_FAMILY}=="*TX Gaming*", GOTO="asusd_start"
GOTO="asusd_end" GOTO="asusd_end"
LABEL="asusd_start" LABEL="asusd_start"
ACTION=="add|change", DRIVER=="asus-nb-wmi", TAG+="systemd", ENV{SYSTEMD_WANTS}="asusd.service" ACTION=="add|change", DRIVER=="asus-nb-wmi", TAG+="systemd", ENV{SYSTEMD_WANTS}+="asusd.service"
ACTION=="add|remove", DRIVER=="asus-nb-wmi", TAG+="systemd", RUN+="/usr/bin/systemctl restart asusd.service" ACTION=="add|remove", DRIVER=="asus-nb-wmi", TAG+="systemd", ENV{SYSTEMD_WANTS}+="asusd.service"
LABEL="asusd_end" LABEL="asusd_end"

View File

@@ -20,7 +20,7 @@
%global debug_package %{nil} %global debug_package %{nil}
%endif %endif
%define version 6.1.16 %define version 6.1.17
%define specrelease %{?dist} %define specrelease %{?dist}
%define pkg_release 9%{specrelease} %define pkg_release 9%{specrelease}
@@ -92,7 +92,9 @@ EOF
%build %build
export RUSTFLAGS="%{rustflags}" export RUSTFLAGS="%{rustflags}"
%if %{defined fedora} %if %{defined fedora}
%cargo_build %# Use an explicit cargo invocation for Fedora to avoid the macro adding `--locked`.
%# `--locked` breaks Fedora builds because the lockfile may not be appropriate for the distro buildroot.
/usr/bin/cargo auditable build --release
%else %else
/usr/bin/cargo auditable build --release /usr/bin/cargo auditable build --release
%endif %endif

View File

@@ -164,7 +164,29 @@ impl LedSupportFile {
return Some(data); return Some(data);
} }
warn!("Does {} exist?", ASUS_LED_MODE_USER_CONF); // If the system-wide support files were not available (e.g. running
// tests in CI or a development environment) try to load the
// bundled test data shipped with the crate under `data/aura_support.ron`.
// This allows unit tests to run without requiring files to be installed
// to `/usr/share/asusd`.
let mut bundled = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
bundled.push("data/aura_support.ron");
if let Ok(buf) = std::fs::read_to_string(&bundled) {
if let Ok(mut tmp) = ron::from_str::<LedSupportFile>(&buf) {
data.0.append(&mut tmp.0);
data.0.sort_by(|a, b| a.device_name.cmp(&b.device_name));
info!("Loaded bundled LED support data from {}", bundled.display());
return Some(data);
} else {
warn!(
"Bundled aura_support.ron present but failed to parse: {}",
bundled.display()
);
}
} else {
warn!("Does {} exist?", ASUS_LED_MODE_USER_CONF);
}
None None
} }
} }

View File

@@ -207,12 +207,14 @@ mod tests {
fn single_key_next_state_then_create() { fn single_key_next_state_then_create() {
let layout = KeyLayout::default_layout(); let layout = KeyLayout::default_layout();
let mut seq = AdvancedEffects::new(false); let mut seq = AdvancedEffects::new(false);
seq.effects seq.effects.push(Effect::Static(Static::new(
.push(Effect::Static(Static::new(LedCode::F, Colour { LedCode::F,
Colour {
r: 255, r: 255,
g: 127, g: 127,
b: 0, b: 0,
}))); },
)));
seq.next_state(&layout); seq.next_state(&layout);
let packets = seq.create_packets(); let packets = seq.create_packets();

View File

@@ -335,93 +335,117 @@ impl KeyLayout {
KeyShape::new_led(1.0, 1.0, 0.1, 0.1, 0.1, 0.1), KeyShape::new_led(1.0, 1.0, 0.1, 0.1, 0.1, 0.1),
)]), )]),
key_rows: vec![ key_rows: vec![
KeyRow::new(0.1, 0.1, vec![ KeyRow::new(
(LedCode::Esc, "regular".to_owned()), 0.1,
(LedCode::F1, "regular".to_owned()), 0.1,
(LedCode::F2, "regular".to_owned()), vec![
(LedCode::F3, "regular".to_owned()), (LedCode::Esc, "regular".to_owned()),
(LedCode::F4, "regular".to_owned()), (LedCode::F1, "regular".to_owned()),
// not sure which key to put here (LedCode::F2, "regular".to_owned()),
(LedCode::F5, "regular".to_owned()), (LedCode::F3, "regular".to_owned()),
(LedCode::F6, "regular".to_owned()), (LedCode::F4, "regular".to_owned()),
(LedCode::F7, "regular".to_owned()), // not sure which key to put here
(LedCode::F8, "regular".to_owned()), (LedCode::F5, "regular".to_owned()),
(LedCode::F9, "regular".to_owned()), (LedCode::F6, "regular".to_owned()),
(LedCode::F10, "regular".to_owned()), (LedCode::F7, "regular".to_owned()),
(LedCode::F11, "regular".to_owned()), (LedCode::F8, "regular".to_owned()),
(LedCode::F12, "regular".to_owned()), (LedCode::F9, "regular".to_owned()),
]), (LedCode::F10, "regular".to_owned()),
KeyRow::new(0.1, 0.1, vec![ (LedCode::F11, "regular".to_owned()),
(LedCode::Tilde, "regular".to_owned()), (LedCode::F12, "regular".to_owned()),
(LedCode::N1, "regular".to_owned()), ],
(LedCode::N2, "regular".to_owned()), ),
(LedCode::N3, "regular".to_owned()), KeyRow::new(
(LedCode::N4, "regular".to_owned()), 0.1,
(LedCode::N5, "regular".to_owned()), 0.1,
(LedCode::N6, "regular".to_owned()), vec![
(LedCode::N7, "regular".to_owned()), (LedCode::Tilde, "regular".to_owned()),
(LedCode::N8, "regular".to_owned()), (LedCode::N1, "regular".to_owned()),
(LedCode::N9, "regular".to_owned()), (LedCode::N2, "regular".to_owned()),
(LedCode::N0, "regular".to_owned()), (LedCode::N3, "regular".to_owned()),
(LedCode::Hyphen, "regular".to_owned()), (LedCode::N4, "regular".to_owned()),
(LedCode::Equals, "regular".to_owned()), (LedCode::N5, "regular".to_owned()),
(LedCode::Backspace, "regular".to_owned()), (LedCode::N6, "regular".to_owned()),
]), (LedCode::N7, "regular".to_owned()),
KeyRow::new(0.1, 0.1, vec![ (LedCode::N8, "regular".to_owned()),
(LedCode::Tab, "regular".to_owned()), (LedCode::N9, "regular".to_owned()),
(LedCode::Q, "regular".to_owned()), (LedCode::N0, "regular".to_owned()),
(LedCode::W, "regular".to_owned()), (LedCode::Hyphen, "regular".to_owned()),
(LedCode::E, "regular".to_owned()), (LedCode::Equals, "regular".to_owned()),
(LedCode::R, "regular".to_owned()), (LedCode::Backspace, "regular".to_owned()),
(LedCode::T, "regular".to_owned()), ],
(LedCode::Y, "regular".to_owned()), ),
(LedCode::U, "regular".to_owned()), KeyRow::new(
(LedCode::I, "regular".to_owned()), 0.1,
(LedCode::O, "regular".to_owned()), 0.1,
(LedCode::P, "regular".to_owned()), vec![
(LedCode::LBracket, "regular".to_owned()), (LedCode::Tab, "regular".to_owned()),
(LedCode::RBracket, "regular".to_owned()), (LedCode::Q, "regular".to_owned()),
(LedCode::BackSlash, "regular".to_owned()), (LedCode::W, "regular".to_owned()),
]), (LedCode::E, "regular".to_owned()),
KeyRow::new(0.1, 0.1, vec![ (LedCode::R, "regular".to_owned()),
(LedCode::Caps, "regular".to_owned()), (LedCode::T, "regular".to_owned()),
(LedCode::A, "regular".to_owned()), (LedCode::Y, "regular".to_owned()),
(LedCode::S, "regular".to_owned()), (LedCode::U, "regular".to_owned()),
(LedCode::D, "regular".to_owned()), (LedCode::I, "regular".to_owned()),
(LedCode::F, "regular".to_owned()), (LedCode::O, "regular".to_owned()),
(LedCode::G, "regular".to_owned()), (LedCode::P, "regular".to_owned()),
(LedCode::H, "regular".to_owned()), (LedCode::LBracket, "regular".to_owned()),
(LedCode::J, "regular".to_owned()), (LedCode::RBracket, "regular".to_owned()),
(LedCode::K, "regular".to_owned()), (LedCode::BackSlash, "regular".to_owned()),
(LedCode::L, "regular".to_owned()), ],
(LedCode::SemiColon, "regular".to_owned()), ),
(LedCode::Quote, "regular".to_owned()), KeyRow::new(
(LedCode::Return, "regular".to_owned()), 0.1,
]), 0.1,
KeyRow::new(0.1, 0.1, vec![ vec![
(LedCode::LShift, "regular".to_owned()), (LedCode::Caps, "regular".to_owned()),
(LedCode::Z, "regular".to_owned()), (LedCode::A, "regular".to_owned()),
(LedCode::X, "regular".to_owned()), (LedCode::S, "regular".to_owned()),
(LedCode::C, "regular".to_owned()), (LedCode::D, "regular".to_owned()),
(LedCode::V, "regular".to_owned()), (LedCode::F, "regular".to_owned()),
(LedCode::B, "regular".to_owned()), (LedCode::G, "regular".to_owned()),
(LedCode::N, "regular".to_owned()), (LedCode::H, "regular".to_owned()),
(LedCode::M, "regular".to_owned()), (LedCode::J, "regular".to_owned()),
(LedCode::Comma, "regular".to_owned()), (LedCode::K, "regular".to_owned()),
(LedCode::Period, "regular".to_owned()), (LedCode::L, "regular".to_owned()),
(LedCode::FwdSlash, "regular".to_owned()), (LedCode::SemiColon, "regular".to_owned()),
(LedCode::Rshift, "regular".to_owned()), (LedCode::Quote, "regular".to_owned()),
]), (LedCode::Return, "regular".to_owned()),
KeyRow::new(0.1, 0.1, vec![ ],
(LedCode::LCtrl, "regular".to_owned()), ),
(LedCode::LFn, "regular".to_owned()), KeyRow::new(
(LedCode::Meta, "regular".to_owned()), 0.1,
(LedCode::LAlt, "regular".to_owned()), 0.1,
(LedCode::Spacebar, "regular".to_owned()), vec![
(LedCode::RAlt, "regular".to_owned()), (LedCode::LShift, "regular".to_owned()),
(LedCode::PrtSc, "regular".to_owned()), (LedCode::Z, "regular".to_owned()),
(LedCode::RCtrl, "regular".to_owned()), (LedCode::X, "regular".to_owned()),
]), (LedCode::C, "regular".to_owned()),
(LedCode::V, "regular".to_owned()),
(LedCode::B, "regular".to_owned()),
(LedCode::N, "regular".to_owned()),
(LedCode::M, "regular".to_owned()),
(LedCode::Comma, "regular".to_owned()),
(LedCode::Period, "regular".to_owned()),
(LedCode::FwdSlash, "regular".to_owned()),
(LedCode::Rshift, "regular".to_owned()),
],
),
KeyRow::new(
0.1,
0.1,
vec![
(LedCode::LCtrl, "regular".to_owned()),
(LedCode::LFn, "regular".to_owned()),
(LedCode::Meta, "regular".to_owned()),
(LedCode::LAlt, "regular".to_owned()),
(LedCode::Spacebar, "regular".to_owned()),
(LedCode::RAlt, "regular".to_owned()),
(LedCode::PrtSc, "regular".to_owned()),
(LedCode::RCtrl, "regular".to_owned()),
],
),
], ],
} }
} }

View File

@@ -24,4 +24,6 @@ pub struct CliStart {
that might match your laptop" that might match your laptop"
)] )]
pub layout_viewing: bool, pub layout_viewing: bool,
#[options(help = "start in tray mode - main window hidden")]
pub tray_mode: bool,
} }

View File

@@ -140,12 +140,22 @@ async fn main() -> Result<()> {
config.startup_in_background = false; config.startup_in_background = false;
config.start_fullscreen = true; config.start_fullscreen = true;
} }
if cli_parsed.tray_mode {
config.enable_tray_icon = true;
config.run_in_background = true;
config.startup_in_background = true;
}
config.write(); config.write();
let enable_tray_icon = config.enable_tray_icon; let enable_tray_icon = config.enable_tray_icon;
let startup_in_background = config.startup_in_background; let startup_in_background = config.startup_in_background;
let config = Arc::new(Mutex::new(config)); let config = Arc::new(Mutex::new(config));
// shared weak handle to the UI so other threads can request UI updates
let ui_handle: Arc<Mutex<Option<slint::Weak<MainWindow>>>> = Arc::new(Mutex::new(None));
start_notifications(config.clone(), &rt)?; start_notifications(config.clone(), &rt)?;
if enable_tray_icon { if enable_tray_icon {
@@ -170,12 +180,14 @@ async fn main() -> Result<()> {
slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/")); slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/"));
} }
let config_for_ui = config.clone();
let ui_handle_for_thread = ui_handle.clone();
thread::spawn(move || { thread::spawn(move || {
let mut state = AppState::StartingUp; let mut state = AppState::StartingUp;
loop { loop {
if is_rog_ally { if is_rog_ally {
let config_copy_2 = config.clone(); let config_copy_2 = config_for_ui.clone();
let newui = setup_window(config.clone()); let newui = setup_window(config_for_ui.clone());
newui.window().on_close_requested(move || { newui.window().on_close_requested(move || {
exit(0); exit(0);
}); });
@@ -211,13 +223,19 @@ async fn main() -> Result<()> {
*app_state = AppState::MainWindowOpen; *app_state = AppState::MainWindowOpen;
} }
let config_copy = config.clone(); let config_copy = config_for_ui.clone();
let app_state_copy = app_state.clone(); let app_state_copy = app_state.clone();
let ui_handle_for_ui = ui_handle_for_thread.clone();
slint::invoke_from_event_loop(move || { slint::invoke_from_event_loop(move || {
let ui_handle_for_ui = ui_handle_for_ui.clone();
UI.with(|ui| { UI.with(|ui| {
let app_state_copy = app_state_copy.clone(); let app_state_copy = app_state_copy.clone();
let mut ui = ui.borrow_mut(); let mut ui = ui.borrow_mut();
if let Some(ui) = ui.as_mut() { if let Some(ui) = ui.as_mut() {
// store weak handle so other threads can update UI globals
if let Ok(mut h) = ui_handle_for_ui.lock() {
*h = Some(ui.as_weak());
}
ui.window().show().unwrap(); ui.window().show().unwrap();
ui.window().on_close_requested(move || { ui.window().on_close_requested(move || {
if let Ok(mut app_state) = app_state_copy.lock() { if let Ok(mut app_state) = app_state_copy.lock() {
@@ -228,6 +246,10 @@ async fn main() -> Result<()> {
} else { } else {
let config_copy_2 = config_copy.clone(); let config_copy_2 = config_copy.clone();
let newui = setup_window(config_copy); let newui = setup_window(config_copy);
// store weak handle for the newly created UI
if let Ok(mut h) = ui_handle_for_ui.lock() {
*h = Some(newui.as_weak());
}
newui.window().on_close_requested(move || { newui.window().on_close_requested(move || {
if let Ok(mut app_state) = app_state_copy.lock() { if let Ok(mut app_state) = app_state_copy.lock() {
*app_state = AppState::MainWindowClosed; *app_state = AppState::MainWindowClosed;
@@ -263,8 +285,8 @@ async fn main() -> Result<()> {
slint::quit_event_loop().unwrap(); slint::quit_event_loop().unwrap();
exit(0); exit(0);
} else if state != AppState::MainWindowOpen { } else if state != AppState::MainWindowOpen {
if let Ok(config) = config.lock() { if let Ok(cfg) = config_for_ui.lock() {
if !config.run_in_background { if !cfg.run_in_background {
slint::quit_event_loop().unwrap(); slint::quit_event_loop().unwrap();
exit(0); exit(0);
} }
@@ -274,11 +296,67 @@ async fn main() -> Result<()> {
} }
}); });
// start config watcher to pick up external edits
spawn_config_watcher(config.clone(), ui_handle.clone());
slint::run_event_loop_until_quit().unwrap(); slint::run_event_loop_until_quit().unwrap();
rt.shutdown_background(); rt.shutdown_background();
Ok(()) Ok(())
} }
// Spawn a watcher thread that polls the config file and reloads it when modified.
// This keeps the running rogcc instance in sync with manual edits to the config file.
fn spawn_config_watcher(
config: Arc<Mutex<Config>>,
ui_handle: Arc<Mutex<Option<slint::Weak<MainWindow>>>>,
) {
std::thread::spawn(move || {
use std::time::SystemTime;
let cfg_path = Config::config_dir().join(Config::new().file_name());
let mut last_mtime: Option<SystemTime> = None;
loop {
if let Ok(meta) = std::fs::metadata(&cfg_path) {
if let Ok(m) = meta.modified() {
if last_mtime.is_none() {
last_mtime = Some(m);
} else if last_mtime.is_some_and(|t| t < m) {
// file changed, reload
last_mtime = Some(m);
let new_cfg = Config::new().load();
if let Ok(mut lock) = config.lock() {
*lock = new_cfg.clone();
}
// Update UI app settings globals if UI is present
if let Ok(maybe_weak) = ui_handle.lock() {
if let Some(weak) = maybe_weak.clone() {
let config_for_ui = config.clone();
weak.upgrade_in_event_loop(move |w| {
if let Ok(lock) = config_for_ui.lock() {
let cfg = lock.clone();
w.global::<rog_control_center::AppSettingsPageData>()
.set_run_in_background(cfg.run_in_background);
w.global::<rog_control_center::AppSettingsPageData>()
.set_startup_in_background(cfg.startup_in_background);
w.global::<rog_control_center::AppSettingsPageData>()
.set_enable_tray_icon(cfg.enable_tray_icon);
w.global::<rog_control_center::AppSettingsPageData>()
.set_enable_dgpu_notifications(
cfg.notifications.enabled,
);
}
})
.ok();
}
}
}
}
}
std::thread::sleep(std::time::Duration::from_secs(2));
}
});
}
fn do_cli_help(parsed: &CliStart) -> bool { fn do_cli_help(parsed: &CliStart) -> bool {
if parsed.help { if parsed.help {
println!("{}", CliStart::usage()); println!("{}", CliStart::usage());

View File

@@ -100,6 +100,31 @@ impl Bios {
pub fn set_panel_od(&self, _b: bool) -> Result<()> { pub fn set_panel_od(&self, _b: bool) -> Result<()> {
Ok(()) Ok(())
} }
// Mock NV/dGPU tunables
pub fn nv_dynamic_boost(&self) -> Result<i16> {
Ok(0)
}
pub fn set_nv_dynamic_boost(&self, _v: i16) -> Result<()> {
Ok(())
}
pub fn nv_temp_target(&self) -> Result<i16> {
Ok(0)
}
pub fn set_nv_temp_target(&self, _v: i16) -> Result<()> {
Ok(())
}
pub fn nv_tgp(&self) -> Result<i16> {
Ok(0)
}
pub fn set_nv_tgp(&self, _v: i16) -> Result<()> {
Ok(())
}
} }
pub struct Profile; pub struct Profile;

View File

@@ -203,11 +203,19 @@ pub fn start_notifications(
})?; })?;
let proxy_copy = proxy.clone(); let proxy_copy = proxy.clone();
let enabled_notifications_copy_action = enabled_notifications_copy.clone();
let mut p = proxy.receive_notify_action().await?; let mut p = proxy.receive_notify_action().await?;
tokio::spawn(async move { tokio::spawn(async move {
info!("Started zbus signal thread: receive_notify_action"); info!("Started zbus signal thread: receive_notify_action");
while let Some(e) = p.next().await { while let Some(e) = p.next().await {
if let Ok(out) = e.args() { if let Ok(out) = e.args() {
// Respect user notification settings for gpu actions
if let Ok(cfg) = enabled_notifications_copy_action.lock() {
if !cfg.notifications.enabled || !cfg.notifications.receive_notify_gfx {
continue;
}
}
let action = out.action(); let action = out.action();
let mode = convert_gfx_mode(proxy.mode().await.unwrap_or_default()); let mode = convert_gfx_mode(proxy.mode().await.unwrap_or_default());
match action { match action {
@@ -309,7 +317,9 @@ fn do_gfx_action_notif(message: &str, action: GfxUserAction, mode: GpuMode) -> R
//.hint(Hint::Resident(true)) //.hint(Hint::Resident(true))
.hint(Hint::Category("device".into())) .hint(Hint::Category("device".into()))
.urgency(Urgency::Critical) .urgency(Urgency::Critical)
.timeout(Timeout::Never) // For user-action notifications keep them visible if they require interaction
// but for non-interactive actions we prefer they auto-hide like other notifs.
.timeout(Timeout::Milliseconds(6000))
.icon("dialog-warning") .icon("dialog-warning")
.hint(Hint::Transient(true)); .hint(Hint::Transient(true));

View File

@@ -130,6 +130,51 @@ macro_rules! init_minmax_property {
// For handling callbacks from UI value changes // For handling callbacks from UI value changes
macro_rules! setup_callback { macro_rules! setup_callback {
// Minmax (AttrMinMax) variant - pass an extra `minmax` token
($property:ident, $handle:expr, $attr:expr, $type:tt, minmax) => {
let handle_copy = $handle.as_weak();
let proxy_copy = $attr.clone();
concat_idents!(on_callback = on_cb_, $property {
$handle
.global::<SystemPageData>()
.on_callback(move |v| {
let handle_copy = handle_copy.clone();
let proxy_copy = proxy_copy.clone();
tokio::spawn(async move {
let res = proxy_copy
.set_current_value(convert_to_dbus!($type, v))
.await;
show_toast(
format!("{} successfully set to {}", stringify!($property), v).into(),
format!("Setting {} failed", stringify!($property)).into(),
handle_copy.clone(),
res.clone(),
);
if res.is_ok() {
let min_v = proxy_copy.min_value().await.unwrap_or(-1);
let max_v = proxy_copy.max_value().await.unwrap_or(-1);
if let Ok(cur_val) = proxy_copy.current_value().await {
let cur_f = cur_val as f32;
handle_copy
.upgrade_in_event_loop(move |handle| {
concat_idents!(setter = set_, $property {
handle.global::<SystemPageData>().setter(AttrMinMax {
min: min_v,
max: max_v,
current: cur_f,
});
});
})
.ok();
}
}
});
});
});
};
// Scalar variant (i32/bool/f32) - update scalar setter with authoritative value
($property:ident, $handle:expr, $attr:expr, $type:tt) => { ($property:ident, $handle:expr, $attr:expr, $type:tt) => {
let handle_copy = $handle.as_weak(); let handle_copy = $handle.as_weak();
let proxy_copy = $attr.clone(); let proxy_copy = $attr.clone();
@@ -140,12 +185,28 @@ macro_rules! setup_callback {
let handle_copy = handle_copy.clone(); let handle_copy = handle_copy.clone();
let proxy_copy = proxy_copy.clone(); let proxy_copy = proxy_copy.clone();
tokio::spawn(async move { tokio::spawn(async move {
let res = proxy_copy
.set_current_value(convert_to_dbus!($type, v))
.await;
show_toast( show_toast(
format!("{} successfully set to {}", stringify!($property), v).into(), format!("{} successfully set to {}", stringify!($property), v).into(),
format!("Setting {} failed", stringify!($property)).into(), format!("Setting {} failed", stringify!($property)).into(),
handle_copy, handle_copy.clone(),
proxy_copy.set_current_value(convert_to_dbus!($type, v)).await, res.clone(),
); );
if res.is_ok() {
// Query authoritative value and set scalar global
if let Ok(cur_val) = proxy_copy.current_value().await {
handle_copy.upgrade_in_event_loop(move |handle| {
concat_idents!(setter = set_, $property {
handle
.global::<SystemPageData>()
.setter(convert_value!($type, cur_val));
});
}).ok();
}
}
}); });
}); });
}); });
@@ -615,49 +676,49 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
} }
FirmwareAttribute::PptPl1Spl => { FirmwareAttribute::PptPl1Spl => {
init_minmax_property!(ppt_pl1_spl, handle, attr); init_minmax_property!(ppt_pl1_spl, handle, attr);
setup_callback!(ppt_pl1_spl, handle, attr, i32); setup_callback!(ppt_pl1_spl, handle, attr, i32, minmax);
setup_callback_restore_default!(ppt_pl1_spl, handle, attr); setup_callback_restore_default!(ppt_pl1_spl, handle, attr);
setup_minmax_external!(ppt_pl1_spl, handle, attr, platform); setup_minmax_external!(ppt_pl1_spl, handle, attr, platform);
} }
FirmwareAttribute::PptPl2Sppt => { FirmwareAttribute::PptPl2Sppt => {
init_minmax_property!(ppt_pl2_sppt, handle, attr); init_minmax_property!(ppt_pl2_sppt, handle, attr);
setup_callback!(ppt_pl2_sppt, handle, attr, i32); setup_callback!(ppt_pl2_sppt, handle, attr, i32, minmax);
setup_callback_restore_default!(ppt_pl2_sppt, handle, attr); setup_callback_restore_default!(ppt_pl2_sppt, handle, attr);
setup_minmax_external!(ppt_pl2_sppt, handle, attr, platform); setup_minmax_external!(ppt_pl2_sppt, handle, attr, platform);
} }
FirmwareAttribute::PptPl3Fppt => { FirmwareAttribute::PptPl3Fppt => {
init_minmax_property!(ppt_pl3_fppt, handle, attr); init_minmax_property!(ppt_pl3_fppt, handle, attr);
setup_callback!(ppt_pl3_fppt, handle, attr, i32); setup_callback!(ppt_pl3_fppt, handle, attr, i32, minmax);
setup_callback_restore_default!(ppt_pl3_fppt, handle, attr); setup_callback_restore_default!(ppt_pl3_fppt, handle, attr);
setup_minmax_external!(ppt_pl3_fppt, handle, attr, platform); setup_minmax_external!(ppt_pl3_fppt, handle, attr, platform);
} }
FirmwareAttribute::PptFppt => { FirmwareAttribute::PptFppt => {
init_minmax_property!(ppt_fppt, handle, attr); init_minmax_property!(ppt_fppt, handle, attr);
setup_callback!(ppt_fppt, handle, attr, i32); setup_callback!(ppt_fppt, handle, attr, i32, minmax);
setup_callback_restore_default!(ppt_fppt, handle, attr); setup_callback_restore_default!(ppt_fppt, handle, attr);
setup_minmax_external!(ppt_fppt, handle, attr, platform); setup_minmax_external!(ppt_fppt, handle, attr, platform);
} }
FirmwareAttribute::PptApuSppt => { FirmwareAttribute::PptApuSppt => {
init_minmax_property!(ppt_apu_sppt, handle, attr); init_minmax_property!(ppt_apu_sppt, handle, attr);
setup_callback!(ppt_apu_sppt, handle, attr, i32); setup_callback!(ppt_apu_sppt, handle, attr, i32, minmax);
setup_callback_restore_default!(ppt_apu_sppt, handle, attr); setup_callback_restore_default!(ppt_apu_sppt, handle, attr);
setup_minmax_external!(ppt_apu_sppt, handle, attr, platform); setup_minmax_external!(ppt_apu_sppt, handle, attr, platform);
} }
FirmwareAttribute::PptPlatformSppt => { FirmwareAttribute::PptPlatformSppt => {
init_minmax_property!(ppt_platform_sppt, handle, attr); init_minmax_property!(ppt_platform_sppt, handle, attr);
setup_callback!(ppt_platform_sppt, handle, attr, i32); setup_callback!(ppt_platform_sppt, handle, attr, i32, minmax);
setup_callback_restore_default!(ppt_platform_sppt, handle, attr); setup_callback_restore_default!(ppt_platform_sppt, handle, attr);
setup_minmax_external!(ppt_platform_sppt, handle, attr, platform); setup_minmax_external!(ppt_platform_sppt, handle, attr, platform);
} }
FirmwareAttribute::NvDynamicBoost => { FirmwareAttribute::NvDynamicBoost => {
init_minmax_property!(nv_dynamic_boost, handle, attr); init_minmax_property!(nv_dynamic_boost, handle, attr);
setup_callback!(nv_dynamic_boost, handle, attr, i32); setup_callback!(nv_dynamic_boost, handle, attr, i32, minmax);
setup_callback_restore_default!(nv_dynamic_boost, handle, attr); setup_callback_restore_default!(nv_dynamic_boost, handle, attr);
setup_minmax_external!(nv_dynamic_boost, handle, attr, platform); setup_minmax_external!(nv_dynamic_boost, handle, attr, platform);
} }
FirmwareAttribute::NvTempTarget => { FirmwareAttribute::NvTempTarget => {
init_minmax_property!(nv_temp_target, handle, attr); init_minmax_property!(nv_temp_target, handle, attr);
setup_callback!(nv_temp_target, handle, attr, i32); setup_callback!(nv_temp_target, handle, attr, i32, minmax);
setup_callback_restore_default!(nv_temp_target, handle, attr); setup_callback_restore_default!(nv_temp_target, handle, attr);
setup_minmax_external!(nv_temp_target, handle, attr, platform); setup_minmax_external!(nv_temp_target, handle, attr, platform);
} }

View File

@@ -95,7 +95,7 @@ impl Attribute {
_ => return Err(PlatformError::InvalidValue), _ => return Err(PlatformError::InvalidValue),
}; };
let mut file = OpenOptions::new().write(true).open(&path)?; let mut file = OpenOptions::new().write(true).truncate(true).open(&path)?;
file.write_all(value_str.as_bytes())?; file.write_all(value_str.as_bytes())?;
Ok(()) Ok(())
} }
@@ -244,6 +244,37 @@ impl FirmwareAttributes {
Self { attrs } Self { attrs }
} }
/// Create attributes collection from an arbitrary base directory. Intended for tests
/// where a fake sysfs-like layout can be supplied.
pub fn from_dir(base_dir: &std::path::Path) -> Self {
let mut attrs = Vec::new();
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();
if name == "pending_reboot" {
continue;
}
let help = read_string(&base_path.join("display_name")).unwrap_or_default();
let (default_value, possible_values, min_value, max_value, scalar_increment) =
Attribute::read_base_values(&base_path);
attrs.push(Attribute {
name,
help,
default_value,
possible_values,
min_value,
max_value,
scalar_increment,
base_path,
});
}
}
Self { attrs }
}
pub fn attributes(&self) -> &Vec<Attribute> { pub fn attributes(&self) -> &Vec<Attribute> {
&self.attrs &self.attrs
} }
@@ -282,8 +313,8 @@ define_attribute_getters!(
ppt_fppt, ppt_fppt,
nv_dynamic_boost, nv_dynamic_boost,
nv_temp_target, nv_temp_target,
dgpu_base_tgp, nv_base_tgp,
dgpu_tgp, nv_tgp,
charge_mode, charge_mode,
boot_sound, boot_sound,
mcu_powersave, mcu_powersave,
@@ -300,6 +331,7 @@ define_attribute_getters!(
/// CamelCase names of the properties. Intended for use with DBUS /// CamelCase names of the properties. Intended for use with DBUS
#[repr(u8)] #[repr(u8)]
#[derive( #[derive(
Debug,
Clone, Clone,
Copy, Copy,
Serialize, Serialize,
@@ -362,6 +394,7 @@ impl FirmwareAttribute {
self, self,
FirmwareAttribute::NvDynamicBoost FirmwareAttribute::NvDynamicBoost
| FirmwareAttribute::NvTempTarget | FirmwareAttribute::NvTempTarget
| FirmwareAttribute::DgpuBaseTgp
| FirmwareAttribute::DgpuTgp | FirmwareAttribute::DgpuTgp
) )
} }
@@ -382,8 +415,11 @@ impl From<&str> for FirmwareAttribute {
"ppt_platform_sppt" => Self::PptPlatformSppt, "ppt_platform_sppt" => Self::PptPlatformSppt,
"nv_dynamic_boost" => Self::NvDynamicBoost, "nv_dynamic_boost" => Self::NvDynamicBoost,
"nv_temp_target" => Self::NvTempTarget, "nv_temp_target" => Self::NvTempTarget,
"nv_tgp" => Self::DgpuTgp,
"nv_base_tgp" => Self::DgpuBaseTgp, "nv_base_tgp" => Self::DgpuBaseTgp,
/* Backwards compatibility: some kernels expose these attributes with a dgpu_* prefix */
"dgpu_tgp" => Self::DgpuTgp, "dgpu_tgp" => Self::DgpuTgp,
"dgpu_base_tgp" => Self::DgpuBaseTgp,
"charge_mode" => Self::ChargeMode, "charge_mode" => Self::ChargeMode,
"boot_sound" => Self::BootSound, "boot_sound" => Self::BootSound,
"mcu_powersave" => Self::McuPowersave, "mcu_powersave" => Self::McuPowersave,
@@ -419,8 +455,8 @@ impl From<FirmwareAttribute> for &str {
FirmwareAttribute::PptPlatformSppt => "ppt_platform_sppt", FirmwareAttribute::PptPlatformSppt => "ppt_platform_sppt",
FirmwareAttribute::NvDynamicBoost => "nv_dynamic_boost", FirmwareAttribute::NvDynamicBoost => "nv_dynamic_boost",
FirmwareAttribute::NvTempTarget => "nv_temp_target", FirmwareAttribute::NvTempTarget => "nv_temp_target",
FirmwareAttribute::DgpuBaseTgp => "dgpu_base_tgp", FirmwareAttribute::DgpuBaseTgp => "nv_base_tgp",
FirmwareAttribute::DgpuTgp => "dgpu_tgp", FirmwareAttribute::DgpuTgp => "nv_tgp",
FirmwareAttribute::ChargeMode => "charge_mode", FirmwareAttribute::ChargeMode => "charge_mode",
FirmwareAttribute::BootSound => "boot_sound", FirmwareAttribute::BootSound => "boot_sound",
FirmwareAttribute::McuPowersave => "mcu_powersave", FirmwareAttribute::McuPowersave => "mcu_powersave",
@@ -519,3 +555,28 @@ mod tests {
attr.set_current_value(&val).unwrap(); attr.set_current_value(&val).unwrap();
} }
} }
#[cfg(test)]
mod alias_tests {
use super::FirmwareAttribute;
#[test]
fn tgp_aliases_map_to_same_variant() {
let nv = FirmwareAttribute::from("nv_tgp");
let dgpu = FirmwareAttribute::from("dgpu_tgp");
assert_eq!(nv, dgpu);
let nv_base = FirmwareAttribute::from("nv_base_tgp");
let dgpu_base = FirmwareAttribute::from("dgpu_base_tgp");
assert_eq!(nv_base, dgpu_base);
}
#[test]
fn tgp_canonical_output_is_nv_tgp() {
let s: &str = FirmwareAttribute::DgpuTgp.into();
assert_eq!(s, "nv_tgp");
let s_base: &str = FirmwareAttribute::DgpuBaseTgp.into();
assert_eq!(s_base, "nv_base_tgp");
}
}

View File

@@ -267,18 +267,24 @@ mod tests {
fn check_cpu() { fn check_cpu() {
let cpu = CPUControl::new().unwrap(); let cpu = CPUControl::new().unwrap();
assert_eq!(cpu.get_governor().unwrap(), CPUGovernor::Powersave); assert_eq!(cpu.get_governor().unwrap(), CPUGovernor::Powersave);
assert_eq!(cpu.get_available_governors().unwrap(), vec![ assert_eq!(
CPUGovernor::Performance, cpu.get_available_governors().unwrap(),
CPUGovernor::Powersave vec![
]); CPUGovernor::Performance,
CPUGovernor::Powersave
]
);
assert_eq!(cpu.get_epp().unwrap(), CPUEPP::BalancePower); assert_eq!(cpu.get_epp().unwrap(), CPUEPP::BalancePower);
assert_eq!(cpu.get_available_epp().unwrap(), vec![ assert_eq!(
CPUEPP::Default, cpu.get_available_epp().unwrap(),
CPUEPP::Performance, vec![
CPUEPP::BalancePerformance, CPUEPP::Default,
CPUEPP::BalancePower, CPUEPP::Performance,
CPUEPP::Power, CPUEPP::BalancePerformance,
]); CPUEPP::BalancePower,
CPUEPP::Power,
]
);
} }
} }

View File

@@ -106,3 +106,13 @@ impl AsusPower {
)) ))
} }
} }
impl Default for AsusPower {
fn default() -> Self {
Self {
mains: PathBuf::from("/this_shouldNeVErr_exisid"),
battery: PathBuf::from("/this_shouldNeVErr_exisid"),
usb: None,
}
}
}

4
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["rustfmt", "clippy"]
profile = "minimal"

View File

@@ -157,13 +157,14 @@ fn main() -> Result<(), Box<dyn Error>> {
for (x_count, b) in dev.buffer[start..=end].iter().enumerate() { for (x_count, b) in dev.buffer[start..=end].iter().enumerate() {
canvas.set_draw_color(Color::RGB(*b, *b, *b)); canvas.set_draw_color(Color::RGB(*b, *b, *b));
let x: i32 = w + x_count as i32 * w let x: i32 = w + x_count as i32 * w - {
- if !(y_count + y_offset as usize).is_multiple_of(2) { #[allow(clippy::manual_is_multiple_of)]
if (y_count + y_offset as usize) % 2 != 0 {
0 0
} else { } else {
w / 2 w / 2
} }
+ row.3 * w; } + row.3 * w;
let y = y_count as i32 * h - y_offset * h; let y = y_count as i32 * h - y_offset * h;
canvas canvas
.fill_rect(Rect::new(x, y, w as u32, h as u32)) .fill_rect(Rect::new(x, y, w as u32, h as u32))