mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
feat(rog-control-center): Implement logic for fans, aura, and monitoring
This commit is contained in:
6
diagnose.sh
Executable file
6
diagnose.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
git status > status.txt 2>&1
|
||||
echo "--- LOG ---" >> status.txt
|
||||
git log -1 >> status.txt 2>&1
|
||||
echo "--- REBASE DIR ---" >> status.txt
|
||||
ls -d .git/rebase* >> status.txt 2>&1
|
||||
@@ -29,6 +29,7 @@ rog_dbus = { path = "../rog-dbus" }
|
||||
rog_aura = { path = "../rog-aura" }
|
||||
rog_profiles = { path = "../rog-profiles" }
|
||||
rog_platform = { path = "../rog-platform" }
|
||||
rog_slash = { path = "../rog-slash" }
|
||||
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", default-features = false }
|
||||
dmi_id = { path = "../dmi-id" }
|
||||
|
||||
@@ -39,12 +40,14 @@ env_logger.workspace = true
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
zbus.workspace = true
|
||||
ron.workspace = true
|
||||
dirs.workspace = true
|
||||
notify-rust.workspace = true
|
||||
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"
|
||||
|
||||
@@ -11,16 +11,21 @@ use gumdrop::Options;
|
||||
use log::{debug, info, warn, LevelFilter};
|
||||
use rog_control_center::cli_options::CliStart;
|
||||
use rog_control_center::config::Config;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
thread_local! {
|
||||
pub static UI: std::cell::RefCell<Option<MainWindow>> = Default::default();
|
||||
pub static TRAY_TOOLTIP: std::cell::RefCell<Option<TrayTooltip>> = Default::default();
|
||||
}
|
||||
use rog_control_center::error::Result;
|
||||
use rog_control_center::notify::start_notifications;
|
||||
use rog_control_center::slint::ComponentHandle;
|
||||
use rog_control_center::tray::init_tray;
|
||||
use rog_control_center::tray::{init_tray, TrayEvent, TrayStats};
|
||||
use rog_control_center::ui::setup_window;
|
||||
use rog_control_center::zbus_proxies::{
|
||||
AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH,
|
||||
};
|
||||
use rog_control_center::{print_versions, MainWindow};
|
||||
use tokio::runtime::Runtime;
|
||||
use rog_control_center::{print_versions, MainWindow, TrayTooltip};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
@@ -165,17 +170,33 @@ async fn main() -> Result<()> {
|
||||
|
||||
start_notifications(config.clone(), &rt)?;
|
||||
|
||||
if enable_tray_icon {
|
||||
init_tray(supported_properties, config.clone());
|
||||
}
|
||||
let (tray_tx, mut tray_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
// Channel for broadcasting system stats to the tray tooltip
|
||||
let (stats_tx, stats_rx) = tokio::sync::watch::channel(TrayStats::default());
|
||||
|
||||
thread_local! { pub static UI: std::cell::RefCell<Option<MainWindow>> = Default::default()};
|
||||
if enable_tray_icon {
|
||||
init_tray(supported_properties, config.clone(), tray_tx, stats_rx);
|
||||
}
|
||||
// i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();
|
||||
|
||||
if !startup_in_background {
|
||||
if let Ok(mut app_state) = app_state.lock() {
|
||||
*app_state = AppState::MainWindowShouldOpen;
|
||||
}
|
||||
} else {
|
||||
// Even in background, we need the UI handle for status polling and tray sync
|
||||
let config_copy = config.clone();
|
||||
let stats_tx_copy = stats_tx.clone();
|
||||
slint::invoke_from_event_loop(move || {
|
||||
UI.with(|ui_cell| {
|
||||
let mut ui = ui_cell.borrow_mut();
|
||||
if ui.is_none() {
|
||||
let newui = setup_window(config_copy, stats_tx_copy.clone());
|
||||
ui.replace(newui);
|
||||
}
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
if std::env::var("RUST_TRANSLATIONS").is_ok() {
|
||||
@@ -187,18 +208,21 @@ async fn main() -> Result<()> {
|
||||
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 || {
|
||||
let mut state = AppState::StartingUp;
|
||||
loop {
|
||||
// Handle tray events
|
||||
while let Ok(event) = tray_rx.try_recv() {
|
||||
match event {
|
||||
TrayEvent::ToggleTooltip(_, _) => {
|
||||
// Native tooltip handled by ksni, no custom window needed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_rog_ally {
|
||||
let config_copy_2 = config.clone();
|
||||
let newui = setup_window(config.clone(), prefetched_supported.clone());
|
||||
let newui = setup_window(config.clone(), stats_tx.clone());
|
||||
newui.window().on_close_requested(move || {
|
||||
exit(0);
|
||||
});
|
||||
@@ -239,9 +263,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
let config_copy = config.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();
|
||||
let stats_tx_loop = stats_tx.clone();
|
||||
slint::invoke_from_event_loop(move || {
|
||||
UI.with(|ui| {
|
||||
let app_state_copy = app_state_copy.clone();
|
||||
@@ -256,7 +278,7 @@ async fn main() -> Result<()> {
|
||||
});
|
||||
} else {
|
||||
let config_copy_2 = config_copy.clone();
|
||||
let newui = setup_window(config_copy, pref_for_invoke.clone());
|
||||
let newui = setup_window(config_copy, stats_tx_loop.clone());
|
||||
newui.window().on_close_requested(move || {
|
||||
if let Ok(mut app_state) = app_state_copy.lock() {
|
||||
*app_state = AppState::MainWindowClosed;
|
||||
|
||||
@@ -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.
|
||||
//!
|
||||
@@ -11,9 +11,10 @@ use std::time::Duration;
|
||||
|
||||
use log::{debug, error, info, warn};
|
||||
use notify_rust::{Hint, Notification, Timeout};
|
||||
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;
|
||||
|
||||
@@ -26,64 +27,67 @@ 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,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
/// 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) {
|
||||
let enabled_notifications_copy = config.clone();
|
||||
rt.spawn(async move {
|
||||
let conn = match zbus::Connection::system().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
error!("zbus signal: platform_profile_mon: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let proxy = match PlatformProxy::builder(&conn).build().await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
error!("zbus signal: platform_profile_mon proxy: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Get initial profile to avoid notification on startup
|
||||
let mut last_profile = proxy.platform_profile().await.ok();
|
||||
|
||||
info!("Started platform profile change monitor");
|
||||
use futures_util::StreamExt;
|
||||
let mut stream = proxy.receive_platform_profile_changed().await;
|
||||
while let Some(e) = stream.next().await {
|
||||
if let Ok(config) = enabled_notifications_copy.lock() {
|
||||
if !config.notifications.enabled
|
||||
|| !config.notifications.receive_notify_platform_profile
|
||||
{
|
||||
continue;
|
||||
}
|
||||
});
|
||||
found_dgpu = true;
|
||||
break;
|
||||
}
|
||||
if let Ok(new_profile) = e.get().await {
|
||||
// Only show notification if profile actually changed
|
||||
if last_profile != Some(new_profile) {
|
||||
debug!("Platform profile changed to: {:?}", new_profile);
|
||||
if let Err(e) = do_platform_profile_notif("Power Profile:", &new_profile)
|
||||
.show()
|
||||
.map(|n| n.on_close(|_| ()))
|
||||
{
|
||||
warn!("Failed to show platform profile notification: {e}");
|
||||
}
|
||||
last_profile = Some(new_profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found_dgpu {
|
||||
warn!("Did not find a dGPU on this system, dGPU status won't be avilable");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn start_notifications(
|
||||
@@ -141,45 +145,8 @@ pub fn start_notifications(
|
||||
}
|
||||
});
|
||||
|
||||
info!("Attempting to start plain dgpu status monitor");
|
||||
start_dpu_status_mon(config.clone());
|
||||
|
||||
// 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>(())
|
||||
// });
|
||||
info!("Starting platform profile change monitor");
|
||||
start_platform_profile_mon(config.clone(), rt);
|
||||
|
||||
Ok(vec![blocking])
|
||||
}
|
||||
@@ -197,14 +164,26 @@ 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",
|
||||
/// 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 {
|
||||
let profile_name = match profile {
|
||||
PlatformProfile::Balanced => "Balanced",
|
||||
PlatformProfile::Performance => "Performance",
|
||||
PlatformProfile::Quiet => "Quiet",
|
||||
PlatformProfile::LowPower => "Low Power",
|
||||
PlatformProfile::Custom => "Custom",
|
||||
};
|
||||
let mut notif = base_notification(message, &profile_name.to_owned());
|
||||
|
||||
// Use appropriate icons for each profile
|
||||
// These icons should be available in the system or ROG icon pack
|
||||
let icon = match profile {
|
||||
PlatformProfile::Balanced => "asus_notif_blue", // Blue for balanced
|
||||
PlatformProfile::Performance => "asus_notif_red", // Red for performance
|
||||
PlatformProfile::Quiet => "asus_notif_green", // Green for quiet/power saving
|
||||
PlatformProfile::LowPower => "asus_notif_green", // Green for low power
|
||||
PlatformProfile::Custom => "asus_notif_white", // White for custom
|
||||
};
|
||||
notif.icon(icon);
|
||||
notif
|
||||
|
||||
@@ -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();
|
||||
@@ -59,6 +53,29 @@ struct AsusTray {
|
||||
current_title: String,
|
||||
current_icon: Icon,
|
||||
proxy: ROGCCZbusProxyBlocking<'static>,
|
||||
tray_channel: Option<tokio::sync::mpsc::UnboundedSender<TrayEvent>>,
|
||||
// System stats for native tooltip
|
||||
cpu_temp: String,
|
||||
gpu_temp: String,
|
||||
cpu_fan: String,
|
||||
gpu_fan: String,
|
||||
power_w: String,
|
||||
power_profile: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TrayEvent {
|
||||
ToggleTooltip(i32, i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TrayStats {
|
||||
pub cpu_temp: String,
|
||||
pub gpu_temp: String,
|
||||
pub cpu_fan: String,
|
||||
pub gpu_fan: String,
|
||||
pub power_w: String,
|
||||
pub power_profile: String,
|
||||
}
|
||||
|
||||
impl ksni::Tray for AsusTray {
|
||||
@@ -78,6 +95,26 @@ impl ksni::Tray for AsusTray {
|
||||
ksni::Status::Active
|
||||
}
|
||||
|
||||
fn activate(&mut self, x: i32, y: i32) {
|
||||
if let Some(tx) = &self.tray_channel {
|
||||
let _ = tx.send(TrayEvent::ToggleTooltip(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
fn tool_tip(&self) -> ksni::ToolTip {
|
||||
ksni::ToolTip {
|
||||
title: "ROG Control Center".into(),
|
||||
description: format!(
|
||||
"<b>Profile:</b> {}\n<b>CPU:</b> {}°C | <b>GPU:</b> {}°C\n<b>Fans:</b> {} / {} RPM\n<b>Power:</b> {} W",
|
||||
self.power_profile,
|
||||
self.cpu_temp, self.gpu_temp,
|
||||
self.cpu_fan, self.gpu_fan,
|
||||
self.power_w
|
||||
),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
|
||||
use ksni::menu::*;
|
||||
vec![
|
||||
@@ -102,60 +139,13 @@ 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>, config: Arc<Mutex<Config>>) {
|
||||
pub fn init_tray(
|
||||
_supported_properties: Vec<Properties>,
|
||||
config: Arc<Mutex<Config>>,
|
||||
tray_channel: tokio::sync::mpsc::UnboundedSender<TrayEvent>,
|
||||
mut stats_rx: tokio::sync::watch::Receiver<TrayStats>,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
let user_con = zbus::blocking::Connection::session().unwrap();
|
||||
let proxy = ROGCCZbusProxyBlocking::new(&user_con).unwrap();
|
||||
@@ -166,10 +156,18 @@ pub fn init_tray(_supported_properties: Vec<Properties>, config: Arc<Mutex<Confi
|
||||
current_title: TRAY_LABEL.to_string(),
|
||||
current_icon: rog_red.clone(),
|
||||
proxy,
|
||||
tray_channel: Some(tray_channel),
|
||||
// Initialize stats fields
|
||||
cpu_temp: "--".into(),
|
||||
gpu_temp: "--".into(),
|
||||
cpu_fan: "--".into(),
|
||||
gpu_fan: "--".into(),
|
||||
power_w: "--".into(),
|
||||
power_profile: "Unknown".into(),
|
||||
};
|
||||
|
||||
// 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) => {
|
||||
@@ -181,72 +179,32 @@ pub fn init_tray(_supported_properties: Vec<Properties>, config: Arc<Mutex<Confi
|
||||
}
|
||||
|
||||
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 {
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
if let Ok(lock) = config.try_lock() {
|
||||
if !lock.enable_tray_icon {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
t.gpu_fan = stats.gpu_fan;
|
||||
t.power_w = stats.power_w;
|
||||
t.power_profile = stats.power_profile;
|
||||
}).await;
|
||||
});
|
||||
}
|
||||
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,6 +1,10 @@
|
||||
pub mod setup_anime;
|
||||
pub mod setup_aura;
|
||||
pub mod setup_fan_curve_custom;
|
||||
pub mod setup_fans;
|
||||
pub mod setup_screenpad;
|
||||
pub mod setup_slash;
|
||||
pub mod setup_status;
|
||||
pub mod setup_system;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -11,9 +15,13 @@ use rog_dbus::list_iface_blocking;
|
||||
use slint::{ComponentHandle, SharedString, Weak};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::tray::TrayStats;
|
||||
use crate::ui::setup_anime::setup_anime_page;
|
||||
use crate::ui::setup_aura::setup_aura_page;
|
||||
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_system::{setup_system_page, setup_system_page_callbacks};
|
||||
use crate::{AppSettingsPageData, MainWindow};
|
||||
|
||||
@@ -84,7 +92,7 @@ pub fn show_toast(
|
||||
|
||||
pub fn setup_window(
|
||||
config: Arc<Mutex<Config>>,
|
||||
prefetched_supported: std::sync::Arc<Option<Vec<i32>>>,
|
||||
stats_tx: tokio::sync::watch::Sender<TrayStats>,
|
||||
) -> MainWindow {
|
||||
slint::set_xdg_app_id("rog-control-center")
|
||||
.map_err(|e| warn!("Couldn't set application ID: {e:?}"))
|
||||
@@ -104,10 +112,14 @@ pub fn setup_window(
|
||||
available.contains(&"xyz.ljones.Platform".to_string()),
|
||||
available.contains(&"xyz.ljones.Aura".to_string()),
|
||||
available.contains(&"xyz.ljones.Anime".to_string()),
|
||||
available.contains(&"xyz.ljones.Slash".to_string()),
|
||||
false,
|
||||
// Screenpad check (Backlight interface)
|
||||
available.contains(&"xyz.ljones.Backlight".to_string()),
|
||||
available.contains(&"xyz.ljones.FanCurves".to_string()),
|
||||
true, // GPU Configuration
|
||||
true, // App Settings
|
||||
true, // About
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
]
|
||||
.into(),
|
||||
);
|
||||
@@ -116,21 +128,60 @@ pub fn setup_window(
|
||||
slint::quit_event_loop().unwrap();
|
||||
});
|
||||
|
||||
// Auto-hide toast logic
|
||||
let toast_gen = Arc::new(Mutex::new(0u64));
|
||||
let ui_weak = ui.as_weak();
|
||||
ui.on_start_toast_timer(move || {
|
||||
let toast_gen_clone = toast_gen.clone();
|
||||
let ui_weak_clone = ui_weak.clone();
|
||||
let my_gen = {
|
||||
if let Ok(mut g) = toast_gen.lock() {
|
||||
*g += 1;
|
||||
*g
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
if my_gen > 0 {
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(4)).await;
|
||||
if let Ok(g) = toast_gen_clone.lock() {
|
||||
if *g == my_gen {
|
||||
let _ =
|
||||
ui_weak_clone.upgrade_in_event_loop(move |ui| ui.invoke_hide_toast());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setup_app_settings_page(&ui, config.clone());
|
||||
if available.contains(&"xyz.ljones.Platform".to_string()) {
|
||||
setup_system_page(&ui, config.clone());
|
||||
setup_system_page_callbacks(&ui, config.clone());
|
||||
}
|
||||
if available.contains(&"xyz.ljones.Aura".to_string()) {
|
||||
setup_aura_page(&ui, config.clone(), prefetched_supported.as_ref().clone());
|
||||
setup_aura_page(&ui, config.clone());
|
||||
}
|
||||
if available.contains(&"xyz.ljones.Anime".to_string()) {
|
||||
setup_anime_page(&ui, config.clone());
|
||||
}
|
||||
if available.contains(&"xyz.ljones.FanCurves".to_string()) {
|
||||
setup_fan_curve_page(&ui, config);
|
||||
if available.contains(&"xyz.ljones.Slash".to_string()) {
|
||||
setup_slash(&ui, config.clone());
|
||||
}
|
||||
|
||||
// We didn't capture the boolean above. Let's just run it, it handles its own availability check internally via async proxy creation.
|
||||
|
||||
if available.contains(&"xyz.ljones.Backlight".to_string()) {
|
||||
setup_screenpad(&ui, config.clone());
|
||||
}
|
||||
|
||||
if available.contains(&"xyz.ljones.FanCurves".to_string()) {
|
||||
setup_fan_curve_page(&ui, config.clone());
|
||||
}
|
||||
|
||||
setup_status(&ui, config, stats_tx);
|
||||
|
||||
ui
|
||||
}
|
||||
|
||||
@@ -157,18 +208,31 @@ pub fn setup_app_settings_page(ui: &MainWindow, config: Arc<Mutex<Config>>) {
|
||||
lock.write();
|
||||
}
|
||||
});
|
||||
|
||||
// Master notifications toggle
|
||||
let config_copy = config.clone();
|
||||
global.on_set_enable_dgpu_notifications(move |enable| {
|
||||
global.on_set_notifications_enabled(move |enable| {
|
||||
if let Ok(mut lock) = config_copy.try_lock() {
|
||||
lock.notifications.enabled = enable;
|
||||
lock.write();
|
||||
}
|
||||
});
|
||||
|
||||
// Granular notification toggles
|
||||
let config_copy = config.clone();
|
||||
global.on_set_notify_platform_profile(move |enable| {
|
||||
if let Ok(mut lock) = config_copy.try_lock() {
|
||||
lock.notifications.receive_notify_platform_profile = enable;
|
||||
lock.write();
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize UI values from config
|
||||
if let Ok(lock) = config.try_lock() {
|
||||
global.set_run_in_background(lock.run_in_background);
|
||||
global.set_startup_in_background(lock.startup_in_background);
|
||||
global.set_enable_tray_icon(lock.enable_tray_icon);
|
||||
global.set_enable_dgpu_notifications(lock.notifications.enabled);
|
||||
global.set_notifications_enabled(lock.notifications.enabled);
|
||||
global.set_notify_platform_profile(lock.notifications.receive_notify_platform_profile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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};
|
||||
|
||||
@@ -34,108 +35,63 @@ 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>> {
|
||||
let conn = zbus::Connection::system().await?;
|
||||
let mgr = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?;
|
||||
let objs = mgr.get_managed_objects().await?;
|
||||
let mut paths: Vec<zbus::zvariant::OwnedObjectPath> = objs
|
||||
.iter()
|
||||
.filter(|(_, ifaces)| ifaces.keys().any(|k| k.as_str() == "xyz.ljones.Aura"))
|
||||
.map(|(p, _)| p.clone())
|
||||
.collect();
|
||||
if paths.len() > 1 {
|
||||
log::debug!("Multiple aura devices: {paths:?}");
|
||||
let f = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?;
|
||||
let interfaces = f.get_managed_objects().await?;
|
||||
let mut aura_paths = Vec::new();
|
||||
for v in interfaces.iter() {
|
||||
for k in v.1.keys() {
|
||||
if k.as_str() == "xyz.ljones.Aura" {
|
||||
println!("Found aura device at {}, {}", v.0, k);
|
||||
aura_paths.push(v.0.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let path = paths.pop().ok_or("No Aura interface")?;
|
||||
AuraProxy::builder(&conn)
|
||||
.path(path)?
|
||||
.destination("xyz.ljones.Asusd")?
|
||||
.build()
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
if aura_paths.len() > 1 {
|
||||
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")?
|
||||
.build()
|
||||
.await?);
|
||||
}
|
||||
|
||||
Err("No Aura interface".into())
|
||||
}
|
||||
|
||||
pub async fn prefetch_supported_basic_modes() -> Option<Vec<i32>> {
|
||||
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| {
|
||||
pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
|
||||
ui.global::<AuraPageData>().on_cb_hex_from_colour(|c| {
|
||||
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();
|
||||
tokio::spawn(async move {
|
||||
let Ok(aura) = find_aura_iface().await else {
|
||||
info!("No aura interfaces");
|
||||
info!("This device appears to have no aura interfaces");
|
||||
return Ok::<(), zbus::Error>(());
|
||||
};
|
||||
|
||||
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, device_type);
|
||||
|
||||
if let Ok(data) = aura.led_mode_data().await {
|
||||
let d = data.into();
|
||||
handle
|
||||
.upgrade_in_event_loop(move |h| {
|
||||
h.global::<AuraPageData>().invoke_update_led_mode_data(d);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
let current_mode: Option<i32> = aura.led_mode().await.ok().map(|m| m.into());
|
||||
|
||||
handle
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
let names = handle.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());
|
||||
}
|
||||
}
|
||||
handle
|
||||
.global::<AuraPageData>()
|
||||
.set_supported_basic_modes(raws.as_slice().into());
|
||||
handle
|
||||
.global::<AuraPageData>()
|
||||
.set_available_mode_names(mode_names.as_slice().into());
|
||||
if let Some(cm) = current_mode {
|
||||
let idx = raws.iter().position(|&r| r == cm).unwrap_or(0) as i32;
|
||||
handle
|
||||
.global::<AuraPageData>()
|
||||
.set_current_available_mode(idx);
|
||||
}
|
||||
})
|
||||
.map_err(|e| error!("{e}"))
|
||||
.ok();
|
||||
|
||||
if let Ok(mut pow3r) = aura.supported_power_zones().await {
|
||||
let dev = aura
|
||||
let dev_type = aura
|
||||
.device_type()
|
||||
.await
|
||||
.unwrap_or(AuraDeviceType::LaptopKeyboard2021);
|
||||
log::debug!("Available LED power modes {pow3r:?}");
|
||||
handle
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
let names: Vec<SharedString> = handle
|
||||
@@ -143,103 +99,282 @@ pub fn setup_aura_page(
|
||||
.get_power_zone_names()
|
||||
.iter()
|
||||
.collect();
|
||||
if dev.is_old_laptop() {
|
||||
|
||||
if dev_type.is_old_laptop() {
|
||||
// Need to add the specific KeyboardAndLightbar
|
||||
if pow3r.contains(&PowerZones::Keyboard)
|
||||
&& pow3r.contains(&PowerZones::Lightbar)
|
||||
{
|
||||
pow3r.push(PowerZones::KeyboardAndLightbar);
|
||||
}
|
||||
let n: Vec<SharedString> =
|
||||
pow3r.iter().map(|z| names[(*z) as usize].clone()).collect();
|
||||
let names: Vec<SharedString> =
|
||||
pow3r.iter().map(|n| names[(*n) as usize].clone()).collect();
|
||||
handle
|
||||
.global::<AuraPageData>()
|
||||
.set_power_zone_names_old(n.as_slice().into());
|
||||
.set_power_zone_names_old(names.as_slice().into());
|
||||
} else {
|
||||
let p: Vec<SlintPowerZones> = pow3r.iter().map(|z| (*z).into()).collect();
|
||||
let power: Vec<SlintPowerZones> =
|
||||
pow3r.iter().map(|p| (*p).into()).collect();
|
||||
|
||||
handle
|
||||
.global::<AuraPageData>()
|
||||
.set_supported_power_zones(p.as_slice().into());
|
||||
.set_supported_power_zones(power.as_slice().into());
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
let proxy = aura.clone();
|
||||
let weak = handle.clone();
|
||||
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());
|
||||
|
||||
// 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();
|
||||
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());
|
||||
|
||||
// Enable software animation if only Static mode is available
|
||||
if static_only {
|
||||
info!("Only Static mode available - enabling software animation controls");
|
||||
handle
|
||||
.global::<AuraPageData>()
|
||||
.set_soft_animation_available(true);
|
||||
|
||||
// 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| {
|
||||
let aura_inner = aura_mode.clone();
|
||||
let handle = match handle_weak.upgrade() {
|
||||
Some(h) => h,
|
||||
None => return,
|
||||
};
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.map_err(|e| error!("{e:}"))
|
||||
.ok();
|
||||
}
|
||||
|
||||
let proxy_copy = aura.clone();
|
||||
handle
|
||||
.upgrade_in_event_loop(move |h| {
|
||||
set_ui_callbacks!(h,
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
set_ui_callbacks!(handle,
|
||||
AuraPageData(.into()),
|
||||
proxy.brightness(.into()),
|
||||
"Brightness set to {}",
|
||||
"Brightness failed"
|
||||
proxy_copy.brightness(.into()),
|
||||
"Keyboard LED brightness successfully set to {}",
|
||||
"Setting keyboard LED brightness failed"
|
||||
);
|
||||
|
||||
let p = proxy.clone();
|
||||
let w = weak.clone();
|
||||
h.global::<AuraPageData>().on_apply_led_mode_data(move || {
|
||||
let Some(ui) = w.upgrade() else { return };
|
||||
let slint_effect = ui.global::<AuraPageData>().get_led_mode_data();
|
||||
let raw: rog_aura::AuraEffect = slint_effect.into();
|
||||
let pp = p.clone();
|
||||
let t = w.clone();
|
||||
tokio::spawn(async move {
|
||||
let r = pp.set_led_mode_data(raw).await;
|
||||
show_toast("LED mode applied".into(), "LED mode failed".into(), t, r);
|
||||
set_ui_callbacks!(handle,
|
||||
AuraPageData(.into()),
|
||||
proxy_copy.led_mode(.into()),
|
||||
"Keyboard LED mode successfully set to {}",
|
||||
"Setting keyboard LEDmode 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}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
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();
|
||||
|
||||
let weak_power = handle.clone();
|
||||
let proxy_power = aura.clone();
|
||||
let handle_copy = handle.clone();
|
||||
let proxy_copy = aura.clone();
|
||||
handle
|
||||
.upgrade_in_event_loop(|h| {
|
||||
h.global::<AuraPageData>().on_cb_led_power(move |power| {
|
||||
let w = weak_power.clone();
|
||||
let p = proxy_power.clone();
|
||||
let pw: LaptopAuraPower = power.into();
|
||||
tokio::spawn(async move {
|
||||
show_toast(
|
||||
"Aura power updated".into(),
|
||||
"Aura power failed".into(),
|
||||
w,
|
||||
p.set_led_power(pw).await,
|
||||
);
|
||||
.upgrade_in_event_loop(|handle| {
|
||||
handle
|
||||
.global::<AuraPageData>()
|
||||
.on_cb_led_power(move |power| {
|
||||
let handle_copy = handle_copy.clone();
|
||||
let proxy_copy = aura.clone();
|
||||
let power: LaptopAuraPower = power.into();
|
||||
tokio::spawn(async move {
|
||||
show_toast(
|
||||
"Aura power settings changed".into(),
|
||||
"Failed to set Aura power settings".into(),
|
||||
handle_copy,
|
||||
proxy_copy.set_led_power(power).await,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.map_err(|e| error!("{e}"))
|
||||
.map_err(|e| error!("{e:}"))
|
||||
.ok();
|
||||
|
||||
let stream_handle = handle.clone();
|
||||
let aura_stream = aura.clone();
|
||||
// Need to update the UI if the mode changes
|
||||
let handle_copy = handle.clone();
|
||||
// spawn required since the while let never exits
|
||||
tokio::spawn(async move {
|
||||
let mut x = proxy_copy.receive_led_mode_data_changed().await;
|
||||
use futures_util::StreamExt;
|
||||
let mut stream = aura_stream.receive_led_mode_data_changed().await;
|
||||
while let Some(e) = stream.next().await {
|
||||
while let Some(e) = x.next().await {
|
||||
if let Ok(out) = e.get().await {
|
||||
let raw: i32 = out.mode.into();
|
||||
let data = out.into();
|
||||
stream_handle
|
||||
.upgrade_in_event_loop(move |h| {
|
||||
h.global::<AuraPageData>().invoke_update_led_mode_data(data);
|
||||
let supported: Vec<i32> = h
|
||||
handle_copy
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
handle
|
||||
.global::<AuraPageData>()
|
||||
.get_supported_basic_modes()
|
||||
.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();
|
||||
.invoke_update_led_mode_data(out.into());
|
||||
handle.invoke_external_colour_change();
|
||||
})
|
||||
.map_err(|e| error!("{e}"))
|
||||
.map_err(|e| error!("{e:}"))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
debug!("Aura setup done");
|
||||
debug!("Aura setup tasks complete");
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
241
rog-control-center/src/ui/setup_fan_curve_custom.rs
Normal file
241
rog-control-center/src/ui/setup_fan_curve_custom.rs
Normal file
@@ -0,0 +1,241 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{FanType, MainWindow, Node};
|
||||
|
||||
const ASUS_CUSTOM_FAN_NAME: &str = "asus_custom_fan_curve";
|
||||
const CONFIG_FILE_NAME: &str = "custom_fans.ron";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CustomCurvePoint {
|
||||
pub temp: u8,
|
||||
pub pwm: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct CustomFanConfig {
|
||||
pub cpu_curve: Vec<CustomCurvePoint>,
|
||||
pub gpu_curve: Vec<CustomCurvePoint>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SysfsPaths {
|
||||
root: PathBuf,
|
||||
}
|
||||
|
||||
impl SysfsPaths {
|
||||
fn new() -> Option<Self> {
|
||||
let hwmon = Path::new("/sys/class/hwmon");
|
||||
if let Ok(entries) = fs::read_dir(hwmon) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
let name_path = path.join("name");
|
||||
if let Ok(name) = fs::read_to_string(&name_path) {
|
||||
if name.trim() == ASUS_CUSTOM_FAN_NAME {
|
||||
info!("Found ASUS Custom Fan Control at {:?}", path);
|
||||
return Some(Self { root: path });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn enable_path(&self, index: u8) -> PathBuf {
|
||||
self.root.join(format!("pwm{}_enable", index))
|
||||
}
|
||||
|
||||
fn point_pwm_path(&self, fan_idx: u8, point_idx: u8) -> PathBuf {
|
||||
self.root
|
||||
.join(format!("pwm{}_auto_point{}_pwm", fan_idx, point_idx))
|
||||
}
|
||||
|
||||
fn point_temp_path(&self, fan_idx: u8, point_idx: u8) -> PathBuf {
|
||||
self.root
|
||||
.join(format!("pwm{}_auto_point{}_temp", fan_idx, point_idx))
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to write with logging
|
||||
fn write_sysfs(path: &Path, value: &str) -> std::io::Result<()> {
|
||||
// debug!("Writing {} to {:?}", value, path);
|
||||
fs::write(path, value)
|
||||
}
|
||||
|
||||
fn _read_sysfs_u8(path: &Path) -> std::io::Result<u8> {
|
||||
let s = fs::read_to_string(path)?;
|
||||
s.trim()
|
||||
.parse::<u8>()
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
|
||||
}
|
||||
|
||||
pub fn is_custom_fan_supported() -> bool {
|
||||
SysfsPaths::new().is_some()
|
||||
}
|
||||
|
||||
// Logic to apply a full curve to a specific fan (1=CPU, 2=GPU usually)
|
||||
// Implements the "Gradual Descent" algorithm
|
||||
fn apply_curve_to_fan(
|
||||
paths: &SysfsPaths,
|
||||
fan_idx: u8,
|
||||
points: &[CustomCurvePoint],
|
||||
) -> std::io::Result<()> {
|
||||
// Sort target points by temp (Hardware Requirement)
|
||||
let mut sorted_target = points.to_vec();
|
||||
sorted_target.sort_by_key(|p| p.temp);
|
||||
|
||||
// Ensure we have 8 points (fill with last if needed, or sensible default)
|
||||
while sorted_target.len() < 8 {
|
||||
if let Some(last) = sorted_target.last() {
|
||||
sorted_target.push(last.clone());
|
||||
} else {
|
||||
sorted_target.push(CustomCurvePoint {
|
||||
temp: 100,
|
||||
pwm: 255,
|
||||
});
|
||||
}
|
||||
}
|
||||
sorted_target.truncate(8);
|
||||
|
||||
// Validate Temp Order (Synchronous Check)
|
||||
for (i, p) in sorted_target.iter().enumerate() {
|
||||
if i > 0 {
|
||||
let prev_temp = sorted_target[i - 1].temp;
|
||||
if p.temp < prev_temp {
|
||||
error!("Invalid temp order");
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"Temp disorder",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn completely detached thread for ALL I/O
|
||||
let paths_clone = paths.clone();
|
||||
let sorted_target = sorted_target.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let paths = paths_clone;
|
||||
|
||||
// 1. Enable custom mode
|
||||
if let Err(e) = write_sysfs(&paths.enable_path(fan_idx), "1") {
|
||||
error!("Failed to enable custom fan mode: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Write Temps
|
||||
for (i, p) in sorted_target.iter().enumerate() {
|
||||
let point_idx = (i + 1) as u8;
|
||||
if let Err(e) = write_sysfs(
|
||||
&paths.point_temp_path(fan_idx, point_idx),
|
||||
&p.temp.to_string(),
|
||||
) {
|
||||
error!("Failed to write temp point {}: {}", point_idx, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Write PWMs directly (hardware handles gradual transition)
|
||||
for (i, target_p) in sorted_target.iter().enumerate() {
|
||||
let point_idx = (i + 1) as u8;
|
||||
if let Err(e) = write_sysfs(
|
||||
&paths.point_pwm_path(fan_idx, point_idx),
|
||||
&target_p.pwm.to_string(),
|
||||
) {
|
||||
error!("Failed to write PWM point {}: {}", point_idx, e);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Ensure enable is set
|
||||
let _ = write_sysfs(&paths.enable_path(fan_idx), "1");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_fan_auto(paths: &SysfsPaths, fan_idx: u8) -> std::io::Result<()> {
|
||||
// 2 = Auto (usually)
|
||||
write_sysfs(&paths.enable_path(fan_idx), "2")
|
||||
}
|
||||
|
||||
fn load_config() -> CustomFanConfig {
|
||||
if let Some(config_dir) = dirs::config_dir() {
|
||||
let path = config_dir.join("rog").join(CONFIG_FILE_NAME);
|
||||
if let Ok(content) = fs::read_to_string(path) {
|
||||
if let Ok(cfg) = ron::from_str(&content) {
|
||||
return cfg;
|
||||
}
|
||||
}
|
||||
}
|
||||
CustomFanConfig::default()
|
||||
}
|
||||
|
||||
fn save_config(config: &CustomFanConfig) {
|
||||
if let Some(config_dir) = dirs::config_dir() {
|
||||
let rog_dir = config_dir.join("rog");
|
||||
let _ = fs::create_dir_all(&rog_dir);
|
||||
let path = rog_dir.join(CONFIG_FILE_NAME);
|
||||
if let Ok(s) = ron::ser::to_string_pretty(config, ron::ser::PrettyConfig::default()) {
|
||||
let _ = fs::write(path, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public entry point called from setup_fans.rs or similar
|
||||
// Returns immediately - all work is done in a detached thread
|
||||
pub fn apply_custom_fan_curve(
|
||||
_handle_weak: slint::Weak<MainWindow>,
|
||||
fan_type: FanType,
|
||||
enabled: bool,
|
||||
nodes: Vec<Node>,
|
||||
) {
|
||||
// Fan Index: 1=CPU, 2=GPU usually.
|
||||
let fan_idx = match fan_type {
|
||||
FanType::CPU => 1,
|
||||
FanType::GPU => 2,
|
||||
_ => return, // Ignore others
|
||||
};
|
||||
|
||||
// Convert nodes to points (fast, CPU-only)
|
||||
let points: Vec<CustomCurvePoint> = nodes
|
||||
.iter()
|
||||
.map(|n| CustomCurvePoint {
|
||||
temp: n.x as u8,
|
||||
pwm: n.y as u8,
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Spawn a completely detached thread for ALL I/O
|
||||
std::thread::spawn(move || {
|
||||
// Get paths (blocking FS operation)
|
||||
let Some(paths) = SysfsPaths::new() else {
|
||||
error!("No custom fan support found");
|
||||
return;
|
||||
};
|
||||
|
||||
// Save config
|
||||
let mut cfg = load_config();
|
||||
if enabled {
|
||||
match fan_type {
|
||||
FanType::CPU => cfg.cpu_curve = points.clone(),
|
||||
FanType::GPU => cfg.gpu_curve = points.clone(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
cfg.enabled = enabled;
|
||||
save_config(&cfg);
|
||||
|
||||
// Apply curve or set auto
|
||||
if enabled {
|
||||
if let Err(e) = apply_curve_to_fan(&paths, fan_idx, &points) {
|
||||
error!("Failed to apply fan curve: {}", e);
|
||||
}
|
||||
} else if let Err(e) = set_fan_auto(&paths, fan_idx) {
|
||||
error!("Failed to set fan auto: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use crate::ui::show_toast;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
use log::error;
|
||||
use rog_dbus::zbus_fan_curves::FanCurvesProxy;
|
||||
@@ -8,7 +10,25 @@ use rog_profiles::fan_curve_set::CurveData;
|
||||
use slint::{ComponentHandle, Model, Weak};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::{FanPageData, FanType, MainWindow, Node};
|
||||
use crate::{FanPageData, FanType, MainWindow, Node, Profile};
|
||||
|
||||
// Isolated Rust-side cache for fan curves (not affected by Slint reactivity)
|
||||
type FanCacheKey = (i32, i32); // (Profile as i32, FanType as i32)
|
||||
static FAN_CACHE: OnceLock<Mutex<HashMap<FanCacheKey, Vec<Node>>>> = OnceLock::new();
|
||||
|
||||
fn fan_cache() -> &'static Mutex<HashMap<FanCacheKey, Vec<Node>>> {
|
||||
FAN_CACHE.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
fn cache_fan_curve(profile: Profile, fan_type: FanType, nodes: Vec<Node>) {
|
||||
let key = (profile as i32, fan_type as i32);
|
||||
fan_cache().lock().unwrap().insert(key, nodes);
|
||||
}
|
||||
|
||||
fn get_cached_fan_curve(profile: Profile, fan_type: FanType) -> Option<Vec<Node>> {
|
||||
let key = (profile as i32, fan_type as i32);
|
||||
fan_cache().lock().unwrap().get(&key).cloned()
|
||||
}
|
||||
|
||||
pub fn update_fan_data(
|
||||
handle: Weak<MainWindow>,
|
||||
@@ -19,7 +39,7 @@ pub fn update_fan_data(
|
||||
handle
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
let global = handle.global::<FanPageData>();
|
||||
let collect = |temp: &[u8], pwm: &[u8]| -> slint::ModelRc<Node> {
|
||||
let _collect = |temp: &[u8], pwm: &[u8]| -> slint::ModelRc<Node> {
|
||||
let tmp: Vec<Node> = temp
|
||||
.iter()
|
||||
.zip(pwm.iter())
|
||||
@@ -33,61 +53,100 @@ pub fn update_fan_data(
|
||||
|
||||
for fan in bal {
|
||||
global.set_balanced_available(true);
|
||||
let nodes_vec: Vec<Node> = fan
|
||||
.temp
|
||||
.iter()
|
||||
.zip(fan.pwm.iter())
|
||||
.map(|(x, y)| Node {
|
||||
x: *x as f32,
|
||||
y: *y as f32,
|
||||
})
|
||||
.collect();
|
||||
let nodes: slint::ModelRc<Node> = nodes_vec.as_slice().into();
|
||||
match fan.fan {
|
||||
rog_profiles::FanCurvePU::CPU => {
|
||||
global.set_cpu_fan_available(true);
|
||||
global.set_balanced_cpu_enabled(fan.enabled);
|
||||
global.set_balanced_cpu(collect(&fan.temp, &fan.pwm))
|
||||
global.set_balanced_cpu(nodes.clone());
|
||||
cache_fan_curve(Profile::Balanced, FanType::CPU, nodes_vec);
|
||||
}
|
||||
rog_profiles::FanCurvePU::GPU => {
|
||||
global.set_gpu_fan_available(true);
|
||||
global.set_balanced_gpu_enabled(fan.enabled);
|
||||
global.set_balanced_gpu(collect(&fan.temp, &fan.pwm))
|
||||
global.set_balanced_gpu(nodes.clone());
|
||||
cache_fan_curve(Profile::Balanced, FanType::GPU, nodes_vec);
|
||||
}
|
||||
rog_profiles::FanCurvePU::MID => {
|
||||
global.set_mid_fan_available(true);
|
||||
global.set_balanced_mid_enabled(fan.enabled);
|
||||
global.set_balanced_mid(collect(&fan.temp, &fan.pwm))
|
||||
global.set_balanced_mid(nodes.clone());
|
||||
cache_fan_curve(Profile::Balanced, FanType::Middle, nodes_vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
for fan in perf {
|
||||
global.set_performance_available(true);
|
||||
let nodes_vec: Vec<Node> = fan
|
||||
.temp
|
||||
.iter()
|
||||
.zip(fan.pwm.iter())
|
||||
.map(|(x, y)| Node {
|
||||
x: *x as f32,
|
||||
y: *y as f32,
|
||||
})
|
||||
.collect();
|
||||
let nodes: slint::ModelRc<Node> = nodes_vec.as_slice().into();
|
||||
match fan.fan {
|
||||
rog_profiles::FanCurvePU::CPU => {
|
||||
global.set_cpu_fan_available(true);
|
||||
global.set_performance_cpu_enabled(fan.enabled);
|
||||
global.set_performance_cpu(collect(&fan.temp, &fan.pwm))
|
||||
global.set_performance_cpu(nodes.clone());
|
||||
cache_fan_curve(Profile::Performance, FanType::CPU, nodes_vec);
|
||||
}
|
||||
rog_profiles::FanCurvePU::GPU => {
|
||||
global.set_gpu_fan_available(true);
|
||||
global.set_performance_gpu_enabled(fan.enabled);
|
||||
global.set_performance_gpu(collect(&fan.temp, &fan.pwm))
|
||||
global.set_performance_gpu(nodes.clone());
|
||||
cache_fan_curve(Profile::Performance, FanType::GPU, nodes_vec);
|
||||
}
|
||||
rog_profiles::FanCurvePU::MID => {
|
||||
global.set_mid_fan_available(true);
|
||||
global.set_performance_mid_enabled(fan.enabled);
|
||||
global.set_performance_mid(collect(&fan.temp, &fan.pwm))
|
||||
global.set_performance_mid(nodes.clone());
|
||||
cache_fan_curve(Profile::Performance, FanType::Middle, nodes_vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
for fan in quiet {
|
||||
global.set_quiet_available(true);
|
||||
let nodes_vec: Vec<Node> = fan
|
||||
.temp
|
||||
.iter()
|
||||
.zip(fan.pwm.iter())
|
||||
.map(|(x, y)| Node {
|
||||
x: *x as f32,
|
||||
y: *y as f32,
|
||||
})
|
||||
.collect();
|
||||
let nodes: slint::ModelRc<Node> = nodes_vec.as_slice().into();
|
||||
match fan.fan {
|
||||
rog_profiles::FanCurvePU::CPU => {
|
||||
global.set_cpu_fan_available(true);
|
||||
global.set_quiet_cpu_enabled(fan.enabled);
|
||||
global.set_quiet_cpu(collect(&fan.temp, &fan.pwm))
|
||||
global.set_quiet_cpu(nodes.clone());
|
||||
cache_fan_curve(Profile::Quiet, FanType::CPU, nodes_vec);
|
||||
}
|
||||
rog_profiles::FanCurvePU::GPU => {
|
||||
global.set_gpu_fan_available(true);
|
||||
global.set_quiet_gpu_enabled(fan.enabled);
|
||||
global.set_quiet_gpu(collect(&fan.temp, &fan.pwm))
|
||||
global.set_quiet_gpu(nodes.clone());
|
||||
cache_fan_curve(Profile::Quiet, FanType::GPU, nodes_vec);
|
||||
}
|
||||
rog_profiles::FanCurvePU::MID => {
|
||||
global.set_mid_fan_available(true);
|
||||
global.set_quiet_mid_enabled(fan.enabled);
|
||||
global.set_quiet_mid(collect(&fan.temp, &fan.pwm))
|
||||
global.set_quiet_mid(nodes.clone());
|
||||
cache_fan_curve(Profile::Quiet, FanType::Middle, nodes_vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,6 +230,7 @@ pub fn setup_fan_curve_page(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
|
||||
let choices_for_ui = platform_profile_choices.clone();
|
||||
let handle_next1 = handle_copy.clone();
|
||||
if let Err(e) = handle_copy.upgrade_in_event_loop(move |handle| {
|
||||
let handle_weak_for_fans = handle.as_weak();
|
||||
let global = handle.global::<FanPageData>();
|
||||
let fans1 = fans.clone();
|
||||
let choices = choices_for_ui.clone();
|
||||
@@ -212,17 +272,103 @@ pub fn setup_fan_curve_page(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
|
||||
update_fan_data(handle_next, balanced, perf, quiet);
|
||||
});
|
||||
});
|
||||
|
||||
let handle_weak_for_cancel = handle_weak_for_fans.clone();
|
||||
global.on_set_fan_data(move |fan, profile, enabled, data| {
|
||||
if crate::ui::setup_fan_curve_custom::is_custom_fan_supported() {
|
||||
let handle_weak = handle_weak_for_fans.clone();
|
||||
let data: Vec<Node> = data.iter().collect();
|
||||
|
||||
use log::info;
|
||||
info!("MainThread: Request to apply custom curve for {:?}", fan);
|
||||
|
||||
// Explicitly spawn a thread to handle this, preventing ANY main thread blocking
|
||||
std::thread::spawn(move || {
|
||||
info!("WorkerThread: applying curve for {:?}", fan);
|
||||
crate::ui::setup_fan_curve_custom::apply_custom_fan_curve(
|
||||
handle_weak.clone(),
|
||||
fan,
|
||||
enabled,
|
||||
data,
|
||||
);
|
||||
info!("WorkerThread: returned from apply (async), clearing busy flag for {:?}", fan);
|
||||
|
||||
// Clear busy flag
|
||||
let _ = handle_weak.upgrade_in_event_loop(move |h| {
|
||||
let g = h.global::<FanPageData>();
|
||||
match fan {
|
||||
FanType::CPU => g.set_is_busy_cpu(false),
|
||||
FanType::GPU => g.set_is_busy_gpu(false),
|
||||
FanType::Middle => g.set_is_busy_mid(false),
|
||||
}
|
||||
info!("MainThread: cleared busy flag for {:?}", fan);
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let fans = fans.clone();
|
||||
let data: Vec<Node> = data.iter().collect();
|
||||
let data = fan_data_for(fan, enabled, data);
|
||||
let handle_weak = handle_weak_for_fans.clone();
|
||||
let nodes_vec: Vec<Node> = data.iter().collect();
|
||||
let _data_copy = nodes_vec.clone();
|
||||
let cache_copy = nodes_vec.clone(); // Clone for cache update
|
||||
let fan_data = fan_data_for(fan, enabled, nodes_vec);
|
||||
tokio::spawn(async move {
|
||||
fans.set_fan_curve(profile.into(), data)
|
||||
.await
|
||||
.map_err(|e| error!("{e:}"))
|
||||
.ok()
|
||||
show_toast(
|
||||
"Fan curve applied".into(),
|
||||
"Failed to apply fan curve".into(),
|
||||
handle_weak.clone(),
|
||||
fans.set_fan_curve(profile.into(), fan_data).await,
|
||||
);
|
||||
let _ = handle_weak.upgrade_in_event_loop(move |h| {
|
||||
let g = h.global::<FanPageData>();
|
||||
// Update Rust-side cache (isolated from Slint properties)
|
||||
cache_fan_curve(profile, fan, cache_copy);
|
||||
|
||||
match fan {
|
||||
FanType::CPU => g.set_is_busy_cpu(false),
|
||||
FanType::GPU => g.set_is_busy_gpu(false),
|
||||
FanType::Middle => g.set_is_busy_mid(false),
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
global.on_cancel(move |fan_type, profile| {
|
||||
let handle_weak = handle_weak_for_cancel.clone();
|
||||
let _ = handle_weak.upgrade_in_event_loop(move |h: MainWindow| {
|
||||
let global = h.global::<FanPageData>();
|
||||
|
||||
// Retrieve from isolated Rust cache
|
||||
let nodes_opt = get_cached_fan_curve(profile, fan_type);
|
||||
|
||||
if let Some(nodes_vec) = nodes_opt {
|
||||
use log::info;
|
||||
info!("Canceling {:?} {:?} - restoring {} nodes from isolated cache", fan_type, profile, nodes_vec.len());
|
||||
|
||||
let new_model: slint::ModelRc<Node> = nodes_vec.as_slice().into();
|
||||
|
||||
match (profile, fan_type) {
|
||||
(crate::Profile::Balanced, FanType::CPU) => global.set_balanced_cpu(new_model),
|
||||
(crate::Profile::Balanced, FanType::GPU) => global.set_balanced_gpu(new_model),
|
||||
(crate::Profile::Balanced, FanType::Middle) => global.set_balanced_mid(new_model),
|
||||
(crate::Profile::Performance, FanType::CPU) => global.set_performance_cpu(new_model),
|
||||
(crate::Profile::Performance, FanType::GPU) => global.set_performance_gpu(new_model),
|
||||
(crate::Profile::Performance, FanType::Middle) => global.set_performance_mid(new_model),
|
||||
(crate::Profile::Quiet, FanType::CPU) => global.set_quiet_cpu(new_model),
|
||||
(crate::Profile::Quiet, FanType::GPU) => global.set_quiet_gpu(new_model),
|
||||
(crate::Profile::Quiet, FanType::Middle) => global.set_quiet_mid(new_model),
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
log::warn!("Cancel failed: No cached data for {:?} {:?}", fan_type, profile);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Initialize warning
|
||||
if crate::ui::setup_fan_curve_custom::is_custom_fan_supported() {
|
||||
global.set_show_custom_warning(true);
|
||||
}
|
||||
}) {
|
||||
error!("setup_fan_curve_page: upgrade_in_event_loop: {e:?}");
|
||||
}
|
||||
|
||||
143
rog-control-center/src/ui/setup_screenpad.rs
Normal file
143
rog-control-center/src/ui/setup_screenpad.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use log::{debug, error};
|
||||
use rog_dbus::zbus_backlight::BacklightProxy;
|
||||
use slint::ComponentHandle;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::ui::show_toast;
|
||||
use crate::{MainWindow, ScreenpadPageData};
|
||||
|
||||
pub fn setup_screenpad(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
|
||||
let handle = ui.as_weak();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Create the connections/proxies here
|
||||
let conn = match zbus::Connection::system().await {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
error!("Failed to connect to system bus for Screenpad: {e:}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let backlight = match BacklightProxy::builder(&conn).build().await {
|
||||
Ok(backlight) => backlight,
|
||||
Err(e) => {
|
||||
error!("Failed to create backlight proxy for Screenpad: {e:}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize state
|
||||
debug!("Initializing Screenpad page data");
|
||||
|
||||
// Use helper to set initial properties
|
||||
if let Ok(val) = backlight.screenpad_brightness().await {
|
||||
handle
|
||||
.upgrade_in_event_loop(move |h| {
|
||||
h.global::<ScreenpadPageData>().set_brightness(val);
|
||||
// Assume power is on if brightness > 0
|
||||
h.global::<ScreenpadPageData>().set_power(val > 0);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
if let Ok(gamma_str) = backlight.screenpad_gamma().await {
|
||||
if let Ok(gamma) = gamma_str.parse::<f32>() {
|
||||
handle
|
||||
.upgrade_in_event_loop(move |h| {
|
||||
h.global::<ScreenpadPageData>().set_gamma(gamma);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(sync) = backlight.screenpad_sync_with_primary().await {
|
||||
handle
|
||||
.upgrade_in_event_loop(move |h| {
|
||||
h.global::<ScreenpadPageData>().set_sync_with_primary(sync);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
// Set up callbacks
|
||||
let handle_copy = handle.clone();
|
||||
let backlight_copy = backlight.clone();
|
||||
handle
|
||||
.upgrade_in_event_loop(move |h| {
|
||||
let global = h.global::<ScreenpadPageData>();
|
||||
|
||||
// Brightness Callback
|
||||
let hl = handle_copy.clone();
|
||||
let bl = backlight_copy.clone();
|
||||
global.on_cb_brightness(move |val| {
|
||||
let bl = bl.clone();
|
||||
let hl = hl.clone();
|
||||
tokio::spawn(async move {
|
||||
show_toast(
|
||||
format!("Screenpad brightness set to {}", val).into(),
|
||||
"Failed to set Screenpad brightness".into(),
|
||||
hl,
|
||||
bl.set_screenpad_brightness(val).await,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Gamma Callback
|
||||
let hl = handle_copy.clone();
|
||||
let bl = backlight_copy.clone();
|
||||
global.on_cb_gamma(move |val| {
|
||||
let bl = bl.clone();
|
||||
let hl = hl.clone();
|
||||
tokio::spawn(async move {
|
||||
show_toast(
|
||||
format!("Screenpad gamma set to {:.2}", val).into(),
|
||||
"Failed to set Screenpad gamma".into(),
|
||||
hl,
|
||||
bl.set_screenpad_gamma(&val.to_string()).await,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Sync Callback
|
||||
let hl = handle_copy.clone();
|
||||
let bl = backlight_copy.clone();
|
||||
global.on_cb_sync_with_primary(move |val| {
|
||||
let bl = bl.clone();
|
||||
let hl = hl.clone();
|
||||
tokio::spawn(async move {
|
||||
show_toast(
|
||||
format!(
|
||||
"Screenpad sync {}",
|
||||
if val { "enabled" } else { "disabled" }
|
||||
)
|
||||
.into(),
|
||||
"Failed to toggle Screenpad sync".into(),
|
||||
hl,
|
||||
bl.set_screenpad_sync_with_primary(val).await,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Power Callback (Toggle brightness to 0/last or 100)
|
||||
let hl = handle_copy.clone();
|
||||
let bl = backlight_copy.clone();
|
||||
global.on_cb_power(move |val| {
|
||||
let bl = bl.clone();
|
||||
let hl = hl.clone();
|
||||
tokio::spawn(async move {
|
||||
let target = if val { 100 } else { 0 };
|
||||
let _ = bl.set_screenpad_brightness(target).await;
|
||||
hl.upgrade_in_event_loop(move |h| {
|
||||
h.global::<ScreenpadPageData>().set_brightness(target);
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
});
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Optional: Value watches for external changes
|
||||
// (Similar to setup_system.rs if needed)
|
||||
});
|
||||
}
|
||||
132
rog-control-center/src/ui/setup_slash.rs
Normal file
132
rog-control-center/src/ui/setup_slash.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use crate::config::Config;
|
||||
use crate::set_ui_callbacks;
|
||||
use crate::ui::show_toast;
|
||||
use crate::{MainWindow, SlashPageData};
|
||||
use rog_dbus::{find_iface_async, zbus_slash::SlashProxy};
|
||||
use rog_slash::SlashMode;
|
||||
use slint::{ComponentHandle, Model};
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub fn setup_slash(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
|
||||
let ui_weak = ui.as_weak();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Find the Slash interface proxy
|
||||
let proxies = match find_iface_async::<SlashProxy>("xyz.ljones.Slash").await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
log::warn!("Failed to find Slash interface: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let proxy = match proxies.first() {
|
||||
Some(p) => p.clone(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
// UI Callbacks (MUST be done on UI thread to access global state)
|
||||
{
|
||||
let proxy_copy = proxy.clone();
|
||||
|
||||
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
|
||||
let proxy = proxy_copy.clone();
|
||||
let ui_handle = ui;
|
||||
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.enabled(), "Slash enabled {}", "Failed to enable slash");
|
||||
|
||||
// Fix: Cast f32 (UI) to u8 (DBus)
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(as f32), proxy.brightness(as u8), "Slash brightness set to {}", "Failed to set slash brightness");
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(as f32), proxy.interval(as u8), "Slash interval set to {}", "Failed to set slash interval");
|
||||
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_battery_warning(), "Battery warning set to {}", "Failed to set battery warning");
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_battery(), "Show on battery set to {}", "Failed to set show on battery");
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_boot(), "Show on boot set to {}", "Failed to set show on boot");
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_shutdown(), "Show on shutdown set to {}", "Failed to set show on shutdown");
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_sleep(), "Show on sleep set to {}", "Failed to set show on sleep");
|
||||
set_ui_callbacks!(ui_handle, SlashPageData(), proxy.show_on_lid_closed(), "Show on lid closed set to {}", "Failed to set show on lid closed");
|
||||
});
|
||||
}
|
||||
|
||||
// Custom Mode Logic - Callback setup
|
||||
{
|
||||
let proxy_copy = proxy.clone();
|
||||
let ui_weak_copy = ui_weak.clone();
|
||||
let _ = ui_weak.upgrade_in_event_loop(move |ui| {
|
||||
let data = ui.global::<SlashPageData>();
|
||||
|
||||
data.on_cb_mode_index(move |idx| {
|
||||
let proxy_copy = proxy_copy.clone();
|
||||
let handle_weak = ui_weak_copy.clone();
|
||||
|
||||
let mode_str_opt = if let Some(h) = handle_weak.upgrade() {
|
||||
let d = h.global::<SlashPageData>();
|
||||
if idx >= 0 && (idx as usize) < d.get_modes().row_count() {
|
||||
Some(d.get_modes().row_data(idx as usize).unwrap_or_default())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(mode_str) = mode_str_opt {
|
||||
if let Ok(mode) = SlashMode::from_str(&mode_str) {
|
||||
tokio::spawn(async move {
|
||||
show_toast(
|
||||
format!("Slash mode set to {}", mode).into(),
|
||||
"Failed to set slash mode".into(),
|
||||
handle_weak,
|
||||
proxy_copy.set_mode(mode).await,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// D-Bus Signal -> UI
|
||||
let proxy_copy = proxy.clone();
|
||||
let handle_copy = ui_weak.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut changes = proxy_copy.receive_mode_changed().await;
|
||||
use futures_util::StreamExt;
|
||||
while let Some(change) = changes.next().await {
|
||||
if let Ok(mode) = change.get().await {
|
||||
let mode_str = mode.to_string();
|
||||
let handle_copy = handle_copy.clone();
|
||||
let _ = slint::invoke_from_event_loop(move || {
|
||||
if let Some(h) = handle_copy.upgrade() {
|
||||
let d = h.global::<SlashPageData>();
|
||||
let model = d.get_modes();
|
||||
for (i, m) in model.iter().enumerate() {
|
||||
if m == mode_str {
|
||||
d.set_mode_index(i as i32);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if let Ok(m) = proxy.mode().await {
|
||||
let mode_str = m.to_string();
|
||||
let _ = slint::invoke_from_event_loop(move || {
|
||||
if let Some(h) = ui_weak.upgrade() {
|
||||
let d = h.global::<SlashPageData>();
|
||||
let model = d.get_modes();
|
||||
for (i, m) in model.iter().enumerate() {
|
||||
if m == mode_str {
|
||||
d.set_mode_index(i as i32);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
150
rog-control-center/src/ui/setup_status.rs
Normal file
150
rog-control-center/src/ui/setup_status.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use crate::config::Config;
|
||||
use crate::tray::TrayStats;
|
||||
use crate::{MainWindow, SystemStatus};
|
||||
use rog_dbus::zbus_platform::PlatformProxy;
|
||||
use slint::ComponentHandle;
|
||||
use std::collections::VecDeque;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::fs;
|
||||
use tokio::time::Duration;
|
||||
use zbus::Connection;
|
||||
|
||||
pub fn setup_status(
|
||||
ui: &MainWindow,
|
||||
_config: Arc<Mutex<Config>>,
|
||||
stats_tx: tokio::sync::watch::Sender<TrayStats>,
|
||||
) {
|
||||
let ui_weak = ui.as_weak();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut power_history: VecDeque<i32> = VecDeque::with_capacity(150); // 300s window at 2s poll
|
||||
// DBus connection for profile
|
||||
let conn = Connection::system().await.ok();
|
||||
let platform = if let Some(c) = &conn {
|
||||
PlatformProxy::new(c).await.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
loop {
|
||||
let (cpu_temp, gpu_temp, cpu_fan, gpu_fan) = read_hwmon().await;
|
||||
let power_microwatts = read_power().await;
|
||||
|
||||
// Rolling average logic
|
||||
if power_history.len() >= 150 {
|
||||
power_history.pop_front();
|
||||
}
|
||||
power_history.push_back(power_microwatts);
|
||||
|
||||
let sum: i64 = power_history.iter().map(|&x| x as i64).sum();
|
||||
let avg_microwatts = if !power_history.is_empty() {
|
||||
sum / power_history.len() as i64
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// Convert to Watts
|
||||
let power_w = power_microwatts as f64 / 1_000_000.0;
|
||||
let avg_w = avg_microwatts as f64 / 1_000_000.0;
|
||||
|
||||
// Fetch profile
|
||||
let mut profile_str = "Unknown".to_string();
|
||||
if let Some(p) = &platform {
|
||||
if let Ok(prof) = p.platform_profile().await {
|
||||
profile_str = format!("{:?}", prof);
|
||||
}
|
||||
}
|
||||
let ui_weak_loop = ui_weak.clone(); // Clone ui_weak for this iteration
|
||||
|
||||
// Send to Tray
|
||||
let _ = stats_tx.send(TrayStats {
|
||||
cpu_temp: format!("{}", cpu_temp),
|
||||
gpu_temp: format!("{}", gpu_temp),
|
||||
cpu_fan: format!("{}", cpu_fan),
|
||||
gpu_fan: format!("{}", gpu_fan),
|
||||
power_w: format!("{:.1}", power_w),
|
||||
power_profile: profile_str,
|
||||
});
|
||||
|
||||
let _ = slint::invoke_from_event_loop(move || {
|
||||
if let Some(ui) = ui_weak_loop.upgrade() {
|
||||
let global = ui.global::<SystemStatus>();
|
||||
global.set_cpu_temp(cpu_temp);
|
||||
global.set_gpu_temp(gpu_temp);
|
||||
global.set_cpu_fan(cpu_fan);
|
||||
global.set_gpu_fan(gpu_fan);
|
||||
|
||||
global.set_power_w(slint::SharedString::from(format!("{:.1}", power_w)));
|
||||
global.set_power_avg_w(slint::SharedString::from(format!("{:.1}", avg_w)));
|
||||
}
|
||||
});
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn read_hwmon() -> (i32, i32, i32, i32) {
|
||||
let mut cpu_temp = 0;
|
||||
let mut gpu_temp = 0;
|
||||
let mut cpu_fan = 0;
|
||||
let mut gpu_fan = 0;
|
||||
|
||||
let mut entries = match fs::read_dir("/sys/class/hwmon").await {
|
||||
Ok(e) => e,
|
||||
Err(_) => return (0, 0, 0, 0),
|
||||
};
|
||||
|
||||
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||
let path = entry.path();
|
||||
let name_path = path.join("name");
|
||||
|
||||
if let Ok(name_str) = fs::read_to_string(&name_path).await {
|
||||
let name = name_str.trim();
|
||||
|
||||
if name == "k10temp" || name == "coretemp" || name == "zenpower" {
|
||||
// Try temp1_input (TCtl/Package)
|
||||
if let Ok(temp) = read_val(&path.join("temp1_input")).await {
|
||||
cpu_temp = temp / 1000;
|
||||
}
|
||||
} else if name == "amdgpu" || name == "nvidia" {
|
||||
if let Ok(temp) = read_val(&path.join("temp1_input")).await {
|
||||
gpu_temp = temp / 1000;
|
||||
}
|
||||
} else if name == "asus" || name == "asus_custom_fan_curve" {
|
||||
if let Ok(fan) = read_val(&path.join("fan1_input")).await {
|
||||
cpu_fan = fan;
|
||||
}
|
||||
if let Ok(fan) = read_val(&path.join("fan2_input")).await {
|
||||
gpu_fan = fan;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(cpu_temp, gpu_temp, cpu_fan, gpu_fan)
|
||||
}
|
||||
|
||||
async fn read_val(path: &PathBuf) -> Result<i32, ()> {
|
||||
let s = fs::read_to_string(path).await.map_err(|_| ())?;
|
||||
s.trim().parse::<i32>().map_err(|_| ())
|
||||
}
|
||||
|
||||
async fn read_power() -> i32 {
|
||||
let mut p = 0;
|
||||
// Try BAT0 then BAT1
|
||||
if let Ok(v) = read_val(&PathBuf::from("/sys/class/power_supply/BAT0/power_now")).await {
|
||||
p = v.abs();
|
||||
} else if let Ok(v) = read_val(&PathBuf::from("/sys/class/power_supply/BAT1/power_now")).await {
|
||||
p = v.abs();
|
||||
}
|
||||
|
||||
// Check status
|
||||
if let Ok(s) = fs::read_to_string("/sys/class/power_supply/BAT0/status").await {
|
||||
if s.trim() == "Discharging" {
|
||||
return -p;
|
||||
}
|
||||
}
|
||||
p
|
||||
}
|
||||
@@ -47,7 +47,6 @@ pub fn setup_system_page(ui: &MainWindow, _config: Arc<Mutex<Config>>) {
|
||||
ui.global::<SystemPageData>().set_screen_auto_brightness(-1);
|
||||
ui.global::<SystemPageData>().set_mcu_powersave(-1);
|
||||
ui.global::<SystemPageData>().set_mini_led_mode(-1);
|
||||
ui.global::<SystemPageData>().set_screenpad_brightness(-1);
|
||||
ui.global::<SystemPageData>().set_ppt_pl1_spl(MINMAX);
|
||||
ui.global::<SystemPageData>().set_ppt_pl2_sppt(MINMAX);
|
||||
ui.global::<SystemPageData>().set_ppt_pl3_fppt(MINMAX);
|
||||
@@ -296,7 +295,7 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
|
||||
log::error!("Failed to create platform proxy: {}", e);
|
||||
})
|
||||
.unwrap();
|
||||
let backlight = BacklightProxy::builder(&conn)
|
||||
let _backlight = BacklightProxy::builder(&conn)
|
||||
.build()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -397,23 +396,7 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
|
||||
|
||||
set_ui_props_async!(handle, platform, SystemPageData, enable_ppt_group);
|
||||
|
||||
set_ui_props_async!(handle, backlight, SystemPageData, screenpad_brightness);
|
||||
if let Ok(value) = backlight.screenpad_gamma().await {
|
||||
handle
|
||||
.upgrade_in_event_loop(move |handle| {
|
||||
handle
|
||||
.global::<SystemPageData>()
|
||||
.set_screenpad_gamma(value.parse().unwrap_or(1.0));
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
set_ui_props_async!(
|
||||
handle,
|
||||
backlight,
|
||||
SystemPageData,
|
||||
screenpad_sync_with_primary
|
||||
);
|
||||
set_ui_props_async!(handle, platform, SystemPageData, enable_ppt_group);
|
||||
|
||||
let platform_copy = platform.clone();
|
||||
handle
|
||||
@@ -536,25 +519,11 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
|
||||
"Setting Throttle policy on AC failed"
|
||||
);
|
||||
|
||||
set_ui_callbacks!(handle,
|
||||
SystemPageData(as i32),
|
||||
backlight.screenpad_brightness(as i32),
|
||||
"Screenpad successfully set to {}",
|
||||
"Setting screenpad brightness failed"
|
||||
);
|
||||
|
||||
set_ui_callbacks!(handle,
|
||||
SystemPageData(as bool),
|
||||
backlight.screenpad_sync_with_primary(as bool),
|
||||
"Screenpad successfully set to {}",
|
||||
"Setting screenpad brightness failed"
|
||||
);
|
||||
|
||||
set_ui_callbacks!(handle,
|
||||
SystemPageData(.parse().unwrap_or(1.0)),
|
||||
backlight.screenpad_gamma(.to_string().as_str()),
|
||||
"Screenpad successfully set to {}",
|
||||
"Setting screenpad brightness failed"
|
||||
platform_copy.change_platform_profile_on_battery(.into()),
|
||||
"Throttle policy on battery enabled: {}",
|
||||
"Setting Throttle policy on AC failed"
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
||||
46
status.txt
Normal file
46
status.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
interactive rebase in progress; onto 5d4b164b
|
||||
Last commands done (5 commands done):
|
||||
pick 19dc1b28 # feat(asusd): Implement threaded Aura animator and hardware coordination
|
||||
pick d92478e8 # feat(rog-control-center): Implement logic for fans, aura, and monitoring
|
||||
(see more in file .git/rebase-merge/done)
|
||||
Next command to do (1 remaining command):
|
||||
pick 43227544 # chore: update dependencies and config
|
||||
(use "git rebase --edit-todo" to view and edit)
|
||||
You are currently rebasing branch 'main' on '5d4b164b'.
|
||||
(fix conflicts and then run "git rebase --continue")
|
||||
(use "git rebase --skip" to skip this patch)
|
||||
(use "git rebase --abort" to check out the original branch)
|
||||
|
||||
Changes to be committed:
|
||||
(use "git restore --staged <file>..." to unstage)
|
||||
modified: rog-control-center/Cargo.toml
|
||||
modified: rog-control-center/src/mocking.rs
|
||||
modified: rog-control-center/src/notify.rs
|
||||
modified: rog-control-center/src/tray.rs
|
||||
new file: rog-control-center/src/ui/setup_fan_curve_custom.rs
|
||||
modified: rog-control-center/src/ui/setup_fans.rs
|
||||
new file: rog-control-center/src/ui/setup_screenpad.rs
|
||||
new file: rog-control-center/src/ui/setup_slash.rs
|
||||
new file: rog-control-center/src/ui/setup_status.rs
|
||||
modified: rog-control-center/src/ui/setup_system.rs
|
||||
|
||||
Unmerged paths:
|
||||
(use "git restore --staged <file>..." to unstage)
|
||||
(use "git add <file>..." to mark resolution)
|
||||
both modified: rog-control-center/src/main.rs
|
||||
both modified: rog-control-center/src/ui/mod.rs
|
||||
both modified: rog-control-center/src/ui/setup_aura.rs
|
||||
|
||||
Untracked files:
|
||||
(use "git add <file>..." to include in what will be committed)
|
||||
diagnose.sh
|
||||
status.txt
|
||||
|
||||
--- LOG ---
|
||||
commit 55b7c245568e9eef869a3b69a57ef28b90000ab0
|
||||
Author: mihai2mn <mihai2mn@users.noreply.gitlab.com>
|
||||
Date: Sat Jan 24 16:53:08 2026 +0100
|
||||
|
||||
feat(asusd): Implement threaded Aura animator and hardware coordination
|
||||
--- REBASE DIR ---
|
||||
.git/rebase-merge
|
||||
Reference in New Issue
Block a user