mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
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:
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user