diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index 470180ccf..b0d1c40b9 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -668,23 +668,39 @@ fn handle_led_power_1_do_1866( power: &LedPowerCommand1, ) -> Result<(), Box> { let mut states = Vec::new(); - if power.keyboard { - states.push(AuraPowerState { - zone: PowerZones::Keyboard, - boot: power.boot.unwrap_or_default(), - awake: power.awake.unwrap_or_default(), - sleep: power.sleep.unwrap_or_default(), - shutdown: false, - }); - } - if power.lightbar { - states.push(AuraPowerState { - zone: PowerZones::Lightbar, - boot: power.boot.unwrap_or_default(), - awake: power.awake.unwrap_or_default(), - sleep: power.sleep.unwrap_or_default(), - shutdown: false, - }); + let is_tuf = aura.device_type()?.is_tuf_laptop(); + + // TUF laptops have only a keyboard zone; lightbar does not exist. + if is_tuf { + if power.keyboard || power.lightbar { + states.push(AuraPowerState { + zone: PowerZones::Keyboard, + boot: power.boot.unwrap_or_default(), + awake: power.awake.unwrap_or_default(), + sleep: power.sleep.unwrap_or_default(), + shutdown: false, + }); + } + } else { + // Pre-2021 0x1866 keyboards may have separate keyboard and lightbar zones. + if power.keyboard { + states.push(AuraPowerState { + zone: PowerZones::Keyboard, + boot: power.boot.unwrap_or_default(), + awake: power.awake.unwrap_or_default(), + sleep: power.sleep.unwrap_or_default(), + shutdown: false, + }); + } + if power.lightbar { + states.push(AuraPowerState { + zone: PowerZones::Lightbar, + boot: power.boot.unwrap_or_default(), + awake: power.awake.unwrap_or_default(), + sleep: power.sleep.unwrap_or_default(), + shutdown: false, + }); + } } let states = LaptopAuraPower { states }; diff --git a/asusd/src/aura_laptop/mod.rs b/asusd/src/aura_laptop/mod.rs index f6ebef2ca..3990f58bb 100644 --- a/asusd/src/aura_laptop/mod.rs +++ b/asusd/src/aura_laptop/mod.rs @@ -41,7 +41,16 @@ impl Aura { } else { config.brightness.into() }; + // These fields are derived during device discovery and deliberately + // excluded from the on-disk configuration. `read()` replaces the + // entire struct, so retain them when refreshing settings for sleep. + let led_type = config.led_type; + let support_data = config.support_data.clone(); + let per_key_mode_active = config.per_key_mode_active; config.read(); + config.led_type = led_type; + config.support_data = support_data; + config.per_key_mode_active = per_key_mode_active; config.brightness = bright.into(); config.write(); Ok(()) @@ -144,7 +153,17 @@ impl Aura { if let Some(backlight) = &self.backlight { // TODO: tuf bool array let buf = config.enabled.to_bytes(config.led_type); - backlight.lock().await.set_kbd_rgb_state(&buf)?; + let backlight = backlight.lock().await; + // Some FA401UH firmware/kernel combinations expose brightness + // and RGB mode but not the optional power-state attribute. + // Power changes must not make the whole Aura interface fail. + if backlight.has_kbd_rgb_state() { + backlight.set_kbd_rgb_state(&buf)?; + } else { + log::debug!( + "TUF keyboard does not expose kbd_rgb_state; skipping power-state write" + ); + } } } else if let Some(hid_raw) = &self.hid { let hid_raw = hid_raw.lock().await; diff --git a/asusd/src/aura_laptop/trait_impls.rs b/asusd/src/aura_laptop/trait_impls.rs index 5cd692b20..5e00daee4 100644 --- a/asusd/src/aura_laptop/trait_impls.rs +++ b/asusd/src/aura_laptop/trait_impls.rs @@ -27,7 +27,6 @@ impl AuraZbus { pub async fn start_tasks( mut self, connection: &Connection, - // _signal_ctx: SignalEmitter<'static>, path: OwnedObjectPath, ) -> Result<(), RogError> { // let task = zbus.clone(); @@ -35,14 +34,20 @@ impl AuraZbus { self.reload() .await .unwrap_or_else(|err| warn!("Controller error: {}", err)); + let task = self.clone(); connection .object_server() .at(path.clone(), self) .await .map_err(|e| error!("Couldn't add server at path: {path}, {e:?}")) .ok(); - // TODO: skip this until we keep handles to tasks so they can be killed - // task.create_tasks(signal_ctx).await + // Subscribe to logind sleep/shutdown events. Without this call the + // Aura interface is available, but the keyboard controller never + // receives on_prepare_for_sleep notifications. + let signal_ctx = SignalEmitter::new(connection, AURA_ZBUS_PATH)?; + info!("Starting CtrlKbdLedTask system-event subscription"); + task.create_tasks(signal_ctx).await?; + info!("Started CtrlKbdLedTask system-event subscription"); Ok(()) } } @@ -241,6 +246,7 @@ impl CtrlTask for AuraZbus { } async fn create_tasks(&self, _: SignalEmitter<'static>) -> Result<(), RogError> { + info!("Creating Aura system-event callbacks"); let inner1 = self.0.clone(); let inner3 = self.0.clone(); self.create_sys_event_tasks( @@ -248,37 +254,54 @@ impl CtrlTask for AuraZbus { let inner1 = inner1.clone(); // unwrap as we want to bomb out of the task async move { - if !sleeping { + info!("CtrlKbdLedTask received prepare_for_sleep({sleeping})"); + if sleeping { + let (sleep_enabled, led_type, user_brightness) = { + let config = inner1.config.lock().await; + ( + config.enabled.states.iter() + .any(|s| s.zone == rog_aura::PowerZones::Keyboard && s.sleep), + config.led_type, + u8::from(config.brightness), + ) + }; + + let config = inner1.config.lock().await; + if let Err(e) = inner1.set_power_states(&config).await { + error!("CtrlKbdLedTask sleep power state write: {e}"); + } + + if sleep_enabled && led_type.is_tuf_laptop() { + info!("CtrlKbdLedTask sleep: TUF sleep strobe requires non-zero brightness"); + if let Some(backlight) = &inner1.backlight { + let target_brightness: u8 = if user_brightness == 0 { 3 } else { user_brightness }; + if let Err(e) = backlight.lock().await.set_brightness(target_brightness) { + error!("CtrlKbdLedTask sleep brightness: {e}"); + } + } + } + } else { info!("CtrlKbdLedTask reloading brightness and modes"); + let (brightness, led_type) = { + let config = inner1.config.lock().await; + (config.brightness.into(), config.led_type) + }; if let Some(backlight) = &inner1.backlight { - backlight - .lock() - .await - .set_brightness(inner1.config.lock().await.brightness.into()) - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - }) - .unwrap(); + if let Err(e) = backlight.lock().await.set_brightness(brightness) { + error!("CtrlKbdLedTask wake brightness: {e}"); + return; + } } let mut config = inner1.config.lock().await; - inner1 - .write_current_config_mode(&mut config) - .await - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - }) - .unwrap(); - } else if sleeping { - inner1 - .update_config() - .await - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - }) - .unwrap(); + if let Err(e) = inner1.write_current_config_mode(&mut config).await { + error!("CtrlKbdLedTask wake mode: {e}"); + return; + } + if led_type.is_tuf_laptop() + && let Err(e) = inner1.set_power_states(&config).await + { + error!("CtrlKbdLedTask wake power state: {e}"); + } } } }, @@ -286,17 +309,11 @@ impl CtrlTask for AuraZbus { let inner3 = inner3.clone(); async move { info!("CtrlKbdLedTask reloading brightness and modes"); + let brightness = inner3.config.lock().await.brightness.into(); if let Some(backlight) = &inner3.backlight { - // unwrap as we want to bomb out of the task - backlight - .lock() - .await - .set_brightness(inner3.config.lock().await.brightness.into()) - .map_err(|e| { - error!("CtrlKbdLedTask: {e}"); - e - }) - .unwrap(); + if let Err(e) = backlight.lock().await.set_brightness(brightness) { + error!("CtrlKbdLedTask: {e}"); + } } } }, diff --git a/asusd/src/lib.rs b/asusd/src/lib.rs index 3789d76cd..3219f743f 100644 --- a/asusd/src/lib.rs +++ b/asusd/src/lib.rs @@ -435,14 +435,21 @@ pub trait CtrlTask { tokio::spawn({ let logind_manager = logind_manager.clone(); async move { - if let Ok(mut notif) = logind_manager.receive_prepare_for_sleep().await { - while let Some(event) = notif.next().await { - // blocks thread :| - if let Ok(args) = event.args() { - debug!("Doing on_prepare_for_sleep({})", args.start); - on_prepare_for_sleep(args.start).await; + match logind_manager.receive_prepare_for_sleep().await { + Ok(mut notif) => { + info!("Subscribed to logind PrepareForSleep"); + while let Some(event) = notif.next().await { + // blocks thread :| + if let Ok(args) = event.args() { + debug!("Doing on_prepare_for_sleep({})", args.start); + on_prepare_for_sleep(args.start).await; + } else { + warn!("Failed to decode logind PrepareForSleep signal"); + } } + warn!("logind PrepareForSleep stream ended"); } + Err(err) => warn!("Could not subscribe to logind PrepareForSleep: {err}"), } } }); diff --git a/rog-aura/data/aura_support.ron b/rog-aura/data/aura_support.ron index 31c4a4a28..f4366df56 100644 --- a/rog-aura/data/aura_support.ron +++ b/rog-aura/data/aura_support.ron @@ -1,4 +1,13 @@ ([ + ( + device_name: "FA401", + product_id: "", + layout_name: "fa507", + basic_modes: [Static, Breathe, Pulse], + basic_zones: [], + advanced_type: r#None, + power_zones: [Keyboard], + ), ( device_name: "FA506I", product_id: "", diff --git a/rog-aura/src/keyboard/power.rs b/rog-aura/src/keyboard/power.rs index 0b2bd70d3..7d68984fe 100644 --- a/rog-aura/src/keyboard/power.rs +++ b/rog-aura/src/keyboard/power.rs @@ -339,6 +339,28 @@ mod test { ) } + #[test] + fn tuf_power_bytes_include_boot_awake_and_sleep() { + let power = LaptopAuraPower { + states: vec![ + AuraPowerState { + zone: PowerZones::Keyboard, + boot: true, + awake: false, + sleep: true, + shutdown: false, + }, + ], + }; + + assert_eq!( + power.to_bytes(AuraDeviceType::LaptopKeyboardTuf), + vec![ + 1, 1, 0, 1, 1 + ] + ); + } + #[test] fn check_0x1866_control_bytes() { let power = LaptopAuraPower {