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

View File

@@ -47,6 +47,7 @@ concat-idents.workspace = true
futures-util.workspace = true
versions.workspace = true
serde_json = "1.0.149"
[dependencies.slint]
git = "https://github.com/slint-ui/slint.git"

View File

@@ -8,7 +8,6 @@ use rog_platform::supported::{
PlatformProfileFunctions, RogBiosSupportedFunctions, SupportedFunctions,
};
use rog_profiles::fan_curve_set::{CurveData, FanCurveSet};
use supergfxctl::pci_device::{GfxMode, GfxPower};
use crate::error::Result;

View File

@@ -1,4 +1,4 @@
//! `update_and_notify` is responsible for both notifications *and* updating
//! update_and_notify is responsible for both notifications and updating
//! stored statuses about the system state. This is done through either direct,
//! intoify, zbus notifications or similar methods.
//!
@@ -15,7 +15,6 @@ use rog_dbus::zbus_platform::PlatformProxy;
use rog_platform::platform::PlatformProfile;
use rog_platform::power::AsusPower;
use serde::{Deserialize, Serialize};
use supergfxctl::pci_device::GfxPower;
use tokio::runtime::Runtime;
use tokio::task::JoinHandle;
@@ -28,8 +27,6 @@ const NOTIF_HEADER: &str = "ROG Control";
#[serde(default)]
pub struct EnabledNotifications {
pub enabled: bool,
pub receive_notify_gfx: bool,
pub receive_notify_gfx_status: bool,
pub receive_notify_platform_profile: bool,
}
@@ -37,59 +34,11 @@ impl Default for EnabledNotifications {
fn default() -> Self {
Self {
enabled: true,
receive_notify_gfx: true,
receive_notify_gfx_status: true,
receive_notify_platform_profile: true,
}
}
}
fn start_dpu_status_mon(config: Arc<Mutex<Config>>) {
use supergfxctl::pci_device::Device;
let dev = Device::find().unwrap_or_default();
let mut found_dgpu = false; // just for logging
for dev in dev {
if dev.is_dgpu() {
info!(
"Found dGPU: {}, starting status notifications",
dev.pci_id()
);
let enabled_notifications_copy = config.clone();
// Plain old thread is perfectly fine since most of this is potentially blocking
std::thread::spawn(move || {
let mut last_status = GfxPower::Unknown;
loop {
std::thread::sleep(Duration::from_millis(1500));
if let Ok(status) = dev.get_runtime_status() {
if status != GfxPower::Unknown && status != last_status {
if let Ok(config) = enabled_notifications_copy.lock() {
if !config.notifications.receive_notify_gfx_status
|| !config.notifications.enabled
{
continue;
}
}
// Required check because status cycles through
// active/unknown/suspended
do_gpu_status_notif("dGPU status changed:", &status)
.show()
.unwrap()
.on_close(|_| ());
debug!("dGPU status changed: {:?}", &status);
}
last_status = status;
}
}
});
found_dgpu = true;
break;
}
}
if !found_dgpu {
warn!("Did not find a dGPU on this system, dGPU status won't be avilable");
}
}
/// Start monitoring for platform profile changes (triggered by Fn+F5 or software)
/// and display an OSD notification when the profile changes.
fn start_platform_profile_mon(config: Arc<Mutex<Config>>, rt: &Runtime) {
@@ -196,49 +145,9 @@ pub fn start_notifications(
}
});
info!("Attempting to start plain dgpu status monitor");
start_dpu_status_mon(config.clone());
info!("Starting platform profile change monitor");
start_platform_profile_mon(config.clone(), rt);
// GPU MUX Mode notif
// TODO: need to get armoury attrs and iter to find
// let enabled_notifications_copy = config.clone();
// tokio::spawn(async move {
// let conn = zbus::Connection::system().await.map_err(|e| {
// error!("zbus signal: receive_notify_gpu_mux_mode: {e}");
// e
// })?;
// let proxy = PlatformProxy::new(&conn).await.map_err(|e| {
// error!("zbus signal: receive_notify_gpu_mux_mode: {e}");
// e
// })?;
// let mut actual_mux_mode = GpuMode::Error;
// if let Ok(mode) = proxy.gpu_mux_mode().await {
// actual_mux_mode = GpuMode::from(mode);
// }
// info!("Started zbus signal thread: receive_notify_gpu_mux_mode");
// while let Some(e) =
// proxy.receive_gpu_mux_mode_changed().await.next().await { if let
// Ok(config) = enabled_notifications_copy.lock() { if
// !config.notifications.enabled || !config.notifications.receive_notify_gfx {
// continue;
// }
// }
// if let Ok(out) = e.get().await {
// let mode = GpuMode::from(out);
// if mode == actual_mux_mode {
// continue;
// }
// do_mux_notification("Reboot required. BIOS GPU MUX mode set to",
// &mode).ok(); }
// }
// Ok::<(), zbus::Error>(())
// });
Ok(vec![blocking])
}
@@ -255,19 +164,6 @@ where
notif
}
fn do_gpu_status_notif(message: &str, data: &GfxPower) -> Notification {
let mut notif = base_notification(message, &<&str>::from(data).to_owned());
let icon = match data {
GfxPower::Suspended => "asus_notif_blue",
GfxPower::Off => "asus_notif_green",
GfxPower::AsusDisabled => "asus_notif_white",
GfxPower::AsusMuxDiscreet | GfxPower::Active => "asus_notif_red",
GfxPower::Unknown => "gpu-integrated",
};
notif.icon(icon);
notif
}
/// Create a notification for platform profile (power mode) changes.
/// Uses profile-specific icons and user-friendly names.
fn do_platform_profile_notif(message: &str, profile: &PlatformProfile) -> Notification {

View File

@@ -6,12 +6,9 @@ use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, OnceLock};
use std::time::Duration;
use ksni::{Handle, Icon, TrayMethods};
use log::{info, warn};
use ksni::{Icon, TrayMethods};
use log::info;
use rog_platform::platform::Properties;
use supergfxctl::pci_device::{Device, GfxMode, GfxPower};
use supergfxctl::zbus_proxy::DaemonProxy as GfxProxy;
use versions::Versioning;
use crate::config::Config;
use crate::zbus_proxies::{AppState, ROGCCZbusProxyBlocking};
@@ -20,11 +17,8 @@ const TRAY_LABEL: &str = "ROG Control Center";
const TRAY_ICON_PATH: &str = "/usr/share/icons/hicolor/512x512/apps/";
struct Icons {
rog_blue: Icon,
#[allow(dead_code)]
rog_red: Icon,
rog_green: Icon,
rog_white: Icon,
gpu_integrated: Icon,
}
static ICONS: OnceLock<Icons> = OnceLock::new();
@@ -145,58 +139,6 @@ impl ksni::Tray for AsusTray {
}
}
async fn set_tray_icon_and_tip(
mode: GfxMode,
power: GfxPower,
tray: &mut Handle<AsusTray>,
supergfx_active: bool,
) {
if let Some(icons) = ICONS.get() {
let icon = match power {
GfxPower::Suspended => icons.rog_blue.clone(),
GfxPower::Off => {
if mode == GfxMode::Vfio {
icons.rog_red.clone()
} else {
icons.rog_green.clone()
}
}
GfxPower::AsusDisabled => icons.rog_white.clone(),
GfxPower::AsusMuxDiscreet | GfxPower::Active => icons.rog_red.clone(),
GfxPower::Unknown => {
if supergfx_active {
icons.gpu_integrated.clone()
} else {
icons.rog_red.clone()
}
}
};
tray.update(|tray: &mut AsusTray| {
tray.current_icon = icon;
tray.current_title = format!(
"ROG: gpu mode = {mode:?}, gpu power =
{power:?}"
);
})
.await;
}
}
fn find_dgpu() -> Option<Device> {
use supergfxctl::pci_device::Device;
let dev = Device::find().unwrap_or_default();
for dev in dev {
if dev.is_dgpu() {
info!("Found dGPU: {}", dev.pci_id());
// Plain old thread is perfectly fine since most of this is potentially blocking
return Some(dev);
}
}
warn!("Did not find a dGPU on this system, dGPU status won't be avilable");
None
}
/// The tray is controlled somewhat by `Arc<Mutex<SystemState>>`
pub fn init_tray(
_supported_properties: Vec<Properties>,
@@ -225,7 +167,7 @@ pub fn init_tray(
};
// TODO: return an error to the UI
let mut tray;
let tray;
match tray_init.disable_dbus_name(true).spawn().await {
Ok(t) => tray = t,
Err(e) => {
@@ -237,57 +179,19 @@ pub fn init_tray(
}
info!("Tray started");
let rog_blue = read_icon(&PathBuf::from("asus_notif_blue.png"));
let rog_green = read_icon(&PathBuf::from("asus_notif_green.png"));
let rog_white = read_icon(&PathBuf::from("asus_notif_white.png"));
let gpu_integrated = read_icon(&PathBuf::from("rog-control-center.png"));
ICONS.get_or_init(|| Icons {
rog_blue,
rog_red: rog_red.clone(),
rog_green,
rog_white,
gpu_integrated,
});
let mut has_supergfx = false;
let conn = zbus::Connection::system().await.unwrap();
if let Ok(gfx_proxy) = GfxProxy::new(&conn).await {
match gfx_proxy.mode().await {
Ok(_) => {
has_supergfx = true;
if let Ok(version) = gfx_proxy.version().await {
if let Some(version) = Versioning::new(&version) {
let curr_gfx = Versioning::new("5.2.0").unwrap();
warn!("supergfxd version = {version}");
if version < curr_gfx {
// Don't allow mode changing if too old a version
warn!("supergfxd found but is too old to use");
has_supergfx = false;
}
}
}
}
Err(e) => match e {
zbus::Error::MethodError(_, _, message) => {
warn!(
"Couldn't get mode from supergfxd: {message:?}, the supergfxd service \
may not be running or installed"
)
}
_ => warn!("Couldn't get mode from supergfxd: {e:?}"),
},
}
info!("Started ROGTray");
info!("Started ROGTray");
let mut last_power = GfxPower::Unknown;
let dev = find_dgpu();
// Loop with select! to handle both periodic checks and stats updates
loop {
tokio::select! {
_ = stats_rx.changed() => {
let stats = stats_rx.borrow().clone();
tray.update(move |t| {
loop {
tokio::select! {
_ = stats_rx.changed() => {
let stats = stats_rx.borrow().clone();
let tray_update = tray.clone();
tokio::spawn(async move {
tray_update.update(move |t| {
t.cpu_temp = stats.cpu_temp;
t.gpu_temp = stats.gpu_temp;
t.cpu_fan = stats.cpu_fan;
@@ -295,31 +199,12 @@ pub fn init_tray(
t.power_w = stats.power_w;
t.power_profile = stats.power_profile;
}).await;
}
_ = tokio::time::sleep(Duration::from_millis(1000)) => {
if let Ok(lock) = config.try_lock() {
if !lock.enable_tray_icon {
return;
}
}
// Handle GPU icon updates
if has_supergfx {
if let Ok(mode) = gfx_proxy.mode().await {
if let Ok(power) = gfx_proxy.power().await {
if last_power != power {
set_tray_icon_and_tip(mode, power, &mut tray, has_supergfx).await;
last_power = power;
}
}
}
} else if let Some(dev) = dev.as_ref() {
if let Ok(power) = dev.get_runtime_status() {
if last_power != power {
set_tray_icon_and_tip(GfxMode::Hybrid, power, &mut tray, has_supergfx)
.await;
last_power = power;
}
}
});
}
_ = tokio::time::sleep(Duration::from_millis(1000)) => {
if let Ok(lock) = config.try_lock() {
if !lock.enable_tray_icon {
return;
}
}
}

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);
}

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);
}
}

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()),

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
});
}

View File

@@ -1,9 +1,28 @@
import { SystemDropdown, RogItem, SystemToggle, SystemToggleVert } from "../widgets/common.slint";
import {
SystemDropdown,
RogItem,
SystemToggle,
SystemToggleVert,
} from "../widgets/common.slint";
import { Button, ComboBox, VerticalBox, GroupBox } from "std-widgets.slint";
import { RogPalette } from "../themes/rog_theme.slint";
import { StyleMetrics, Slider, HorizontalBox, TextEdit, SpinBox, LineEdit, ScrollView } from "std-widgets.slint";
import {
StyleMetrics,
Slider,
HorizontalBox,
TextEdit,
SpinBox,
LineEdit,
ScrollView,
} from "std-widgets.slint";
import { ColourSlider } from "../widgets/colour_picker.slint";
import { AuraPageData, AuraDevType, PowerZones, LaptopAuraPower, AuraEffect } from "../types/aura_types.slint";
import {
AuraPageData,
AuraDevType,
PowerZones,
LaptopAuraPower,
AuraEffect,
} from "../types/aura_types.slint";
import { AuraPowerGroup, AuraPowerGroupOld } from "../widgets/aura_power.slint";
export component PageAura inherits Rectangle {
@@ -190,22 +209,21 @@ export component PageAura inherits Rectangle {
VerticalLayout {
padding: 10px;
spacing: 8px;
Text {
text: @tr("Software Animation (Static-only keyboards)");
font-size: 14px;
font-weight: 600;
color: RogPalette.accent;
}
HorizontalLayout {
spacing: 20px;
VerticalLayout {
Text {
text: @tr("Animation Mode");
color: RogPalette.text-secondary;
}
ComboBox {
current_index <=> AuraPageData.soft_animation_mode;
current_value: AuraPageData.soft_animation_modes[self.current-index];
@@ -215,22 +233,6 @@ export component PageAura inherits Rectangle {
}
}
}
VerticalLayout {
horizontal-stretch: 1;
Text {
text: @tr("Speed: ") + Math.round(AuraPageData.soft_animation_speed) + "ms";
color: RogPalette.text-secondary;
}
Slider {
minimum: 150;
maximum: 1000;
value <=> AuraPageData.soft_animation_speed;
released => {
AuraPageData.cb_soft_animation_speed(Math.round(AuraPageData.soft_animation_speed));
}
}
}
}
}
}

View File

@@ -179,6 +179,8 @@ export global AuraPageData {
@tr("Animation mode" => "None"),
@tr("Animation mode" => "Rainbow"),
@tr("Animation mode" => "Color Cycle"),
@tr("Animation mode" => "Breathe"),
@tr("Animation mode" => "Pulse"),
];
in-out property <int> soft_animation_mode: 0;
in-out property <float> soft_animation_speed: 200; // ms between updates