refactor: Address review feedback

- Remove deprecated supergfx integration
- Ensure DBus is used instead of direct calls (verified)
- Clean up unused imports and modules
This commit is contained in:
mihai2mn
2026-01-17 00:05:45 +01:00
parent f5f997e057
commit 37c74a6bba
21 changed files with 699 additions and 649 deletions
-200
View File
@@ -1,200 +0,0 @@
//! Software-based keyboard animation for keyboards that only support Static mode.
//! Provides Rainbow and Color Cycle animations via timer-based color updates.
use log::{info, warn};
use slint::Weak;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Arc;
use std::time::Duration;
use crate::MainWindow;
/// Animation mode enum matching the UI
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum AnimationMode {
#[default]
None,
Rainbow,
ColorCycle,
}
impl From<i32> for AnimationMode {
fn from(v: i32) -> Self {
match v {
1 => AnimationMode::Rainbow,
2 => AnimationMode::ColorCycle,
_ => AnimationMode::None,
}
}
}
/// Shared state for the animator
pub struct AnimatorState {
/// Current animation mode
pub mode: AtomicU32,
/// Animation speed in milliseconds (update interval)
pub speed_ms: AtomicU32,
/// Stop signal
pub stop: AtomicBool,
/// Current hue for rainbow mode (0-360)
hue: AtomicU32,
}
impl Default for AnimatorState {
fn default() -> Self {
Self {
mode: AtomicU32::new(0),
speed_ms: AtomicU32::new(200),
stop: AtomicBool::new(false),
hue: AtomicU32::new(0),
}
}
}
/// Convert HSV to RGB (H: 0-360, S: 0-100, V: 0-100)
fn hsv_to_rgb(h: u32, s: u32, v: u32) -> (u8, u8, u8) {
let s = s as f32 / 100.0;
let v = v as f32 / 100.0;
let c = v * s;
let h_prime = (h as f32 / 60.0) % 6.0;
let x = c * (1.0 - ((h_prime % 2.0) - 1.0).abs());
let m = v - c;
let (r, g, b) = match h_prime as u32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
(
((r + m) * 255.0) as u8,
((g + m) * 255.0) as u8,
((b + m) * 255.0) as u8,
)
}
/// Format RGB as hex color string for asusctl
fn rgb_to_hex(r: u8, g: u8, b: u8) -> String {
format!("{:02x}{:02x}{:02x}", r, g, b)
}
// Simple LCG for random numbers to avoid pulling in rand crate
fn next_random(seed: &mut u64) -> u32 {
*seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
(*seed >> 32) as u32
}
/// Start the animation loop (runs in tokio task)
pub fn start_animator(state: Arc<AnimatorState>, _ui_weak: Weak<MainWindow>) {
info!("Starting keyboard animator");
tokio::spawn(async move {
// Local state for Color Cycle (RGB)
let mut current_r: f32 = 255.0;
let mut current_g: f32 = 0.0;
let mut current_b: f32 = 0.0;
let mut target_r: f32 = 0.0;
let mut target_g: f32 = 255.0;
let mut target_b: f32 = 0.0;
let mut seed = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(12345);
loop {
// Check for stop signal
if state.stop.load(Ordering::Relaxed) {
info!("Animator stopping");
break;
}
let mode = AnimationMode::from(state.mode.load(Ordering::Relaxed) as i32);
// Cap speed at 150ms for stability
let raw_speed = state.speed_ms.load(Ordering::Relaxed);
let effective_speed = raw_speed.max(150) as u64;
if mode == AnimationMode::None {
// No animation, sleep longer
tokio::time::sleep(Duration::from_millis(500)).await;
continue;
}
// Calculate next color
let hex_color = match mode {
AnimationMode::Rainbow => {
// Hue step 1 for smooth, granular transitions
let hue = state.hue.fetch_add(1, Ordering::Relaxed) % 360;
let (r, g, b) = hsv_to_rgb(hue, 100, 100);
rgb_to_hex(r, g, b)
}
AnimationMode::ColorCycle => {
// RGB Linear Interpolation (Fading) - NOT Rainbow
// 1. Check distance to target
let dist_sq = (target_r - current_r).powi(2)
+ (target_g - current_g).powi(2)
+ (target_b - current_b).powi(2);
// If close, pick new random target color
if dist_sq < 100.0 {
let next_h = next_random(&mut seed) % 360;
let (r, g, b) = hsv_to_rgb(next_h, 100, 100);
target_r = r as f32;
target_g = g as f32;
target_b = b as f32;
}
// 2. Lerp towards target (5% per frame for smooth ease-out)
let factor = 0.05;
current_r += (target_r - current_r) * factor;
current_g += (target_g - current_g) * factor;
current_b += (target_b - current_b) * factor;
rgb_to_hex(current_r as u8, current_g as u8, current_b as u8)
}
AnimationMode::None => continue,
};
// Send color update via asusctl command (blocking, AWAITED to prevent races)
let hex = hex_color.clone();
let _ = tokio::task::spawn_blocking(move || {
let result = Command::new("asusctl")
.args([
"aura", "static", "-c", &hex,
])
.output();
if let Err(e) = result {
warn!("Failed to set aura color: {}", e);
}
})
.await;
// Sleep for the animation speed interval
tokio::time::sleep(Duration::from_millis(effective_speed)).await;
}
});
}
/// Stop the animator
pub fn stop_animator(state: &Arc<AnimatorState>) {
state.stop.store(true, Ordering::Relaxed);
state.mode.store(0, Ordering::Relaxed);
}
/// Set animation mode
pub fn set_animation_mode(state: &Arc<AnimatorState>, mode: AnimationMode) {
state.mode.store(mode as u32, Ordering::Relaxed);
// Reset stop flag in case we're restarting
state.stop.store(false, Ordering::Relaxed);
}
/// Set animation speed
pub fn set_animation_speed(state: &Arc<AnimatorState>, speed_ms: u32) {
let clamped = speed_ms.clamp(50, 2000);
state.speed_ms.store(clamped, Ordering::Relaxed);
}
+1 -37
View File
@@ -1,4 +1,3 @@
pub mod aura_animator;
pub mod setup_anime;
pub mod setup_aura;
pub mod setup_fan_curve_custom;
@@ -6,7 +5,6 @@ pub mod setup_fans;
pub mod setup_screenpad;
pub mod setup_slash;
pub mod setup_status;
pub mod setup_supergfx;
pub mod setup_system;
use std::sync::{Arc, Mutex};
@@ -24,7 +22,6 @@ use crate::ui::setup_fans::setup_fan_curve_page;
use crate::ui::setup_screenpad::setup_screenpad;
use crate::ui::setup_slash::setup_slash;
use crate::ui::setup_status::setup_status;
use crate::ui::setup_supergfx::setup_supergfx;
use crate::ui::setup_system::{setup_system_page, setup_system_page_callbacks};
use crate::{AppSettingsPageData, MainWindow};
@@ -116,20 +113,7 @@ pub fn setup_window(
available.contains(&"xyz.ljones.Aura".to_string()),
available.contains(&"xyz.ljones.Anime".to_string()),
available.contains(&"xyz.ljones.Slash".to_string()),
// Supergfx check
{
if let Ok(conn) = zbus::blocking::Connection::system() {
zbus::blocking::fdo::DBusProxy::new(&conn)
.ok()
.and_then(|p| {
p.name_has_owner("org.supergfxctl.Daemon".try_into().ok()?)
.ok()
})
.unwrap_or(false)
} else {
false
}
},
false,
// Screenpad check (Backlight interface)
available.contains(&"xyz.ljones.Backlight".to_string()),
available.contains(&"xyz.ljones.FanCurves".to_string()),
@@ -186,9 +170,7 @@ pub fn setup_window(
setup_slash(&ui, config.clone());
}
// Always try to setup supergfx if detected above, but for simplicity here we assume if sidebar has it (re-check or just run)
// We didn't capture the boolean above. Let's just run it, it handles its own availability check internally via async proxy creation.
setup_supergfx(&ui, config.clone());
if available.contains(&"xyz.ljones.Backlight".to_string()) {
setup_screenpad(&ui, config.clone());
@@ -237,22 +219,6 @@ pub fn setup_app_settings_page(ui: &MainWindow, config: Arc<Mutex<Config>>) {
});
// Granular notification toggles
let config_copy = config.clone();
global.on_set_notify_gfx_switch(move |enable| {
if let Ok(mut lock) = config_copy.try_lock() {
lock.notifications.receive_notify_gfx = enable;
lock.write();
}
});
let config_copy = config.clone();
global.on_set_notify_gfx_status(move |enable| {
if let Ok(mut lock) = config_copy.try_lock() {
lock.notifications.receive_notify_gfx_status = enable;
lock.write();
}
});
let config_copy = config.clone();
global.on_set_notify_platform_profile(move |enable| {
if let Ok(mut lock) = config_copy.try_lock() {
@@ -267,8 +233,6 @@ pub fn setup_app_settings_page(ui: &MainWindow, config: Arc<Mutex<Config>>) {
global.set_startup_in_background(lock.startup_in_background);
global.set_enable_tray_icon(lock.enable_tray_icon);
global.set_notifications_enabled(lock.notifications.enabled);
global.set_notify_gfx_switch(lock.notifications.receive_notify_gfx);
global.set_notify_gfx_status(lock.notifications.receive_notify_gfx_status);
global.set_notify_platform_profile(lock.notifications.receive_notify_platform_profile);
}
}
+137 -28
View File
@@ -1,15 +1,13 @@
use std::sync::{Arc, Mutex};
use log::{debug, error, info};
use rog_aura::animation::AnimationMode;
use rog_aura::keyboard::LaptopAuraPower;
use rog_aura::{AuraDeviceType, PowerZones};
use rog_aura::{AuraDeviceType, Colour, PowerZones};
use rog_dbus::zbus_aura::AuraProxy;
use slint::{ComponentHandle, Model, RgbaColor, SharedString};
use crate::config::Config;
use crate::ui::aura_animator::{
set_animation_mode, set_animation_speed, start_animator, AnimationMode, AnimatorState,
};
use crate::ui::show_toast;
use crate::{
set_ui_callbacks, set_ui_props_async, AuraPageData, MainWindow, PowerZones as SlintPowerZones,
@@ -126,17 +124,15 @@ pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
.ok();
}
// Create animator state (shared across callbacks)
let animator_state = Arc::new(AnimatorState::default());
if let Ok(modes) = aura.supported_basic_modes().await {
log::debug!("Available LED modes {modes:?}");
// Check if only Static mode is available (enable software animation)
let static_only = modes.len() == 1 && modes.iter().any(|m| *m == 0.into());
let handle_for_anim = handle.clone();
let animator_state_clone = animator_state.clone();
// Clone proxy for callbacks
let aura_for_animation = aura.clone();
handle
.upgrade_in_event_loop(move |handle| {
let m: Vec<i32> = modes.iter().map(|n| (*n).into()).collect();
@@ -163,23 +159,69 @@ pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
.global::<AuraPageData>()
.set_soft_animation_available(true);
// Start the animator thread
start_animator(animator_state_clone.clone(), handle_for_anim.clone());
// Connect mode callback
let state_for_mode = animator_state_clone.clone();
// Connect mode callback - uses DBus to start animation in daemon
let aura_mode = aura_for_animation.clone();
let handle_weak = handle.as_weak();
handle
.global::<AuraPageData>()
.on_cb_soft_animation_mode(move |mode| {
set_animation_mode(&state_for_mode, AnimationMode::from(mode));
});
let aura_inner = aura_mode.clone();
let handle = match handle_weak.upgrade() {
Some(h) => h,
None => return,
};
// Connect speed callback
let state_for_speed = animator_state_clone.clone();
handle
.global::<AuraPageData>()
.on_cb_soft_animation_speed(move |speed| {
set_animation_speed(&state_for_speed, speed as u32);
let data = handle.global::<AuraPageData>().get_led_mode_data();
let c1 = data.colour1;
let c2 = data.colour2;
let c1_rog = Colour {
r: c1.red(),
g: c1.green(),
b: c1.blue(),
};
let c2_rog = Colour {
r: c2.red(),
g: c2.green(),
b: c2.blue(),
};
let anim_mode = match mode {
1 => AnimationMode::Rainbow { speed_ms: 100 },
2 => AnimationMode::ColorCycle {
speed_ms: 200,
colors: vec![
Colour { r: 255, g: 0, b: 0 },
Colour { r: 0, g: 255, b: 0 },
Colour { r: 0, g: 0, b: 255 },
],
},
3 => AnimationMode::Breathe {
speed_ms: 100,
color1: c1_rog,
color2: c2_rog,
},
4 => AnimationMode::Pulse {
speed_ms: 50,
color: c1_rog,
min_brightness: 0.2,
max_brightness: 1.0,
},
_ => AnimationMode::None,
};
tokio::spawn(async move {
let json =
serde_json::to_string(&anim_mode).unwrap_or_default();
if anim_mode == AnimationMode::None {
if let Err(e) = aura_inner.stop_animation().await {
error!("Failed to stop animation: {e}");
}
} else {
if let Err(e) = aura_inner.start_animation(json).await {
error!("Failed to start animation: {e}");
}
}
});
});
}
})
@@ -204,12 +246,79 @@ pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
"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"
);
let proxy_data = proxy_copy.clone();
let aura_soft = proxy_copy.clone();
let handle_weak = handle.as_weak();
handle
.global::<AuraPageData>()
.on_cb_led_mode_data(move |data| {
// 1. Update hardware mode
let p = proxy_data.clone();
let d = data.clone();
tokio::spawn(async move {
if let Err(e) = p.set_led_mode_data(d.into()).await {
error!("Setting keyboard LED mode failed: {e}");
} else {
debug!("Keyboard LED mode set");
}
});
// 2. Update software animation if active
let handle = match handle_weak.upgrade() {
Some(h) => h,
None => return,
};
let soft_mode = handle.global::<AuraPageData>().get_soft_animation_mode();
if soft_mode != 0 {
let c1 = data.colour1;
let c2 = data.colour2;
let c1_rog = Colour {
r: c1.red(),
g: c1.green(),
b: c1.blue(),
};
let c2_rog = Colour {
r: c2.red(),
g: c2.green(),
b: c2.blue(),
};
let anim_mode = match soft_mode {
1 => AnimationMode::Rainbow { speed_ms: 100 },
2 => AnimationMode::ColorCycle {
speed_ms: 200,
colors: vec![
Colour { r: 255, g: 0, b: 0 },
Colour { r: 0, g: 255, b: 0 },
Colour { r: 0, g: 0, b: 255 },
],
},
3 => AnimationMode::Breathe {
speed_ms: 100,
color1: c1_rog,
color2: c2_rog,
},
4 => AnimationMode::Pulse {
speed_ms: 50,
color: c1_rog,
min_brightness: 0.2,
max_brightness: 1.0,
},
_ => AnimationMode::None,
};
let aura_s = aura_soft.clone();
tokio::spawn(async move {
if let Ok(json) = serde_json::to_string(&anim_mode) {
if let Err(e) = aura_s.start_animation(json).await {
error!("Failed to update software animation: {e}");
}
}
});
}
});
// set_ui_callbacks!(handle,
// AuraPageData(.clone().into()),
-102
View File
@@ -1,102 +0,0 @@
use crate::config::Config;
use crate::ui::show_toast;
use crate::{MainWindow, SupergfxPageData};
use slint::{ComponentHandle, Model, SharedString, VecModel};
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use zbus::proxy;
#[proxy(
interface = "org.supergfxctl.Daemon",
default_service = "org.supergfxctl.Daemon",
default_path = "/org/supergfxctl/Gfx"
)]
trait Supergfx {
fn supported(&self) -> zbus::Result<Vec<String>>;
fn mode(&self) -> zbus::Result<String>;
fn set_mode(&self, mode: &str) -> zbus::Result<()>;
fn vendor(&self) -> zbus::Result<String>;
}
pub fn setup_supergfx(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
let ui_weak = ui.as_weak();
tokio::spawn(async move {
let conn = match zbus::Connection::system().await {
Ok(c) => c,
Err(e) => {
log::warn!("Failed to connect to system bus: {}", e);
return;
}
};
let proxy = match SupergfxProxy::new(&conn).await {
Ok(p) => p,
Err(e) => {
log::warn!("Failed to create Supergfx proxy: {}", e);
return;
}
};
// Register Callbacks on UI Thread
{
let proxy_copy = proxy.clone();
let ui_weak_copy = ui_weak.clone();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let handle_copy = ui_weak_copy.clone();
ui.global::<SupergfxPageData>()
.on_set_mode(move |mode_str| {
let proxy = proxy_copy.clone();
let handle = handle_copy.clone();
tokio::spawn(async move {
show_toast(
format!("Switching to {}. Logout required.", mode_str).into(),
"Failed to set mode".into(),
handle,
proxy.set_mode(&mode_str).await,
);
});
});
});
}
// Fetch Initial State
// Vendor
if let Ok(vendor) = proxy.vendor().await {
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
ui.global::<SupergfxPageData>().set_vendor(vendor.into())
});
}
// Supported Modes
if let Ok(supported) = proxy.supported().await {
let modes: Vec<SharedString> = supported
.iter()
.map(|s| SharedString::from(s.as_str()))
.collect();
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let mode_model = Rc::new(VecModel::from(modes));
ui.global::<SupergfxPageData>()
.set_supported_modes(mode_model.into())
});
}
// Current Mode
if let Ok(mode) = proxy.mode().await {
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
let g = ui.global::<SupergfxPageData>();
g.set_current_mode(mode.clone().into());
// Update selection index
let model = g.get_supported_modes();
for (i, m) in model.iter().enumerate() {
if m == mode.as_str() {
g.set_selected_index(i as i32);
break;
}
}
});
}
// No signal monitoring implemented as supergfxctl state changes usually require user action/logout
});
}