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 - Improve firmware attributes handling
- Very good looking UI restyling from @shevchenko0013 - Very good looking UI restyling from @shevchenko0013
- Added support for GA402NV matrix: thanks @Ghoul4500 - Added support for GA402NV matrix: thanks @Ghoul4500
- Fixed aura CLI interface
- Fixed LEDs handling in rogcc
## [6.3.1] ## [6.3.1]
+207 -7
View File
@@ -312,6 +312,133 @@ pub struct TwoColourSpeed {
pub zone: AuraZone, 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 /// Multi-zone colour settings
#[derive(FromArgs, Debug, Clone, Default)] #[derive(FromArgs, Debug, Clone, Default)]
#[allow(dead_code)] #[allow(dead_code)]
@@ -359,14 +486,14 @@ pub enum SetAuraBuiltin {
Breathe(TwoColourSpeed), // 1 Breathe(TwoColourSpeed), // 1
RainbowCycle(SingleSpeed), // 2 RainbowCycle(SingleSpeed), // 2
RainbowWave(SingleSpeedDirection), // 3 RainbowWave(SingleSpeedDirection), // 3
Stars(TwoColourSpeed), // 4 Stars(StarsTwoColour), // 4
Rain(SingleSpeed), // 5 Rain(RainSingleSpeed), // 5
Highlight(SingleColourSpeed), // 6 Highlight(SingleColourSpeed), // 6
Laser(SingleColourSpeed), // 7 Laser(LaserSingleColourSpeed), // 7
Ripple(SingleColourSpeed), // 8 Ripple(RippleSingleColourSpeed), // 8
Pulse(SingleColour), // 10 Pulse(PulseSingleColour), // 10
Comet(SingleColour), // 11 Comet(CometSingleColour), // 11
Flash(SingleColour), // 12 Flash(FlashSingleColour), // 12
} }
impl Default for SetAuraBuiltin { 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 { impl From<&SetAuraBuiltin> for AuraEffect {
fn from(aura: &SetAuraBuiltin) -> Self { fn from(aura: &SetAuraBuiltin) -> Self {
match aura { match aura {
-1
View File
@@ -33,7 +33,6 @@ pub struct AuraConfig {
} }
impl StdConfig for AuraConfig { impl StdConfig for AuraConfig {
/// Detect the keyboard type and load from default DB if data available
fn new() -> Self { fn new() -> Self {
panic!("This should not be used"); panic!("This should not be used");
} }
+71 -2
View File
@@ -25,6 +25,30 @@ pub struct Aura {
impl Aura { impl Aura {
/// Initialise the device if required. /// Initialise the device if required.
pub async fn do_initialization(&self) -> Result<(), RogError> { 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(()) 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 = [ 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)?; hid_raw.write_bytes(&msg)?;
} }
+4 -4
View File
@@ -182,7 +182,6 @@ impl AuraZbus {
self.0.set_brightness(config.brightness.into()).await?; self.0.set_brightness(config.brightness.into()).await?;
config.set_builtin(effect); config.set_builtin(effect);
config.write(); config.write();
Ok(()) Ok(())
} }
@@ -208,9 +207,10 @@ impl AuraZbus {
let mut config = self.0.config.lock().await; let mut config = self.0.config.lock().await;
for opt in options.states { for opt in options.states {
let zone = opt.zone; let zone = opt.zone;
for config in config.enabled.states.iter_mut() { for state in config.enabled.states.iter_mut() {
if config.zone == zone { if state.zone == zone {
*config = opt; *state = opt;
break;
} }
} }
} }
+1
View File
@@ -196,6 +196,7 @@ impl DeviceHandle {
Some(Arc::new(Mutex::new(k))) 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); let mut config = AuraConfig::load_and_update_config(prod_id);
config.led_type = aura_type; config.led_type = aura_type;
let aura = Aura { let aura = Aura {
+19 -1
View File
@@ -579,7 +579,7 @@
device_name: "G835L", device_name: "G835L",
product_id: "", product_id: "",
layout_name: "g814ji-per-key", 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: [], basic_zones: [],
advanced_type: PerKey, advanced_type: PerKey,
power_zones: [Keyboard, Lightbar, Logo], power_zones: [Keyboard, Lightbar, Logo],
@@ -980,6 +980,24 @@
advanced_type: r#None, advanced_type: r#None,
power_zones: [Keyboard], 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", device_name: "RC71L",
product_id: "", product_id: "",
+20 -11
View File
@@ -123,7 +123,8 @@ impl AuraPowerState {
| ((self.shutdown as u32) << 7) | ((self.shutdown as u32) << 7)
} }
PowerZones::Lightbar => { 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.awake as u32) << (7 + 3))
| ((self.sleep as u32) << (7 + 4)) | ((self.sleep as u32) << (7 + 4))
| ((self.shutdown as u32) << (7 + 5)) | ((self.shutdown as u32) << (7 + 5))
@@ -133,12 +134,20 @@ impl AuraPowerState {
| ((self.awake as u32) << (15 + 2)) | ((self.awake as u32) << (15 + 2))
| ((self.sleep as u32) << (15 + 3)) | ((self.sleep as u32) << (15 + 3))
| ((self.shutdown as u32) << (15 + 4)) | ((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 => { PowerZones::RearGlow => {
((self.boot as u32) << (23 + 1)) ((self.boot as u32) << (23 + 1))
| ((self.awake as u32) << (23 + 2)) | ((self.awake as u32) << (23 + 2))
| ((self.sleep as u32) << (23 + 3)) | ((self.sleep as u32) << (23 + 3))
| ((self.shutdown as u32) << (23 + 4)) | ((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, PowerZones::None | PowerZones::KeyboardAndLightbar => 0,
} }
@@ -618,19 +627,19 @@ mod test {
assert_eq!(shut_keyb_, "10000000, 00000000, 00000000, 00000000"); assert_eq!(shut_keyb_, "10000000, 00000000, 00000000, 00000000");
// //
assert_eq!(boot_bar__, "00000000, 00000010, 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!(sleep_bar_, "00000000, 00001000, 00000000, 00000000");
assert_eq!(shut_bar__, "00000000, 00010000, 00000000, 00000000"); assert_eq!(shut_bar__, "00000000, 00010000, 00000000, 00000000");
// //
assert_eq!(boot_lid__, "00000000, 00000000, 00000001, 00000000"); assert_eq!(boot_lid__, "00000000, 00000000, 00010001, 00000000");
assert_eq!(awake_lid_, "00000000, 00000000, 00000010, 00000000"); assert_eq!(awake_lid_, "00000000, 00000000, 00100010, 00000000");
assert_eq!(sleep_lid_, "00000000, 00000000, 00000100, 00000000"); assert_eq!(sleep_lid_, "00000000, 00000000, 01000100, 00000000");
assert_eq!(shut_lid__, "00000000, 00000000, 00001000, 00000000"); assert_eq!(shut_lid__, "00000000, 00000000, 10001000, 00000000");
// //
assert_eq!(boot_rear_, "00000000, 00000000, 00000000, 00000001"); assert_eq!(boot_rear_, "00000000, 00000000, 00000000, 00010001");
assert_eq!(awake_rear, "00000000, 00000000, 00000000, 00000010"); assert_eq!(awake_rear, "00000000, 00000000, 00000000, 00100010");
assert_eq!(sleep_rear, "00000000, 00000000, 00000000, 00000100"); assert_eq!(sleep_rear, "00000000, 00000000, 00000000, 01000100");
assert_eq!(shut_rear_, "00000000, 00000000, 00000000, 00001000"); assert_eq!(shut_rear_, "00000000, 00000000, 00000000, 10001000");
// All on // All on
let byte1 = to_binary_string_post2021(&LaptopAuraPower { 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") { match s.to_lowercase().trim_start_matches("0x") {
"tuf" => AuraDeviceType::LaptopKeyboardTuf, "tuf" => AuraDeviceType::LaptopKeyboardTuf,
"1932" => AuraDeviceType::ScsiExtDisk, "1932" => AuraDeviceType::ScsiExtDisk,
"1866" | "18c6" | "1869" | "1854" => Self::LaptopKeyboardPre2021, "1866" | "1869" | "1854" => Self::LaptopKeyboardPre2021,
"1abe" | "1b4c" => Self::Ally, "1abe" | "1b4c" => Self::Ally,
"19b3" | "193b" => Self::AnimeOrSlash, "19b3" | "193b" => Self::AnimeOrSlash,
"19b6" => Self::LaptopKeyboard2021, "19b6" | "1a30" | "18c6" => Self::LaptopKeyboard2021,
_ => Self::Unknown, _ => Self::Unknown,
} }
} }
+11 -2
View File
@@ -187,12 +187,18 @@ async fn main() -> Result<()> {
slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/")); 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 || { 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.clone();
let newui = setup_window(config.clone()); let newui = setup_window(config.clone(), prefetched_supported.clone());
newui.window().on_close_requested(move || { newui.window().on_close_requested(move || {
exit(0); exit(0);
}); });
@@ -233,6 +239,9 @@ async fn main() -> Result<()> {
let config_copy = config.clone(); let config_copy = config.clone();
let app_state_copy = app_state.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 || { slint::invoke_from_event_loop(move || {
UI.with(|ui| { UI.with(|ui| {
let app_state_copy = app_state_copy.clone(); let app_state_copy = app_state_copy.clone();
@@ -247,7 +256,7 @@ 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, pref_for_invoke.clone());
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;
+42 -5
View File
@@ -3,7 +3,11 @@ pub mod setup_aura;
pub mod setup_fans; pub mod setup_fans;
pub mod setup_system; pub mod setup_system;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration;
static TOAST_SEQ: AtomicU64 = AtomicU64::new(0);
use config_traits::StdConfig; use config_traits::StdConfig;
use log::warn; use log::warn;
@@ -70,19 +74,52 @@ pub fn show_toast(
handle: Weak<MainWindow>, handle: Weak<MainWindow>,
result: zbus::Result<()>, 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 { match result {
Ok(_) => { 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) => slint::invoke_from_event_loop(move || { });
}
Err(e) => {
let delayed_handle = handle.clone();
let delayed_text = fail.clone();
slint::invoke_from_event_loop(move || {
log::warn!("{fail}: {e}"); log::warn!("{fail}: {e}");
handle.unwrap().invoke_show_toast(fail) handle.unwrap().invoke_show_toast(fail)
}) })
.ok(), .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();
}
});
}
}; };
} }
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") slint::set_xdg_app_id("rog-control-center")
.map_err(|e| warn!("Couldn't set application ID: {e:?}")) .map_err(|e| warn!("Couldn't set application ID: {e:?}"))
.ok(); .ok();
@@ -119,7 +156,7 @@ pub fn setup_window(config: Arc<Mutex<Config>>) -> MainWindow {
setup_system_page_callbacks(&ui, config.clone()); setup_system_page_callbacks(&ui, config.clone());
} }
if available.contains(&"xyz.ljones.Aura".to_string()) { 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()) { if available.contains(&"xyz.ljones.Anime".to_string()) {
setup_anime_page(&ui, config.clone()); setup_anime_page(&ui, config.clone());
+130 -122
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>> { async fn find_aura_iface() -> Result<AuraProxy<'static>, Box<dyn std::error::Error>> {
let conn = zbus::Connection::system().await?; let conn = zbus::Connection::system().await?;
let f = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?; let mgr = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?;
let interfaces = f.get_managed_objects().await?; let objs = mgr.get_managed_objects().await?;
let mut aura_paths = Vec::new(); let mut paths: Vec<zbus::zvariant::OwnedObjectPath> = objs
for v in interfaces.iter() { .iter()
for k in v.1.keys() { .filter(|(_, ifaces)| ifaces.keys().any(|k| k.as_str() == "xyz.ljones.Aura"))
if k.as_str() == "xyz.ljones.Aura" { .map(|(p, _)| p.clone())
println!("Found aura device at {}, {}", v.0, k); .collect();
aura_paths.push(v.0.clone()); if paths.len() > 1 {
log::debug!("Multiple aura devices: {paths:?}");
} }
} let path = paths.pop().ok_or("No Aura interface")?;
} AuraProxy::builder(&conn)
if aura_paths.len() > 1 { .path(path)?
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")? .destination("xyz.ljones.Asusd")?
.build() .build()
.await?); .await
} .map_err(Into::into)
Err("No Aura interface".into())
} }
pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) { pub async fn prefetch_supported_basic_modes() -> Option<Vec<i32>> {
ui.global::<AuraPageData>().on_cb_hex_from_colour(|c| { 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() format!("#{:02X}{:02X}{:02X}", c.red(), c.green(), c.blue()).into()
}); });
g.on_cb_hex_to_colour(|s| decode_hex(s.as_str()).into());
ui.global::<AuraPageData>()
.on_cb_hex_to_colour(|s| decode_hex(s.as_str()).into());
let handle = ui.as_weak(); let handle = ui.as_weak();
tokio::spawn(async move { tokio::spawn(async move {
let Ok(aura) = find_aura_iface().await else { 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>(()); return Ok::<(), zbus::Error>(());
}; };
set_ui_props_async!(handle, aura, AuraPageData, brightness); 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, led_power);
set_ui_props_async!(handle, aura, AuraPageData, device_type); 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 { if let Ok(mut pow3r) = aura.supported_power_zones().await {
let dev_type = aura let dev = aura
.device_type() .device_type()
.await .await
.unwrap_or(AuraDeviceType::LaptopKeyboard2021); .unwrap_or(AuraDeviceType::LaptopKeyboard2021);
log::debug!("Available LED power modes {pow3r:?}");
handle handle
.upgrade_in_event_loop(move |handle| { .upgrade_in_event_loop(move |handle| {
let names: Vec<SharedString> = 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() .get_power_zone_names()
.iter() .iter()
.collect(); .collect();
if dev.is_old_laptop() {
if dev_type.is_old_laptop() {
// Need to add the specific KeyboardAndLightbar
if pow3r.contains(&PowerZones::Keyboard) if pow3r.contains(&PowerZones::Keyboard)
&& pow3r.contains(&PowerZones::Lightbar) && pow3r.contains(&PowerZones::Lightbar)
{ {
pow3r.push(PowerZones::KeyboardAndLightbar); pow3r.push(PowerZones::KeyboardAndLightbar);
} }
let names: Vec<SharedString> = let n: Vec<SharedString> =
pow3r.iter().map(|n| names[(*n) as usize].clone()).collect(); pow3r.iter().map(|z| names[(*z) as usize].clone()).collect();
handle handle
.global::<AuraPageData>() .global::<AuraPageData>()
.set_power_zone_names_old(names.as_slice().into()); .set_power_zone_names_old(n.as_slice().into());
} else { } else {
let power: Vec<SlintPowerZones> = let p: Vec<SlintPowerZones> = pow3r.iter().map(|z| (*z).into()).collect();
pow3r.iter().map(|p| (*p).into()).collect();
handle handle
.global::<AuraPageData>() .global::<AuraPageData>()
.set_supported_power_zones(power.as_slice().into()); .set_supported_power_zones(p.as_slice().into());
} }
}) })
.ok(); .ok();
} }
if let Ok(modes) = aura.supported_basic_modes().await { let proxy = aura.clone();
log::debug!("Available LED modes {modes:?}"); let weak = handle.clone();
handle handle
.upgrade_in_event_loop(move |handle| { .upgrade_in_event_loop(move |h| {
let m: Vec<i32> = modes.iter().map(|n| (*n).into()).collect(); set_ui_callbacks!(h,
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();
handle
.upgrade_in_event_loop(move |handle| {
set_ui_callbacks!(handle,
AuraPageData(.into()), AuraPageData(.into()),
proxy_copy.brightness(.into()), proxy.brightness(.into()),
"Keyboard LED brightness successfully set to {}", "Brightness set to {}",
"Setting keyboard LED brightness failed" "Brightness failed"
); );
set_ui_callbacks!(handle, let p = proxy.clone();
AuraPageData(.into()), let w = weak.clone();
proxy_copy.led_mode(.into()), h.global::<AuraPageData>().on_apply_led_mode_data(move || {
"Keyboard LED mode successfully set to {}", let Some(ui) = w.upgrade() else { return };
"Setting keyboard LEDmode failed" let slint_effect = ui.global::<AuraPageData>().get_led_mode_data();
); let raw: rog_aura::AuraEffect = slint_effect.into();
let pp = p.clone();
set_ui_callbacks!(handle, let t = w.clone();
AuraPageData(.into()), tokio::spawn(async move {
proxy_copy.led_mode_data(.into()), let r = pp.set_led_mode_data(raw).await;
"Keyboard LED mode set to {:?}", show_toast("LED mode applied".into(), "LED mode failed".into(), t, r);
"Setting keyboard LED mode failed" });
); });
h.invoke_external_colour_change();
// 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(); .ok();
let handle_copy = handle.clone(); let weak_power = handle.clone();
let proxy_copy = aura.clone(); let proxy_power = aura.clone();
handle handle
.upgrade_in_event_loop(|handle| { .upgrade_in_event_loop(|h| {
handle h.global::<AuraPageData>().on_cb_led_power(move |power| {
.global::<AuraPageData>() let w = weak_power.clone();
.on_cb_led_power(move |power| { let p = proxy_power.clone();
let handle_copy = handle_copy.clone(); let pw: LaptopAuraPower = power.into();
let proxy_copy = aura.clone();
let power: LaptopAuraPower = power.into();
tokio::spawn(async move { tokio::spawn(async move {
show_toast( show_toast(
"Aura power settings changed".into(), "Aura power updated".into(),
"Failed to set Aura power settings".into(), "Aura power failed".into(),
handle_copy, w,
proxy_copy.set_led_power(power).await, p.set_led_power(pw).await,
); );
}); });
}); });
}) })
.map_err(|e| error!("{e:}")) .map_err(|e| error!("{e}"))
.ok(); .ok();
// Need to update the UI if the mode changes let stream_handle = handle.clone();
let handle_copy = handle.clone(); let aura_stream = aura.clone();
// spawn required since the while let never exits
tokio::spawn(async move { tokio::spawn(async move {
let mut x = proxy_copy.receive_led_mode_data_changed().await;
use futures_util::StreamExt; 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 { if let Ok(out) = e.get().await {
handle_copy let raw: i32 = out.mode.into();
.upgrade_in_event_loop(move |handle| { let data = out.into();
handle stream_handle
.upgrade_in_event_loop(move |h| {
h.global::<AuraPageData>().invoke_update_led_mode_data(data);
let supported: Vec<i32> = h
.global::<AuraPageData>() .global::<AuraPageData>()
.invoke_update_led_mode_data(out.into()); .get_supported_basic_modes()
handle.invoke_external_colour_change(); .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(); .ok();
} }
} }
}); });
debug!("Aura setup tasks complete"); debug!("Aura setup done");
Ok(()) Ok(())
}); });
} }
+7
View File
@@ -43,6 +43,13 @@ export component MainWindow inherits Window {
toast = text != ""; toast = text != "";
toast_text = 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 exit-app();
callback show_notification(bool); callback show_notification(bool);
show_notification(yes) => { show_notification(yes) => {
+60 -59
View File
@@ -42,10 +42,14 @@ export component PageAura inherits Rectangle {
current_value: AuraPageData.available_mode_names[self.current-index]; current_value: AuraPageData.available_mode_names[self.current-index];
model <=> AuraPageData.available_mode_names; model <=> AuraPageData.available_mode_names;
selected => { selected => {
AuraPageData.led_mode_data.mode = AuraPageData.led_mode; AuraPageData.apply_effect({
AuraPageData.led_mode_data.mode = AuraPageData.current_available_mode; mode: AuraPageData.supported_basic_modes[self.current-index],
self.current_value = AuraPageData.available_mode_names[self.current-index]; zone: AuraPageData.led_mode_data.zone,
AuraPageData.cb_led_mode(AuraPageData.current_available_mode); 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; vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center;
} }
HorizontalBox { HorizontalBox {
c1 := ColourSlider { 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; final_colour <=> AuraPageData.color1;
colourbox <=> AuraPageData.colorbox1; colourbox <=> AuraPageData.colorbox1;
set_hex_from_colour(c1) => { set_hex_from_colour(c) => { return AuraPageData.cb_hex_from_colour(c); }
return AuraPageData.cb_hex_from_colour(c1); hex_to_colour(s) => { return AuraPageData.cb_hex_to_colour(s); }
}
hex_to_colour(s) => {
return AuraPageData.cb_hex_to_colour(s);
}
released => { released => {
AuraPageData.led_mode_data.colour1 = AuraPageData.color1; AuraPageData.apply_effect({
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data); 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 { VerticalBox {
Text { Text { text: @tr("Colour 2"); vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center; }
text: @tr("Colour 2");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
HorizontalBox { HorizontalBox {
c2 := ColourSlider { c2 := ColourSlider {
enabled: AuraPageData.led_mode == 1 || AuraPageData.led_mode == 4; enabled: AuraPageData.colour2_enabled;
final_colour <=> AuraPageData.color2; final_colour <=> AuraPageData.color2;
colourbox <=> AuraPageData.colorbox2; colourbox <=> AuraPageData.colorbox2;
set_hex_from_colour(c1) => { set_hex_from_colour(c) => { return AuraPageData.cb_hex_from_colour(c); }
return AuraPageData.cb_hex_from_colour(c1); hex_to_colour(s) => { return AuraPageData.cb_hex_to_colour(s); }
}
hex_to_colour(s) => {
return AuraPageData.cb_hex_to_colour(s);
}
released => { released => {
AuraPageData.led_mode_data.colour2 = AuraPageData.color2; AuraPageData.apply_effect({
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data); 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; max-height: 90px;
RogItem { RogItem {
VerticalBox { VerticalBox {
Text { Text { text: @tr("Zone"); vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center; }
text: @tr("Zone");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
ComboBox { ComboBox {
// enabled: AuraPageData.led_mode == ;
enabled: false; enabled: false;
current_index <=> AuraPageData.zone; current_index <=> AuraPageData.zone;
current_value: AuraPageData.zone_names[self.current-index]; current_value: AuraPageData.zone_names[self.current-index];
model <=> AuraPageData.zone_names; model <=> AuraPageData.zone_names;
selected => { selected => {
AuraPageData.led_mode_data.zone = self.current-index; AuraPageData.apply_effect({
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data); 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 { RogItem {
VerticalBox { VerticalBox {
Text { Text { text: @tr("Direction"); vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center; }
text: @tr("Direction");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
ComboBox { ComboBox {
enabled: AuraPageData.led_mode == 3; enabled: AuraPageData.direction_enabled;
current_index <=> AuraPageData.direction; current_index <=> AuraPageData.direction;
current_value: AuraPageData.direction_names[self.current-index]; current_value: AuraPageData.direction_names[self.current-index];
model <=> AuraPageData.direction_names; model <=> AuraPageData.direction_names;
selected => { selected => {
AuraPageData.led_mode_data.direction = self.current-index; AuraPageData.apply_effect({
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data); 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 { RogItem {
VerticalBox { VerticalBox {
Text { Text { text: @tr("Speed"); vertical-alignment: TextVerticalAlignment.center; horizontal-alignment: TextHorizontalAlignment.center; }
text: @tr("Speed");
vertical-alignment: TextVerticalAlignment.center;
horizontal-alignment: TextHorizontalAlignment.center;
}
ComboBox { 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_index <=> AuraPageData.speed;
current_value: AuraPageData.speed_names[self.current-index]; current_value: AuraPageData.speed_names[self.current-index];
model <=> AuraPageData.speed_names; model <=> AuraPageData.speed_names;
selected => { selected => {
AuraPageData.led_mode_data.speed = self.current-index; AuraPageData.apply_effect({
AuraPageData.cb_led_mode_data(AuraPageData.led_mode_data); 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], 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 { export global AuraPageData {
// The ordering must match the rog-aura crate
in-out property <[string]> power_zone_names: [ in-out property <[string]> power_zone_names: [
@tr("Aura power zone" => "Logo"), @tr("Aura power zone" => "Logo"),
@tr("Aura power zone" => "Keyboard"), @tr("Aura power zone" => "Keyboard"),
@@ -87,15 +89,9 @@ export global AuraPageData {
@tr("Basic aura mode" => "Comet"), @tr("Basic aura mode" => "Comet"),
@tr("Basic aura mode" => "Flash"), @tr("Basic aura mode" => "Flash"),
]; ];
in-out property <[string]> available_mode_names: [ in-out property <[string]> available_mode_names: [ @tr("Basic aura mode" => "Static"), @tr("Basic aura mode" => "Breathe"), @tr("Basic aura mode" => "Strobe") ];
@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> 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]> 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: [ in-out property <[string]> zone_names: [
@tr("Aura zone" => "None"), @tr("Aura zone" => "None"),
@tr("Aura zone" => "Key1"), @tr("Aura zone" => "Key1"),
@@ -106,51 +102,57 @@ export global AuraPageData {
@tr("Aura zone" => "Lightbar Left"), @tr("Aura zone" => "Lightbar Left"),
@tr("Aura zone" => "Lightbar Right"), @tr("Aura zone" => "Lightbar Right"),
]; ];
in-out property <int> zone;
in-out property <[string]> direction_names: [ in-out property <[string]> direction_names: [
@tr("Aura direction" => "Right"), @tr("Aura direction" => "Right"),
@tr("Aura direction" => "Left"), @tr("Aura direction" => "Left"),
@tr("Aura direction" => "Up"), @tr("Aura direction" => "Up"),
@tr("Aura direction" => "Down"), @tr("Aura direction" => "Down"),
]; ];
in-out property <int> direction;
in-out property <[string]> speed_names: [ in-out property <[string]> speed_names: [
@tr("Aura speed" => "Low"), @tr("Aura speed" => "Low"),
@tr("Aura speed" => "Medium"), @tr("Aura speed" => "Medium"),
@tr("Aura speed" => "High"), @tr("Aura speed" => "High"),
]; ];
in-out property <int> speed;
in-out property <AuraEffect> led_mode_data: { in-out property <AuraEffect> led_mode_data: {
mode: 0, mode: 0,
zone: 0, zone: 0,
colour1: Colors.aquamarine, colour1: Colors.aquamarine,
colourbox1: Colors.aquamarine,
colour2: Colors.hotpink, colour2: Colors.hotpink,
colourbox2: Colors.hotpink,
speed: 0, speed: 0,
direction: 0, direction: 0,
}; };
callback cb_led_mode_data(AuraEffect);
in-out property <color> color1; in-out property <color> color1;
in-out property <brush> colorbox1; in-out property <brush> colorbox1;
in-out property <color> color2; in-out property <color> color2;
in-out property <brush> colorbox2; 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); callback update_led_mode_data(AuraEffect);
update_led_mode_data(data) => { update_led_mode_data(d) => {
led_mode_data = data; led_mode_data = d;
current_available_mode = data.mode; zone = d.zone;
zone = data.zone; speed = d.speed;
speed = data.speed; direction = d.direction;
direction = data.direction; color1 = d.colour1;
color1 = data.colour1; color2 = d.colour2;
color2 = data.colour2; colorbox1 = d.colour1;
colorbox1 = data.colour1; colorbox2 = d.colour2;
colorbox2 = data.colour2;
} }
callback cb_hex_from_colour(color) -> string; callback cb_hex_from_colour(color) -> string;
callback cb_hex_to_colour(string) -> color; callback cb_hex_to_colour(string) -> color;
in-out property <AuraDevType> device_type: AuraDevType.Old; 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: [ in-out property <[PowerZones]> supported_power_zones: [
PowerZones.Keyboard, PowerZones.Keyboard,
PowerZones.Lightbar, PowerZones.Lightbar,
+13 -4
View File
@@ -162,7 +162,6 @@ impl CurveData {
/// Write this curve to the device fan specified by `self.fan` /// Write this curve to the device fan specified by `self.fan`
pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> { pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> {
let pwm_num: char = self.fan.into(); let pwm_num: char = self.fan.into();
let enable = if self.enabled { '1' } else { '2' };
for (index, out) in self.pwm.iter().enumerate() { for (index, out) in self.pwm.iter().enumerate() {
let pwm = pwm_str(pwm_num, index); let pwm = pwm_str(pwm_num, index);
@@ -176,10 +175,20 @@ impl CurveData {
device.set_attribute_value(&temp, out.to_string())?; 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 device
.set_attribute_value(format!("pwm{pwm_num}_enable"), enable.to_string()) .set_attribute_value(&enable_attr, enable.to_string())
.map_err(|e| error!("Failed to set pwm{pwm_num}_enable to {enable}: {e:?}")) .map_err(|e| error!("Failed to set {enable_attr} to {enable}: {e:?}"))
.ok(); .ok();
Ok(()) Ok(())
} }
+20 -6
View File
@@ -181,15 +181,29 @@ impl FanCurveProfiles {
PlatformProfile::Quiet | PlatformProfile::LowPower => &mut self.quiet, PlatformProfile::Quiet | PlatformProfile::LowPower => &mut self.quiet,
PlatformProfile::Custom => &mut self.custom, 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)?; 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) { for fan in fans.iter().filter(|f| f.enabled) {
debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}"); debug!(
fan.write_to_device(device)?; "write_profile_curve_to_platform: enabling fan for profile:{profile}, {:?}",
fan.fan
);
fan.set_enable(device)?;
} }
Ok(()) Ok(())
} }