Compare commits

...

13 Commits

Author SHA1 Message Date
rforced 0821c2d516 Merge branch 'fix-z13-2025-1' into 'devel'
Fixes for Asus z13 2025

See merge request asus-linux/asusctl!245
2026-01-24 15:16:21 -05:00
Denis Benato d890461777 Merge branch 'devel' into 'devel'
feat: add pulse mode to G835L and format file

See merge request asus-linux/asusctl!252
2026-01-24 20:12:56 +00:00
Denis Benato bcdbc45931 feat: make notifications disappear after 5 seconds from last action 2026-01-24 21:10:44 +01:00
Ghoul b97242981c feat: add pulse to G835L LED modes 2026-01-24 22:48:48 +05:00
Denis Benato 1d10f99e77 chore: updated CHANGELOG.md 2026-01-24 18:06:18 +01:00
Denis Benato 5901a4b4d9 fix: aura CLI interface 2026-01-24 17:58:54 +01:00
Denis Benato 9cd14d108c wip: reload current settings in rogcc for LEDs 2026-01-24 17:26:42 +01:00
Denis Benato 5d4b164b3b fix: LEDs managfement in rogcc 2026-01-24 16:49:48 +01:00
Josh Dariano f471f340d4 Fix fan controls on z13 2026-01-16 16:37:15 -05:00
Josh Dariano 8095ac34ed Fix formatting 2026-01-16 15:48:02 -05:00
Josh Dariano 9d629b62ca Fix aura backlight for z13 2026-01-16 15:33:30 -05:00
Josh Dariano 5282c56f59 Merge remote-tracking branch 'rforced/fix-z13-2025' 2026-01-16 14:13:45 -05:00
Josh Dariano 0d2cd4eb10 Fix keyboard light 2026-01-16 13:57:35 -05:00
17 changed files with 659 additions and 274 deletions
+2
View File
@@ -7,6 +7,8 @@
- Improve firmware attributes handling
- Very good looking UI restyling from @shevchenko0013
- Added support for GA402NV matrix: thanks @Ghoul4500
- Fixed aura CLI interface
- Fixed LEDs handling in rogcc
## [6.3.1]
+207 -7
View File
@@ -312,6 +312,133 @@ pub struct TwoColourSpeed {
pub zone: AuraZone,
}
/// Two-colour star effect (separate subcommand name)
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(subcommand, name = "stars", description = "two-colour star effect")]
pub struct StarsTwoColour {
#[argh(option, description = "set the first RGB value e.g. ff00ff")]
pub colour: Colour,
#[argh(option, description = "set the second RGB value e.g. ff00ff")]
pub colour2: Colour,
#[argh(option, description = "set the speed: low, med, high")]
pub speed: Speed,
#[argh(
option,
default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)]
pub zone: AuraZone,
}
/// Rain effect (single-speed, separate subcommand name)
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "rain",
description = "single speed-based rain effect"
)]
pub struct RainSingleSpeed {
#[argh(option, description = "set the speed: low, med, high")]
pub speed: Speed,
#[argh(
option,
default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)]
pub zone: AuraZone,
}
/// Laser (single-colour with speed) separate subcommand
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "laser",
description = "single-colour effect with speed"
)]
pub struct LaserSingleColourSpeed {
#[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
pub colour: Colour,
#[argh(option, description = "set the speed: low, med, high")]
pub speed: Speed,
#[argh(
option,
default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)]
pub zone: AuraZone,
}
/// Ripple (single-colour with speed) separate subcommand
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(
subcommand,
name = "ripple",
description = "single-colour effect with speed"
)]
pub struct RippleSingleColourSpeed {
#[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
pub colour: Colour,
#[argh(option, description = "set the speed: low, med, high")]
pub speed: Speed,
#[argh(
option,
default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)]
pub zone: AuraZone,
}
/// Pulse / Comet / Flash variants (single-colour) separate subcommands
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(subcommand, name = "pulse", description = "single-colour pulse effect")]
pub struct PulseSingleColour {
#[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
pub colour: Colour,
#[argh(
option,
default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)]
pub zone: AuraZone,
}
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(subcommand, name = "comet", description = "single-colour comet effect")]
pub struct CometSingleColour {
#[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
pub colour: Colour,
#[argh(
option,
default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)]
pub zone: AuraZone,
}
#[derive(FromArgs, Debug, Clone, Default)]
#[argh(subcommand, name = "flash", description = "single-colour flash effect")]
pub struct FlashSingleColour {
#[argh(option, short = 'c', description = "set the RGB value e.g. ff00ff")]
pub colour: Colour,
#[argh(
option,
default = "AuraZone::None",
description = "set the zone for this effect e.g. 0, 1, one, logo, lightbar-left"
)]
pub zone: AuraZone,
}
/// Multi-zone colour settings
#[derive(FromArgs, Debug, Clone, Default)]
#[allow(dead_code)]
@@ -359,14 +486,14 @@ pub enum SetAuraBuiltin {
Breathe(TwoColourSpeed), // 1
RainbowCycle(SingleSpeed), // 2
RainbowWave(SingleSpeedDirection), // 3
Stars(TwoColourSpeed), // 4
Rain(SingleSpeed), // 5
Stars(StarsTwoColour), // 4
Rain(RainSingleSpeed), // 5
Highlight(SingleColourSpeed), // 6
Laser(SingleColourSpeed), // 7
Ripple(SingleColourSpeed), // 8
Pulse(SingleColour), // 10
Comet(SingleColour), // 11
Flash(SingleColour), // 12
Laser(LaserSingleColourSpeed), // 7
Ripple(RippleSingleColourSpeed), // 8
Pulse(PulseSingleColour), // 10
Comet(CometSingleColour), // 11
Flash(FlashSingleColour), // 12
}
impl Default for SetAuraBuiltin {
@@ -428,6 +555,79 @@ impl From<&SingleSpeedDirection> for AuraEffect {
}
}
impl From<&StarsTwoColour> for AuraEffect {
fn from(aura: &StarsTwoColour) -> Self {
Self {
colour1: aura.colour,
colour2: aura.colour2,
zone: aura.zone,
..Default::default()
}
}
}
impl From<&RainSingleSpeed> for AuraEffect {
fn from(aura: &RainSingleSpeed) -> Self {
Self {
speed: aura.speed,
zone: aura.zone,
..Default::default()
}
}
}
impl From<&LaserSingleColourSpeed> for AuraEffect {
fn from(aura: &LaserSingleColourSpeed) -> Self {
Self {
colour1: aura.colour,
speed: aura.speed,
zone: aura.zone,
..Default::default()
}
}
}
impl From<&RippleSingleColourSpeed> for AuraEffect {
fn from(aura: &RippleSingleColourSpeed) -> Self {
Self {
colour1: aura.colour,
speed: aura.speed,
zone: aura.zone,
..Default::default()
}
}
}
impl From<&PulseSingleColour> for AuraEffect {
fn from(aura: &PulseSingleColour) -> Self {
Self {
colour1: aura.colour,
zone: aura.zone,
..Default::default()
}
}
}
impl From<&CometSingleColour> for AuraEffect {
fn from(aura: &CometSingleColour) -> Self {
Self {
colour1: aura.colour,
zone: aura.zone,
..Default::default()
}
}
}
impl From<&FlashSingleColour> for AuraEffect {
fn from(aura: &FlashSingleColour) -> Self {
Self {
colour1: aura.colour,
zone: aura.zone,
..Default::default()
}
}
}
impl From<&SetAuraBuiltin> for AuraEffect {
fn from(aura: &SetAuraBuiltin) -> Self {
match aura {
-1
View File
@@ -33,7 +33,6 @@ pub struct AuraConfig {
}
impl StdConfig for AuraConfig {
/// Detect the keyboard type and load from default DB if data available
fn new() -> Self {
panic!("This should not be used");
}
+71 -2
View File
@@ -25,6 +25,30 @@ pub struct Aura {
impl Aura {
/// Initialise the device if required.
pub async fn do_initialization(&self) -> Result<(), RogError> {
if let Some(hid) = &self.hid {
let hid = hid.lock().await;
let init_1: [u8; 2] = [
0x5d, 0xb9,
];
let init_2 = b"]ASUS Tech.Inc.";
let init_3: [u8; 6] = [
0x5d, 0x05, 0x20, 0x31, 0, 0x1a,
];
hid.write_bytes(&init_1)?;
hid.write_bytes(init_2)?;
hid.write_bytes(&init_3)?;
let config = self.config.lock().await;
if config.support_data.device_name.contains("GZ30")
|| config.support_data.device_name.contains("Z13")
{
let z13_init: [u8; 4] = [
0x5d, 0xc0, 0x03, 0x01,
];
hid.write_bytes(&z13_init)?;
}
}
Ok(())
}
@@ -152,9 +176,54 @@ impl Aura {
}
}
let bytes = config.enabled.to_bytes(config.led_type);
let mut enabled = config.enabled.clone();
if config.support_data.device_name.contains("GZ30")
|| config.support_data.device_name.contains("Z13")
{
let logo_state = enabled
.states
.iter()
.find(|s| s.zone == PowerZones::Logo)
.cloned();
if let Some(logo) = logo_state {
let mut lid_found = false;
let mut bar_found = false;
for s in enabled.states.iter_mut() {
if s.zone == PowerZones::Lid {
s.boot = logo.boot;
s.awake = logo.awake;
s.sleep = logo.sleep;
s.shutdown = logo.shutdown;
lid_found = true;
}
if s.zone == PowerZones::Lightbar {
s.boot = logo.boot;
s.awake = logo.awake;
s.sleep = logo.sleep;
s.shutdown = logo.shutdown;
bar_found = true;
}
}
if !lid_found {
let mut new_state = logo;
new_state.zone = PowerZones::Lid;
enabled.states.push(new_state.clone());
new_state.zone = PowerZones::Lightbar;
enabled.states.push(new_state);
} else if !bar_found {
// Lid found but not bar?
let mut new_state = logo;
new_state.zone = PowerZones::Lightbar;
enabled.states.push(new_state);
}
}
}
let bytes = enabled.to_bytes(config.led_type);
let msg = [
0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3],
0x5d, 0xbd, 0x01, bytes[0], bytes[1], bytes[2], bytes[3], 0xff,
];
hid_raw.write_bytes(&msg)?;
}
+4 -4
View File
@@ -182,7 +182,6 @@ impl AuraZbus {
self.0.set_brightness(config.brightness.into()).await?;
config.set_builtin(effect);
config.write();
Ok(())
}
@@ -208,9 +207,10 @@ impl AuraZbus {
let mut config = self.0.config.lock().await;
for opt in options.states {
let zone = opt.zone;
for config in config.enabled.states.iter_mut() {
if config.zone == zone {
*config = opt;
for state in config.enabled.states.iter_mut() {
if state.zone == zone {
*state = opt;
break;
}
}
}
+1
View File
@@ -196,6 +196,7 @@ impl DeviceHandle {
Some(Arc::new(Mutex::new(k)))
});
// Load saved mode, colours, brightness, power from disk; apply on reload
let mut config = AuraConfig::load_and_update_config(prod_id);
config.led_type = aura_type;
let aura = Aura {
+28 -10
View File
@@ -125,7 +125,7 @@
advanced_type: r#None,
power_zones: [Keyboard],
),
(
(
device_name: "FX607V",
product_id: "",
layout_name: "fa506i",
@@ -351,13 +351,13 @@
power_zones: [Keyboard, Lightbar, Logo, RearGlow],
),
(
device_name: "G635L",
product_id: "",
layout_name: "g635l-per-key",
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Star, Rain, Highlight, Laser, Ripple, Pulse, Comet, Flash],
basic_zones: [],
advanced_type: PerKey,
power_zones: [Keyboard, Lightbar, Logo],
device_name: "G635L",
product_id: "",
layout_name: "g635l-per-key",
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Star, Rain, Highlight, Laser, Ripple, Pulse, Comet, Flash],
basic_zones: [],
advanced_type: PerKey,
power_zones: [Keyboard, Lightbar, Logo],
),
(
device_name: "G712LI",
@@ -579,7 +579,7 @@
device_name: "G835L",
product_id: "",
layout_name: "g814ji-per-key",
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Star, Rain, Highlight, Laser, Ripple, Comet, Flash],
basic_modes: [Static, Breathe, RainbowCycle, RainbowWave, Star, Rain, Highlight, Laser, Ripple, Pulse, Comet, Flash],
basic_zones: [],
advanced_type: PerKey,
power_zones: [Keyboard, Lightbar, Logo],
@@ -980,6 +980,24 @@
advanced_type: r#None,
power_zones: [Keyboard],
),
(
device_name: "GZ302",
product_id: "18c6",
layout_name: "",
basic_modes: [Static, Breathe, Pulse],
basic_zones: [],
advanced_type: r#None,
power_zones: [Logo],
),
(
device_name: "GZ302",
product_id: "1a30",
layout_name: "ga401q",
basic_modes: [Static, Breathe, Pulse],
basic_zones: [],
advanced_type: r#None,
power_zones: [Keyboard],
),
(
device_name: "RC71L",
product_id: "",
@@ -998,4 +1016,4 @@
advanced_type: r#None,
power_zones: [Ally],
),
])
])
+20 -11
View File
@@ -123,7 +123,8 @@ impl AuraPowerState {
| ((self.shutdown as u32) << 7)
}
PowerZones::Lightbar => {
((self.boot as u32) << (7 + 2))
((self.awake as u32) << (7 + 1))
| ((self.boot as u32) << (7 + 2))
| ((self.awake as u32) << (7 + 3))
| ((self.sleep as u32) << (7 + 4))
| ((self.shutdown as u32) << (7 + 5))
@@ -133,12 +134,20 @@ impl AuraPowerState {
| ((self.awake as u32) << (15 + 2))
| ((self.sleep as u32) << (15 + 3))
| ((self.shutdown as u32) << (15 + 4))
| ((self.boot as u32) << (15 + 5))
| ((self.awake as u32) << (15 + 6))
| ((self.sleep as u32) << (15 + 7))
| ((self.shutdown as u32) << (15 + 8))
}
PowerZones::RearGlow => {
((self.boot as u32) << (23 + 1))
| ((self.awake as u32) << (23 + 2))
| ((self.sleep as u32) << (23 + 3))
| ((self.shutdown as u32) << (23 + 4))
| ((self.boot as u32) << (23 + 5))
| ((self.awake as u32) << (23 + 6))
| ((self.sleep as u32) << (23 + 7))
| ((self.shutdown as u32) << (23 + 8))
}
PowerZones::None | PowerZones::KeyboardAndLightbar => 0,
}
@@ -618,19 +627,19 @@ mod test {
assert_eq!(shut_keyb_, "10000000, 00000000, 00000000, 00000000");
//
assert_eq!(boot_bar__, "00000000, 00000010, 00000000, 00000000");
assert_eq!(awake_bar_, "00000000, 00000100, 00000000, 00000000");
assert_eq!(awake_bar_, "00000000, 00000101, 00000000, 00000000");
assert_eq!(sleep_bar_, "00000000, 00001000, 00000000, 00000000");
assert_eq!(shut_bar__, "00000000, 00010000, 00000000, 00000000");
//
assert_eq!(boot_lid__, "00000000, 00000000, 00000001, 00000000");
assert_eq!(awake_lid_, "00000000, 00000000, 00000010, 00000000");
assert_eq!(sleep_lid_, "00000000, 00000000, 00000100, 00000000");
assert_eq!(shut_lid__, "00000000, 00000000, 00001000, 00000000");
assert_eq!(boot_lid__, "00000000, 00000000, 00010001, 00000000");
assert_eq!(awake_lid_, "00000000, 00000000, 00100010, 00000000");
assert_eq!(sleep_lid_, "00000000, 00000000, 01000100, 00000000");
assert_eq!(shut_lid__, "00000000, 00000000, 10001000, 00000000");
//
assert_eq!(boot_rear_, "00000000, 00000000, 00000000, 00000001");
assert_eq!(awake_rear, "00000000, 00000000, 00000000, 00000010");
assert_eq!(sleep_rear, "00000000, 00000000, 00000000, 00000100");
assert_eq!(shut_rear_, "00000000, 00000000, 00000000, 00001000");
assert_eq!(boot_rear_, "00000000, 00000000, 00000000, 00010001");
assert_eq!(awake_rear, "00000000, 00000000, 00000000, 00100010");
assert_eq!(sleep_rear, "00000000, 00000000, 00000000, 01000100");
assert_eq!(shut_rear_, "00000000, 00000000, 00000000, 10001000");
// All on
let byte1 = to_binary_string_post2021(&LaptopAuraPower {
@@ -657,6 +666,6 @@ mod test {
},
],
});
assert_eq!(byte1, "11111111, 00011110, 00001111, 00001111");
assert_eq!(byte1, "11111111, 00011111, 11111111, 11111111");
}
}
+2 -2
View File
@@ -106,10 +106,10 @@ impl From<&str> for AuraDeviceType {
match s.to_lowercase().trim_start_matches("0x") {
"tuf" => AuraDeviceType::LaptopKeyboardTuf,
"1932" => AuraDeviceType::ScsiExtDisk,
"1866" | "18c6" | "1869" | "1854" => Self::LaptopKeyboardPre2021,
"1866" | "1869" | "1854" => Self::LaptopKeyboardPre2021,
"1abe" | "1b4c" => Self::Ally,
"19b3" | "193b" => Self::AnimeOrSlash,
"19b6" => Self::LaptopKeyboard2021,
"19b6" | "1a30" | "18c6" => Self::LaptopKeyboard2021,
_ => Self::Unknown,
}
}
+11 -2
View File
@@ -187,12 +187,18 @@ async fn main() -> Result<()> {
slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/"));
}
// Prefetch supported Aura modes once at startup and move into the
// spawned UI thread so the UI uses a stable, immutable list.
let prefetched_supported: std::sync::Arc<Option<Vec<i32>>> = std::sync::Arc::new(
rog_control_center::ui::setup_aura::prefetch_supported_basic_modes().await,
);
thread::spawn(move || {
let mut state = AppState::StartingUp;
loop {
if is_rog_ally {
let config_copy_2 = config.clone();
let newui = setup_window(config.clone());
let newui = setup_window(config.clone(), prefetched_supported.clone());
newui.window().on_close_requested(move || {
exit(0);
});
@@ -233,6 +239,9 @@ async fn main() -> Result<()> {
let config_copy = config.clone();
let app_state_copy = app_state.clone();
// Avoid moving the original `prefetched_supported` into the
// closure — clone an Arc for the closure to capture.
let pref_for_invoke = prefetched_supported.clone();
slint::invoke_from_event_loop(move || {
UI.with(|ui| {
let app_state_copy = app_state_copy.clone();
@@ -247,7 +256,7 @@ async fn main() -> Result<()> {
});
} else {
let config_copy_2 = config_copy.clone();
let newui = setup_window(config_copy);
let newui = setup_window(config_copy, pref_for_invoke.clone());
newui.window().on_close_requested(move || {
if let Ok(mut app_state) = app_state_copy.lock() {
*app_state = AppState::MainWindowClosed;
+45 -8
View File
@@ -3,7 +3,11 @@ pub mod setup_aura;
pub mod setup_fans;
pub mod setup_system;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
static TOAST_SEQ: AtomicU64 = AtomicU64::new(0);
use config_traits::StdConfig;
use log::warn;
@@ -70,19 +74,52 @@ pub fn show_toast(
handle: Weak<MainWindow>,
result: zbus::Result<()>,
) {
// bump sequence so that any previously spawned timers won't clear newer toasts
let seq = TOAST_SEQ.fetch_add(1, Ordering::SeqCst) + 1;
match result {
Ok(_) => {
slint::invoke_from_event_loop(move || handle.unwrap().invoke_show_toast(success)).ok()
let delayed_handle = handle.clone();
let delayed_text = success.clone();
slint::invoke_from_event_loop(move || handle.unwrap().invoke_show_toast(success)).ok();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
if TOAST_SEQ.load(Ordering::SeqCst) == seq {
slint::invoke_from_event_loop(move || {
delayed_handle
.unwrap()
.invoke_clear_toast_if_matches(delayed_text)
})
.ok();
}
});
}
Err(e) => {
let delayed_handle = handle.clone();
let delayed_text = fail.clone();
slint::invoke_from_event_loop(move || {
log::warn!("{fail}: {e}");
handle.unwrap().invoke_show_toast(fail)
})
.ok();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_secs(5)).await;
if TOAST_SEQ.load(Ordering::SeqCst) == seq {
slint::invoke_from_event_loop(move || {
delayed_handle
.unwrap()
.invoke_clear_toast_if_matches(delayed_text)
})
.ok();
}
});
}
Err(e) => slint::invoke_from_event_loop(move || {
log::warn!("{fail}: {e}");
handle.unwrap().invoke_show_toast(fail)
})
.ok(),
};
}
pub fn setup_window(config: Arc<Mutex<Config>>) -> MainWindow {
pub fn setup_window(
config: Arc<Mutex<Config>>,
prefetched_supported: std::sync::Arc<Option<Vec<i32>>>,
) -> MainWindow {
slint::set_xdg_app_id("rog-control-center")
.map_err(|e| warn!("Couldn't set application ID: {e:?}"))
.ok();
@@ -119,7 +156,7 @@ pub fn setup_window(config: Arc<Mutex<Config>>) -> MainWindow {
setup_system_page_callbacks(&ui, config.clone());
}
if available.contains(&"xyz.ljones.Aura".to_string()) {
setup_aura_page(&ui, config.clone());
setup_aura_page(&ui, config.clone(), prefetched_supported.as_ref().clone());
}
if available.contains(&"xyz.ljones.Anime".to_string()) {
setup_anime_page(&ui, config.clone());
+140 -132
View File
@@ -34,63 +34,103 @@ fn decode_hex(s: &str) -> RgbaColor<u8> {
}
}
/// Returns the first available Aura interface
// TODO: return all
async fn find_aura_iface() -> Result<AuraProxy<'static>, Box<dyn std::error::Error>> {
let conn = zbus::Connection::system().await?;
let f = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?;
let interfaces = f.get_managed_objects().await?;
let mut aura_paths = Vec::new();
for v in interfaces.iter() {
for k in v.1.keys() {
if k.as_str() == "xyz.ljones.Aura" {
println!("Found aura device at {}, {}", v.0, k);
aura_paths.push(v.0.clone());
}
}
let mgr = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?;
let objs = mgr.get_managed_objects().await?;
let mut paths: Vec<zbus::zvariant::OwnedObjectPath> = objs
.iter()
.filter(|(_, ifaces)| ifaces.keys().any(|k| k.as_str() == "xyz.ljones.Aura"))
.map(|(p, _)| p.clone())
.collect();
if paths.len() > 1 {
log::debug!("Multiple aura devices: {paths:?}");
}
if aura_paths.len() > 1 {
println!("Multiple aura devices found: {aura_paths:?}");
println!("TODO: enable selection");
}
if let Some(path) = aura_paths.first() {
return Ok(AuraProxy::builder(&conn)
.path(path.clone())?
.destination("xyz.ljones.Asusd")?
.build()
.await?);
}
Err("No Aura interface".into())
let path = paths.pop().ok_or("No Aura interface")?;
AuraProxy::builder(&conn)
.path(path)?
.destination("xyz.ljones.Asusd")?
.build()
.await
.map_err(Into::into)
}
pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
ui.global::<AuraPageData>().on_cb_hex_from_colour(|c| {
pub async fn prefetch_supported_basic_modes() -> Option<Vec<i32>> {
let proxy = find_aura_iface().await.ok()?;
let modes = proxy.supported_basic_modes().await.ok()?;
Some(modes.iter().map(|n| (*n).into()).collect())
}
pub fn setup_aura_page(
ui: &MainWindow,
_states: Arc<Mutex<Config>>,
prefetched_supported: Option<Vec<i32>>,
) {
let g = ui.global::<AuraPageData>();
g.on_cb_hex_from_colour(|c| {
format!("#{:02X}{:02X}{:02X}", c.red(), c.green(), c.blue()).into()
});
ui.global::<AuraPageData>()
.on_cb_hex_to_colour(|s| decode_hex(s.as_str()).into());
g.on_cb_hex_to_colour(|s| decode_hex(s.as_str()).into());
let handle = ui.as_weak();
tokio::spawn(async move {
let Ok(aura) = find_aura_iface().await else {
info!("This device appears to have no aura interfaces");
info!("No aura interfaces");
return Ok::<(), zbus::Error>(());
};
set_ui_props_async!(handle, aura, AuraPageData, brightness);
set_ui_props_async!(handle, aura, AuraPageData, led_mode);
set_ui_props_async!(handle, aura, AuraPageData, led_mode_data);
set_ui_props_async!(handle, aura, AuraPageData, led_power);
set_ui_props_async!(handle, aura, AuraPageData, device_type);
let modes_vec: Vec<i32> = match prefetched_supported {
Some(p) => p,
None => aura
.supported_basic_modes()
.await
.ok()
.map(|m| m.iter().map(|n| (*n).into()).collect())
.unwrap_or_default(),
};
// Restore saved mode, colours, zone, speed, direction from asusd (persisted to disk).
// Use effect.mode as single source — avoid led_mode() which can fail (try_lock).
let restore = aura.led_mode_data().await.ok();
let raw_mode: Option<i32> = restore.as_ref().map(|d| d.mode.into());
let d_slint = restore.map(|d| d.into());
handle
.upgrade_in_event_loop(move |h| {
let names = h.global::<AuraPageData>().get_mode_names();
let mut raws = Vec::new();
let mut mode_names = Vec::new();
for (i, name) in names.iter().enumerate() {
let raw = i as i32;
if modes_vec.contains(&raw) && i != 9 {
raws.push(raw);
mode_names.push(name.clone());
}
}
h.global::<AuraPageData>()
.set_supported_basic_modes(raws.as_slice().into());
h.global::<AuraPageData>()
.set_available_mode_names(mode_names.as_slice().into());
if let Some(d) = d_slint {
h.global::<AuraPageData>().invoke_update_led_mode_data(d);
if let Some(cm) = raw_mode {
let idx = raws.iter().position(|&r| r == cm).unwrap_or(0) as i32;
h.global::<AuraPageData>().set_current_available_mode(idx);
}
h.invoke_external_colour_change();
}
})
.map_err(|e| error!("{e}"))
.ok();
if let Ok(mut pow3r) = aura.supported_power_zones().await {
let dev_type = aura
let dev = aura
.device_type()
.await
.unwrap_or(AuraDeviceType::LaptopKeyboard2021);
log::debug!("Available LED power modes {pow3r:?}");
handle
.upgrade_in_event_loop(move |handle| {
let names: Vec<SharedString> = handle
@@ -98,135 +138,103 @@ pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
.get_power_zone_names()
.iter()
.collect();
if dev_type.is_old_laptop() {
// Need to add the specific KeyboardAndLightbar
if dev.is_old_laptop() {
if pow3r.contains(&PowerZones::Keyboard)
&& pow3r.contains(&PowerZones::Lightbar)
{
pow3r.push(PowerZones::KeyboardAndLightbar);
}
let names: Vec<SharedString> =
pow3r.iter().map(|n| names[(*n) as usize].clone()).collect();
let n: Vec<SharedString> =
pow3r.iter().map(|z| names[(*z) as usize].clone()).collect();
handle
.global::<AuraPageData>()
.set_power_zone_names_old(names.as_slice().into());
.set_power_zone_names_old(n.as_slice().into());
} else {
let power: Vec<SlintPowerZones> =
pow3r.iter().map(|p| (*p).into()).collect();
let p: Vec<SlintPowerZones> = pow3r.iter().map(|z| (*z).into()).collect();
handle
.global::<AuraPageData>()
.set_supported_power_zones(power.as_slice().into());
.set_supported_power_zones(p.as_slice().into());
}
})
.ok();
}
if let Ok(modes) = aura.supported_basic_modes().await {
log::debug!("Available LED modes {modes:?}");
handle
.upgrade_in_event_loop(move |handle| {
let m: Vec<i32> = modes.iter().map(|n| (*n).into()).collect();
handle
.global::<AuraPageData>()
.set_supported_basic_modes(m.as_slice().into());
// Get the translated names
let names = handle.global::<AuraPageData>().get_mode_names();
let res: Vec<SharedString> = names
.iter()
.enumerate()
.filter(|(n, _)| modes.contains(&(*n as i32).into()) && *n != 9)
.map(|(_, i)| i)
.collect();
handle
.global::<AuraPageData>()
.set_available_mode_names(res.as_slice().into());
})
.map_err(|e| error!("{e:}"))
.ok();
}
let proxy_copy = aura.clone();
let proxy = aura.clone();
let weak = handle.clone();
handle
.upgrade_in_event_loop(move |handle| {
set_ui_callbacks!(handle,
.upgrade_in_event_loop(move |h| {
set_ui_callbacks!(h,
AuraPageData(.into()),
proxy_copy.brightness(.into()),
"Keyboard LED brightness successfully set to {}",
"Setting keyboard LED brightness failed"
proxy.brightness(.into()),
"Brightness set to {}",
"Brightness failed"
);
set_ui_callbacks!(handle,
AuraPageData(.into()),
proxy_copy.led_mode(.into()),
"Keyboard LED mode successfully set to {}",
"Setting keyboard LEDmode failed"
);
set_ui_callbacks!(handle,
AuraPageData(.into()),
proxy_copy.led_mode_data(.into()),
"Keyboard LED mode set to {:?}",
"Setting keyboard LED mode failed"
);
// set_ui_callbacks!(handle,
// AuraPageData(.clone().into()),
// proxy_copy.led_power(.into()),
// "Keyboard LED power successfully set to {:?}",
// "Setting keyboard power failed"
// );
handle.invoke_external_colour_change();
})
.ok();
let handle_copy = handle.clone();
let proxy_copy = aura.clone();
handle
.upgrade_in_event_loop(|handle| {
handle
.global::<AuraPageData>()
.on_cb_led_power(move |power| {
let handle_copy = handle_copy.clone();
let proxy_copy = aura.clone();
let power: LaptopAuraPower = power.into();
tokio::spawn(async move {
show_toast(
"Aura power settings changed".into(),
"Failed to set Aura power settings".into(),
handle_copy,
proxy_copy.set_led_power(power).await,
);
});
let p = proxy.clone();
let w = weak.clone();
h.global::<AuraPageData>().on_apply_led_mode_data(move || {
let Some(ui) = w.upgrade() else { return };
let slint_effect = ui.global::<AuraPageData>().get_led_mode_data();
let raw: rog_aura::AuraEffect = slint_effect.into();
let pp = p.clone();
let t = w.clone();
tokio::spawn(async move {
let r = pp.set_led_mode_data(raw).await;
show_toast("LED mode applied".into(), "LED mode failed".into(), t, r);
});
});
h.invoke_external_colour_change();
})
.map_err(|e| error!("{e:}"))
.ok();
// Need to update the UI if the mode changes
let handle_copy = handle.clone();
// spawn required since the while let never exits
let weak_power = handle.clone();
let proxy_power = aura.clone();
handle
.upgrade_in_event_loop(|h| {
h.global::<AuraPageData>().on_cb_led_power(move |power| {
let w = weak_power.clone();
let p = proxy_power.clone();
let pw: LaptopAuraPower = power.into();
tokio::spawn(async move {
show_toast(
"Aura power updated".into(),
"Aura power failed".into(),
w,
p.set_led_power(pw).await,
);
});
});
})
.map_err(|e| error!("{e}"))
.ok();
let stream_handle = handle.clone();
let aura_stream = aura.clone();
tokio::spawn(async move {
let mut x = proxy_copy.receive_led_mode_data_changed().await;
use futures_util::StreamExt;
while let Some(e) = x.next().await {
let mut stream = aura_stream.receive_led_mode_data_changed().await;
while let Some(e) = stream.next().await {
if let Ok(out) = e.get().await {
handle_copy
.upgrade_in_event_loop(move |handle| {
handle
let raw: i32 = out.mode.into();
let data = out.into();
stream_handle
.upgrade_in_event_loop(move |h| {
h.global::<AuraPageData>().invoke_update_led_mode_data(data);
let supported: Vec<i32> = h
.global::<AuraPageData>()
.invoke_update_led_mode_data(out.into());
handle.invoke_external_colour_change();
.get_supported_basic_modes()
.iter()
.collect();
let idx = supported.iter().position(|&x| x == raw).unwrap_or(0) as i32;
h.global::<AuraPageData>().set_current_available_mode(idx);
h.invoke_external_colour_change();
})
.map_err(|e| error!("{e:}"))
.map_err(|e| error!("{e}"))
.ok();
}
}
});
debug!("Aura setup tasks complete");
debug!("Aura setup done");
Ok(())
});
}
+7
View File
@@ -43,6 +43,13 @@ export component MainWindow inherits Window {
toast = text != "";
toast_text = text;
}
callback clear_toast_if_matches(string);
clear_toast_if_matches(text) => {
if (toast && toast_text == text) {
toast = false;
toast_text = "";
}
}
callback exit-app();
callback show_notification(bool);
show_notification(yes) => {
+61 -60
View File
@@ -42,10 +42,14 @@ export component PageAura inherits Rectangle {
current_value: AuraPageData.available_mode_names[self.current-index];
model <=> AuraPageData.available_mode_names;
selected => {
AuraPageData.led_mode_data.mode = AuraPageData.led_mode;
AuraPageData.led_mode_data.mode = AuraPageData.current_available_mode;
self.current_value = AuraPageData.available_mode_names[self.current-index];
AuraPageData.cb_led_mode(AuraPageData.current_available_mode);
AuraPageData.apply_effect({
mode: AuraPageData.supported_basic_modes[self.current-index],
zone: AuraPageData.led_mode_data.zone,
colour1: AuraPageData.led_mode_data.colour1,
colour2: AuraPageData.led_mode_data.colour2,
speed: AuraPageData.led_mode_data.speed,
direction: AuraPageData.led_mode_data.direction,
});
}
}
}
@@ -62,47 +66,44 @@ export component PageAura inherits Rectangle {
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
HorizontalBox {
c1 := ColourSlider {
enabled: AuraPageData.led_mode == 0 || AuraPageData.led_mode == 1 || AuraPageData.led_mode == 4 || AuraPageData.led_mode == 6 || AuraPageData.led_mode == 7 || AuraPageData.led_mode == 8 || AuraPageData.led_mode == 10 || AuraPageData.led_mode == 11 || AuraPageData.led_mode == 12;
enabled: AuraPageData.colour1_enabled;
final_colour <=> AuraPageData.color1;
colourbox <=> AuraPageData.colorbox1;
set_hex_from_colour(c1) => {
return AuraPageData.cb_hex_from_colour(c1);
}
hex_to_colour(s) => {
return AuraPageData.cb_hex_to_colour(s);
}
set_hex_from_colour(c) => { return AuraPageData.cb_hex_from_colour(c); }
hex_to_colour(s) => { return AuraPageData.cb_hex_to_colour(s); }
released => {
AuraPageData.led_mode_data.colour1 = AuraPageData.color1;
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data);
AuraPageData.apply_effect({
mode: AuraPageData.led_mode_data.mode,
zone: AuraPageData.led_mode_data.zone,
colour1: AuraPageData.color1,
colour2: AuraPageData.led_mode_data.colour2,
speed: AuraPageData.led_mode_data.speed,
direction: AuraPageData.led_mode_data.direction,
});
}
}
}
}
VerticalBox {
Text {
text: @tr("Colour 2");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
Text { text: @tr("Colour 2"); vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center; }
HorizontalBox {
c2 := ColourSlider {
enabled: AuraPageData.led_mode == 1 || AuraPageData.led_mode == 4;
enabled: AuraPageData.colour2_enabled;
final_colour <=> AuraPageData.color2;
colourbox <=> AuraPageData.colorbox2;
set_hex_from_colour(c1) => {
return AuraPageData.cb_hex_from_colour(c1);
}
hex_to_colour(s) => {
return AuraPageData.cb_hex_to_colour(s);
}
set_hex_from_colour(c) => { return AuraPageData.cb_hex_from_colour(c); }
hex_to_colour(s) => { return AuraPageData.cb_hex_to_colour(s); }
released => {
AuraPageData.led_mode_data.colour2 = AuraPageData.color2;
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data);
AuraPageData.apply_effect({
mode: AuraPageData.led_mode_data.mode,
zone: AuraPageData.led_mode_data.zone,
colour1: AuraPageData.led_mode_data.colour1,
colour2: AuraPageData.color2,
speed: AuraPageData.led_mode_data.speed,
direction: AuraPageData.led_mode_data.direction,
});
}
}
}
@@ -116,63 +117,63 @@ export component PageAura inherits Rectangle {
max-height: 90px;
RogItem {
VerticalBox {
Text {
text: @tr("Zone");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
Text { text: @tr("Zone"); vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center; }
ComboBox {
// enabled: AuraPageData.led_mode == ;
enabled: false;
enabled: false;
current_index <=> AuraPageData.zone;
current_value: AuraPageData.zone_names[self.current-index];
model <=> AuraPageData.zone_names;
selected => {
AuraPageData.led_mode_data.zone = self.current-index;
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data);
AuraPageData.apply_effect({
mode: AuraPageData.led_mode_data.mode,
zone: self.current-index,
colour1: AuraPageData.led_mode_data.colour1,
colour2: AuraPageData.led_mode_data.colour2,
speed: AuraPageData.led_mode_data.speed,
direction: AuraPageData.led_mode_data.direction,
});
}
}
}
}
RogItem {
VerticalBox {
Text {
text: @tr("Direction");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
Text { text: @tr("Direction"); vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center; }
ComboBox {
enabled: AuraPageData.led_mode == 3;
enabled: AuraPageData.direction_enabled;
current_index <=> AuraPageData.direction;
current_value: AuraPageData.direction_names[self.current-index];
model <=> AuraPageData.direction_names;
selected => {
AuraPageData.led_mode_data.direction = self.current-index;
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data);
AuraPageData.apply_effect({
mode: AuraPageData.led_mode_data.mode,
zone: AuraPageData.led_mode_data.zone,
colour1: AuraPageData.led_mode_data.colour1,
colour2: AuraPageData.led_mode_data.colour2,
speed: AuraPageData.led_mode_data.speed,
direction: self.current-index,
});
}
}
}
}
RogItem {
VerticalBox {
Text {
text: @tr("Speed");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
Text { text: @tr("Speed"); vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center; }
ComboBox {
enabled: AuraPageData.led_mode == 1 || AuraPageData.led_mode == 2 || AuraPageData.led_mode == 3 || AuraPageData.led_mode == 4 || AuraPageData.led_mode == 5 || AuraPageData.led_mode == 6 || AuraPageData.led_mode == 7 || AuraPageData.led_mode == 8;
enabled: AuraPageData.speed_enabled;
current_index <=> AuraPageData.speed;
current_value: AuraPageData.speed_names[self.current-index];
model <=> AuraPageData.speed_names;
selected => {
AuraPageData.led_mode_data.speed = self.current-index;
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data);
AuraPageData.apply_effect({
mode: AuraPageData.led_mode_data.mode,
zone: AuraPageData.led_mode_data.zone,
colour1: AuraPageData.led_mode_data.colour1,
colour2: AuraPageData.led_mode_data.colour2,
speed: self.current-index,
direction: AuraPageData.led_mode_data.direction,
});
}
}
}
+27 -25
View File
@@ -46,8 +46,10 @@ export struct LaptopAuraPower {
states: [AuraPowerState],
}
// Modes with colour1: Static,Breathe,Star,Rain,Highlight,Laser,Ripple,Pulse,Comet,Flash (excl. Strobe,Rainbow,Nothing)
// Modes with colour2: Breathe, Star only.
// Speed: Breathe,Strobe,Rainbow,Star,Rain,Highlight,Laser,Ripple. Direction: Rainbow only.
export global AuraPageData {
// The ordering must match the rog-aura crate
in-out property <[string]> power_zone_names: [
@tr("Aura power zone" => "Logo"),
@tr("Aura power zone" => "Keyboard"),
@@ -87,15 +89,9 @@ export global AuraPageData {
@tr("Basic aura mode" => "Comet"),
@tr("Basic aura mode" => "Flash"),
];
in-out property <[string]> available_mode_names: [
@tr("Basic aura mode" => "Static"),
@tr("Basic aura mode" => "Breathe"),
@tr("Basic aura mode" => "Strobe"),
];
in-out property <[string]> available_mode_names: [ @tr("Basic aura mode" => "Static"), @tr("Basic aura mode" => "Breathe"), @tr("Basic aura mode" => "Strobe") ];
in-out property <int> current_available_mode: 0;
in-out property <[int]> supported_basic_modes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12];
in-out property <int> led_mode;
callback cb_led_mode(int);
in-out property <[string]> zone_names: [
@tr("Aura zone" => "None"),
@tr("Aura zone" => "Key1"),
@@ -106,51 +102,57 @@ export global AuraPageData {
@tr("Aura zone" => "Lightbar Left"),
@tr("Aura zone" => "Lightbar Right"),
];
in-out property <int> zone;
in-out property <[string]> direction_names: [
@tr("Aura direction" => "Right"),
@tr("Aura direction" => "Left"),
@tr("Aura direction" => "Up"),
@tr("Aura direction" => "Down"),
];
in-out property <int> direction;
in-out property <[string]> speed_names: [
@tr("Aura speed" => "Low"),
@tr("Aura speed" => "Medium"),
@tr("Aura speed" => "High"),
];
in-out property <int> speed;
in-out property <AuraEffect> led_mode_data: {
mode: 0,
zone: 0,
colour1: Colors.aquamarine,
colourbox1: Colors.aquamarine,
colour2: Colors.hotpink,
colourbox2: Colors.hotpink,
speed: 0,
direction: 0,
};
callback cb_led_mode_data(AuraEffect);
in-out property <color> color1;
in-out property <brush> colorbox1;
in-out property <color> color2;
in-out property <brush> colorbox2;
out property <bool> colour1_enabled: led_mode_data.mode == 0 || led_mode_data.mode == 1 || led_mode_data.mode == 4 || led_mode_data.mode == 6 || led_mode_data.mode == 7 || led_mode_data.mode == 8 || led_mode_data.mode == 10 || led_mode_data.mode == 11 || led_mode_data.mode == 12;
out property <bool> colour2_enabled: led_mode_data.mode == 1 || led_mode_data.mode == 4;
out property <bool> speed_enabled: led_mode_data.mode == 1 || led_mode_data.mode == 2 || led_mode_data.mode == 3 || led_mode_data.mode == 4 || led_mode_data.mode == 5 || led_mode_data.mode == 6 || led_mode_data.mode == 7 || led_mode_data.mode == 8;
out property <bool> direction_enabled: led_mode_data.mode == 3;
callback apply_led_mode_data();
callback apply_effect(AuraEffect);
apply_effect(e) => { led_mode_data = e; apply_led_mode_data(); }
in-out property <int> zone;
in-out property <int> speed;
in-out property <int> direction;
callback update_led_mode_data(AuraEffect);
update_led_mode_data(data) => {
led_mode_data = data;
current_available_mode = data.mode;
zone = data.zone;
speed = data.speed;
direction = data.direction;
color1 = data.colour1;
color2 = data.colour2;
colorbox1 = data.colour1;
colorbox2 = data.colour2;
update_led_mode_data(d) => {
led_mode_data = d;
zone = d.zone;
speed = d.speed;
direction = d.direction;
color1 = d.colour1;
color2 = d.colour2;
colorbox1 = d.colour1;
colorbox2 = d.colour2;
}
callback cb_hex_from_colour(color) -> string;
callback cb_hex_to_colour(string) -> color;
in-out property <AuraDevType> device_type: AuraDevType.Old;
// List of indexes to power_zone_names. Must correspond to rog-aura crate
in-out property <[PowerZones]> supported_power_zones: [
PowerZones.Keyboard,
PowerZones.Lightbar,
+13 -4
View File
@@ -162,7 +162,6 @@ impl CurveData {
/// Write this curve to the device fan specified by `self.fan`
pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> {
let pwm_num: char = self.fan.into();
let enable = if self.enabled { '1' } else { '2' };
for (index, out) in self.pwm.iter().enumerate() {
let pwm = pwm_str(pwm_num, index);
@@ -176,10 +175,20 @@ impl CurveData {
device.set_attribute_value(&temp, out.to_string())?;
}
// Enable must be done *after* all points are written pwm3_enable
// Note: pwm_enable is set by write_profile_curve_to_platform after all
// curves are written, because on some devices (e.g., ASUS Z13 2025)
// setting any pwm_enable to 2 resets ALL fan enables.
Ok(())
}
/// Set the enable state for this fan curve
pub fn set_enable(&self, device: &mut Device) -> std::io::Result<()> {
let pwm_num: char = self.fan.into();
let enable = if self.enabled { "1" } else { "2" };
let enable_attr = format!("pwm{pwm_num}_enable");
device
.set_attribute_value(format!("pwm{pwm_num}_enable"), enable.to_string())
.map_err(|e| error!("Failed to set pwm{pwm_num}_enable to {enable}: {e:?}"))
.set_attribute_value(&enable_attr, enable.to_string())
.map_err(|e| error!("Failed to set {enable_attr} to {enable}: {e:?}"))
.ok();
Ok(())
}
+20 -6
View File
@@ -181,15 +181,29 @@ impl FanCurveProfiles {
PlatformProfile::Quiet | PlatformProfile::LowPower => &mut self.quiet,
PlatformProfile::Custom => &mut self.custom,
};
for fan in fans.iter().filter(|f| !f.enabled) {
debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}");
// First write all curve data (pwm/temp values) for all fans
for fan in fans.iter() {
debug!("write_profile_curve_to_platform: writing curve data for profile:{profile}, {fan:?}");
fan.write_to_device(device)?;
}
// Write enabled fans last because the kernel currently resets *all* if one is
// disabled
// Then set enables: disabled fans first, then enabled fans last.
// This order is important because on some devices (e.g., ASUS Z13 2025)
// setting any pwm_enable to 2 (disabled) resets ALL fan enables.
for fan in fans.iter().filter(|f| !f.enabled) {
debug!(
"write_profile_curve_to_platform: disabling fan for profile:{profile}, {:?}",
fan.fan
);
fan.set_enable(device)?;
}
for fan in fans.iter().filter(|f| f.enabled) {
debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}");
fan.write_to_device(device)?;
debug!(
"write_profile_curve_to_platform: enabling fan for profile:{profile}, {:?}",
fan.fan
);
fan.set_enable(device)?;
}
Ok(())
}