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_aura = { path = "../rog-aura" }
|
||||||
rog_profiles = { path = "../rog-profiles" }
|
rog_profiles = { path = "../rog-profiles" }
|
||||||
rog_platform = { path = "../rog-platform" }
|
rog_platform = { path = "../rog-platform" }
|
||||||
|
rog_slash = { path = "../rog-slash" }
|
||||||
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", default-features = false }
|
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", default-features = false }
|
||||||
dmi_id = { path = "../dmi-id" }
|
dmi_id = { path = "../dmi-id" }
|
||||||
|
|
||||||
@@ -39,12 +40,14 @@ env_logger.workspace = true
|
|||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
zbus.workspace = true
|
zbus.workspace = true
|
||||||
|
ron.workspace = true
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
notify-rust.workspace = true
|
notify-rust.workspace = true
|
||||||
concat-idents.workspace = true
|
concat-idents.workspace = true
|
||||||
futures-util.workspace = true
|
futures-util.workspace = true
|
||||||
|
|
||||||
versions.workspace = true
|
versions.workspace = true
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
|
||||||
[dependencies.slint]
|
[dependencies.slint]
|
||||||
git = "https://github.com/slint-ui/slint.git"
|
git = "https://github.com/slint-ui/slint.git"
|
||||||
|
|||||||
@@ -11,16 +11,21 @@ use gumdrop::Options;
|
|||||||
use log::{debug, info, warn, LevelFilter};
|
use log::{debug, info, warn, LevelFilter};
|
||||||
use rog_control_center::cli_options::CliStart;
|
use rog_control_center::cli_options::CliStart;
|
||||||
use rog_control_center::config::Config;
|
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::error::Result;
|
||||||
use rog_control_center::notify::start_notifications;
|
use rog_control_center::notify::start_notifications;
|
||||||
use rog_control_center::slint::ComponentHandle;
|
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::ui::setup_window;
|
||||||
use rog_control_center::zbus_proxies::{
|
use rog_control_center::zbus_proxies::{
|
||||||
AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH,
|
AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH,
|
||||||
};
|
};
|
||||||
use rog_control_center::{print_versions, MainWindow};
|
use rog_control_center::{print_versions, MainWindow, TrayTooltip};
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
@@ -165,17 +170,33 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
start_notifications(config.clone(), &rt)?;
|
start_notifications(config.clone(), &rt)?;
|
||||||
|
|
||||||
if enable_tray_icon {
|
let (tray_tx, mut tray_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
init_tray(supported_properties, config.clone());
|
// 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();
|
// i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();
|
||||||
|
|
||||||
if !startup_in_background {
|
if !startup_in_background {
|
||||||
if let Ok(mut app_state) = app_state.lock() {
|
if let Ok(mut app_state) = app_state.lock() {
|
||||||
*app_state = AppState::MainWindowShouldOpen;
|
*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() {
|
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/"));
|
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 || {
|
thread::spawn(move || {
|
||||||
let mut state = AppState::StartingUp;
|
let mut state = AppState::StartingUp;
|
||||||
loop {
|
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 {
|
if is_rog_ally {
|
||||||
let config_copy_2 = config.clone();
|
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 || {
|
newui.window().on_close_requested(move || {
|
||||||
exit(0);
|
exit(0);
|
||||||
});
|
});
|
||||||
@@ -239,9 +263,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
let config_copy = config.clone();
|
let config_copy = config.clone();
|
||||||
let app_state_copy = app_state.clone();
|
let app_state_copy = app_state.clone();
|
||||||
// Avoid moving the original `prefetched_supported` into the
|
let stats_tx_loop = stats_tx.clone();
|
||||||
// closure — clone an Arc for the closure to capture.
|
|
||||||
let pref_for_invoke = prefetched_supported.clone();
|
|
||||||
slint::invoke_from_event_loop(move || {
|
slint::invoke_from_event_loop(move || {
|
||||||
UI.with(|ui| {
|
UI.with(|ui| {
|
||||||
let app_state_copy = app_state_copy.clone();
|
let app_state_copy = app_state_copy.clone();
|
||||||
@@ -256,7 +278,7 @@ async fn main() -> Result<()> {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let config_copy_2 = config_copy.clone();
|
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 || {
|
newui.window().on_close_requested(move || {
|
||||||
if let Ok(mut app_state) = app_state_copy.lock() {
|
if let Ok(mut app_state) = app_state_copy.lock() {
|
||||||
*app_state = AppState::MainWindowClosed;
|
*app_state = AppState::MainWindowClosed;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use rog_platform::supported::{
|
|||||||
PlatformProfileFunctions, RogBiosSupportedFunctions, SupportedFunctions,
|
PlatformProfileFunctions, RogBiosSupportedFunctions, SupportedFunctions,
|
||||||
};
|
};
|
||||||
use rog_profiles::fan_curve_set::{CurveData, FanCurveSet};
|
use rog_profiles::fan_curve_set::{CurveData, FanCurveSet};
|
||||||
use supergfxctl::pci_device::{GfxMode, GfxPower};
|
|
||||||
|
|
||||||
use crate::error::Result;
|
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,
|
//! stored statuses about the system state. This is done through either direct,
|
||||||
//! intoify, zbus notifications or similar methods.
|
//! intoify, zbus notifications or similar methods.
|
||||||
//!
|
//!
|
||||||
@@ -11,9 +11,10 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use notify_rust::{Hint, Notification, Timeout};
|
use notify_rust::{Hint, Notification, Timeout};
|
||||||
|
use rog_dbus::zbus_platform::PlatformProxy;
|
||||||
|
use rog_platform::platform::PlatformProfile;
|
||||||
use rog_platform::power::AsusPower;
|
use rog_platform::power::AsusPower;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use supergfxctl::pci_device::GfxPower;
|
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
@@ -26,64 +27,67 @@ const NOTIF_HEADER: &str = "ROG Control";
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct EnabledNotifications {
|
pub struct EnabledNotifications {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub receive_notify_gfx: bool,
|
pub receive_notify_platform_profile: bool,
|
||||||
pub receive_notify_gfx_status: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EnabledNotifications {
|
impl Default for EnabledNotifications {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
receive_notify_gfx: true,
|
receive_notify_platform_profile: true,
|
||||||
receive_notify_gfx_status: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_dpu_status_mon(config: Arc<Mutex<Config>>) {
|
/// Start monitoring for platform profile changes (triggered by Fn+F5 or software)
|
||||||
use supergfxctl::pci_device::Device;
|
/// and display an OSD notification when the profile changes.
|
||||||
let dev = Device::find().unwrap_or_default();
|
fn start_platform_profile_mon(config: Arc<Mutex<Config>>, rt: &Runtime) {
|
||||||
let mut found_dgpu = false; // just for logging
|
let enabled_notifications_copy = config.clone();
|
||||||
for dev in dev {
|
rt.spawn(async move {
|
||||||
if dev.is_dgpu() {
|
let conn = match zbus::Connection::system().await {
|
||||||
info!(
|
Ok(c) => c,
|
||||||
"Found dGPU: {}, starting status notifications",
|
Err(e) => {
|
||||||
dev.pci_id()
|
error!("zbus signal: platform_profile_mon: {e}");
|
||||||
);
|
return;
|
||||||
let enabled_notifications_copy = config.clone();
|
}
|
||||||
// Plain old thread is perfectly fine since most of this is potentially blocking
|
};
|
||||||
std::thread::spawn(move || {
|
let proxy = match PlatformProxy::builder(&conn).build().await {
|
||||||
let mut last_status = GfxPower::Unknown;
|
Ok(p) => p,
|
||||||
loop {
|
Err(e) => {
|
||||||
std::thread::sleep(Duration::from_millis(1500));
|
error!("zbus signal: platform_profile_mon proxy: {e}");
|
||||||
if let Ok(status) = dev.get_runtime_status() {
|
return;
|
||||||
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
|
// Get initial profile to avoid notification on startup
|
||||||
{
|
let mut last_profile = proxy.platform_profile().await.ok();
|
||||||
continue;
|
|
||||||
}
|
info!("Started platform profile change monitor");
|
||||||
}
|
use futures_util::StreamExt;
|
||||||
// Required check because status cycles through
|
let mut stream = proxy.receive_platform_profile_changed().await;
|
||||||
// active/unknown/suspended
|
while let Some(e) = stream.next().await {
|
||||||
do_gpu_status_notif("dGPU status changed:", &status)
|
if let Ok(config) = enabled_notifications_copy.lock() {
|
||||||
.show()
|
if !config.notifications.enabled
|
||||||
.unwrap()
|
|| !config.notifications.receive_notify_platform_profile
|
||||||
.on_close(|_| ());
|
{
|
||||||
debug!("dGPU status changed: {:?}", &status);
|
continue;
|
||||||
}
|
|
||||||
last_status = status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
found_dgpu = true;
|
if let Ok(new_profile) = e.get().await {
|
||||||
break;
|
// 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(
|
pub fn start_notifications(
|
||||||
@@ -141,45 +145,8 @@ pub fn start_notifications(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("Attempting to start plain dgpu status monitor");
|
info!("Starting platform profile change monitor");
|
||||||
start_dpu_status_mon(config.clone());
|
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])
|
Ok(vec![blocking])
|
||||||
}
|
}
|
||||||
@@ -197,14 +164,26 @@ where
|
|||||||
notif
|
notif
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_gpu_status_notif(message: &str, data: &GfxPower) -> Notification {
|
/// Create a notification for platform profile (power mode) changes.
|
||||||
let mut notif = base_notification(message, &<&str>::from(data).to_owned());
|
/// Uses profile-specific icons and user-friendly names.
|
||||||
let icon = match data {
|
fn do_platform_profile_notif(message: &str, profile: &PlatformProfile) -> Notification {
|
||||||
GfxPower::Suspended => "asus_notif_blue",
|
let profile_name = match profile {
|
||||||
GfxPower::Off => "asus_notif_green",
|
PlatformProfile::Balanced => "Balanced",
|
||||||
GfxPower::AsusDisabled => "asus_notif_white",
|
PlatformProfile::Performance => "Performance",
|
||||||
GfxPower::AsusMuxDiscreet | GfxPower::Active => "asus_notif_red",
|
PlatformProfile::Quiet => "Quiet",
|
||||||
GfxPower::Unknown => "gpu-integrated",
|
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.icon(icon);
|
||||||
notif
|
notif
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::sync::{Arc, Mutex, OnceLock};
|
use std::sync::{Arc, Mutex, OnceLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use ksni::{Handle, Icon, TrayMethods};
|
use ksni::{Icon, TrayMethods};
|
||||||
use log::{info, warn};
|
use log::info;
|
||||||
use rog_platform::platform::Properties;
|
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::config::Config;
|
||||||
use crate::zbus_proxies::{AppState, ROGCCZbusProxyBlocking};
|
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/";
|
const TRAY_ICON_PATH: &str = "/usr/share/icons/hicolor/512x512/apps/";
|
||||||
|
|
||||||
struct Icons {
|
struct Icons {
|
||||||
rog_blue: Icon,
|
#[allow(dead_code)]
|
||||||
rog_red: Icon,
|
rog_red: Icon,
|
||||||
rog_green: Icon,
|
|
||||||
rog_white: Icon,
|
|
||||||
gpu_integrated: Icon,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ICONS: OnceLock<Icons> = OnceLock::new();
|
static ICONS: OnceLock<Icons> = OnceLock::new();
|
||||||
@@ -59,6 +53,29 @@ struct AsusTray {
|
|||||||
current_title: String,
|
current_title: String,
|
||||||
current_icon: Icon,
|
current_icon: Icon,
|
||||||
proxy: ROGCCZbusProxyBlocking<'static>,
|
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 {
|
impl ksni::Tray for AsusTray {
|
||||||
@@ -78,6 +95,26 @@ impl ksni::Tray for AsusTray {
|
|||||||
ksni::Status::Active
|
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>> {
|
fn menu(&self) -> Vec<ksni::MenuItem<Self>> {
|
||||||
use ksni::menu::*;
|
use ksni::menu::*;
|
||||||
vec![
|
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>>`
|
/// 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 {
|
tokio::spawn(async move {
|
||||||
let user_con = zbus::blocking::Connection::session().unwrap();
|
let user_con = zbus::blocking::Connection::session().unwrap();
|
||||||
let proxy = ROGCCZbusProxyBlocking::new(&user_con).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_title: TRAY_LABEL.to_string(),
|
||||||
current_icon: rog_red.clone(),
|
current_icon: rog_red.clone(),
|
||||||
proxy,
|
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
|
// TODO: return an error to the UI
|
||||||
let mut tray;
|
let tray;
|
||||||
match tray_init.disable_dbus_name(true).spawn().await {
|
match tray_init.disable_dbus_name(true).spawn().await {
|
||||||
Ok(t) => tray = t,
|
Ok(t) => tray = t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -181,72 +179,32 @@ pub fn init_tray(_supported_properties: Vec<Properties>, config: Arc<Mutex<Confi
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("Tray started");
|
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 {
|
ICONS.get_or_init(|| Icons {
|
||||||
rog_blue,
|
|
||||||
rog_red: rog_red.clone(),
|
rog_red: rog_red.clone(),
|
||||||
rog_green,
|
|
||||||
rog_white,
|
|
||||||
gpu_integrated,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut has_supergfx = false;
|
info!("Started ROGTray");
|
||||||
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");
|
loop {
|
||||||
let mut last_power = GfxPower::Unknown;
|
tokio::select! {
|
||||||
let dev = find_dgpu();
|
_ = stats_rx.changed() => {
|
||||||
loop {
|
let stats = stats_rx.borrow().clone();
|
||||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
let tray_update = tray.clone();
|
||||||
if let Ok(lock) = config.try_lock() {
|
tokio::spawn(async move {
|
||||||
if !lock.enable_tray_icon {
|
tray_update.update(move |t| {
|
||||||
return;
|
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 {
|
_ = tokio::time::sleep(Duration::from_millis(1000)) => {
|
||||||
if let Ok(mode) = gfx_proxy.mode().await {
|
if let Ok(lock) = config.try_lock() {
|
||||||
if let Ok(power) = gfx_proxy.power().await {
|
if !lock.enable_tray_icon {
|
||||||
if last_power != power {
|
return;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
pub mod setup_anime;
|
pub mod setup_anime;
|
||||||
pub mod setup_aura;
|
pub mod setup_aura;
|
||||||
|
pub mod setup_fan_curve_custom;
|
||||||
pub mod setup_fans;
|
pub mod setup_fans;
|
||||||
|
pub mod setup_screenpad;
|
||||||
|
pub mod setup_slash;
|
||||||
|
pub mod setup_status;
|
||||||
pub mod setup_system;
|
pub mod setup_system;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -11,9 +15,13 @@ use rog_dbus::list_iface_blocking;
|
|||||||
use slint::{ComponentHandle, SharedString, Weak};
|
use slint::{ComponentHandle, SharedString, Weak};
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::tray::TrayStats;
|
||||||
use crate::ui::setup_anime::setup_anime_page;
|
use crate::ui::setup_anime::setup_anime_page;
|
||||||
use crate::ui::setup_aura::setup_aura_page;
|
use crate::ui::setup_aura::setup_aura_page;
|
||||||
use crate::ui::setup_fans::setup_fan_curve_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::ui::setup_system::{setup_system_page, setup_system_page_callbacks};
|
||||||
use crate::{AppSettingsPageData, MainWindow};
|
use crate::{AppSettingsPageData, MainWindow};
|
||||||
|
|
||||||
@@ -84,7 +92,7 @@ pub fn show_toast(
|
|||||||
|
|
||||||
pub fn setup_window(
|
pub fn setup_window(
|
||||||
config: Arc<Mutex<Config>>,
|
config: Arc<Mutex<Config>>,
|
||||||
prefetched_supported: std::sync::Arc<Option<Vec<i32>>>,
|
stats_tx: tokio::sync::watch::Sender<TrayStats>,
|
||||||
) -> MainWindow {
|
) -> MainWindow {
|
||||||
slint::set_xdg_app_id("rog-control-center")
|
slint::set_xdg_app_id("rog-control-center")
|
||||||
.map_err(|e| warn!("Couldn't set application ID: {e:?}"))
|
.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.Platform".to_string()),
|
||||||
available.contains(&"xyz.ljones.Aura".to_string()),
|
available.contains(&"xyz.ljones.Aura".to_string()),
|
||||||
available.contains(&"xyz.ljones.Anime".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()),
|
available.contains(&"xyz.ljones.FanCurves".to_string()),
|
||||||
true, // GPU Configuration
|
true,
|
||||||
true, // App Settings
|
true,
|
||||||
true, // About
|
true,
|
||||||
]
|
]
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
@@ -116,21 +128,60 @@ pub fn setup_window(
|
|||||||
slint::quit_event_loop().unwrap();
|
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());
|
setup_app_settings_page(&ui, config.clone());
|
||||||
if available.contains(&"xyz.ljones.Platform".to_string()) {
|
if available.contains(&"xyz.ljones.Platform".to_string()) {
|
||||||
setup_system_page(&ui, config.clone());
|
setup_system_page(&ui, config.clone());
|
||||||
setup_system_page_callbacks(&ui, config.clone());
|
setup_system_page_callbacks(&ui, config.clone());
|
||||||
}
|
}
|
||||||
if available.contains(&"xyz.ljones.Aura".to_string()) {
|
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()) {
|
if available.contains(&"xyz.ljones.Anime".to_string()) {
|
||||||
setup_anime_page(&ui, config.clone());
|
setup_anime_page(&ui, config.clone());
|
||||||
}
|
}
|
||||||
if available.contains(&"xyz.ljones.FanCurves".to_string()) {
|
if available.contains(&"xyz.ljones.Slash".to_string()) {
|
||||||
setup_fan_curve_page(&ui, config);
|
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
|
ui
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,18 +208,31 @@ pub fn setup_app_settings_page(ui: &MainWindow, config: Arc<Mutex<Config>>) {
|
|||||||
lock.write();
|
lock.write();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Master notifications toggle
|
||||||
let config_copy = config.clone();
|
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() {
|
if let Ok(mut lock) = config_copy.try_lock() {
|
||||||
lock.notifications.enabled = enable;
|
lock.notifications.enabled = enable;
|
||||||
lock.write();
|
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() {
|
if let Ok(lock) = config.try_lock() {
|
||||||
global.set_run_in_background(lock.run_in_background);
|
global.set_run_in_background(lock.run_in_background);
|
||||||
global.set_startup_in_background(lock.startup_in_background);
|
global.set_startup_in_background(lock.startup_in_background);
|
||||||
global.set_enable_tray_icon(lock.enable_tray_icon);
|
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 std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
|
use rog_aura::animation::AnimationMode;
|
||||||
use rog_aura::keyboard::LaptopAuraPower;
|
use rog_aura::keyboard::LaptopAuraPower;
|
||||||
use rog_aura::{AuraDeviceType, PowerZones};
|
use rog_aura::{AuraDeviceType, Colour, PowerZones};
|
||||||
use rog_dbus::zbus_aura::AuraProxy;
|
use rog_dbus::zbus_aura::AuraProxy;
|
||||||
use slint::{ComponentHandle, Model, RgbaColor, SharedString};
|
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>> {
|
async fn find_aura_iface() -> Result<AuraProxy<'static>, Box<dyn std::error::Error>> {
|
||||||
let conn = zbus::Connection::system().await?;
|
let conn = zbus::Connection::system().await?;
|
||||||
let mgr = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?;
|
let f = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?;
|
||||||
let objs = mgr.get_managed_objects().await?;
|
let interfaces = f.get_managed_objects().await?;
|
||||||
let mut paths: Vec<zbus::zvariant::OwnedObjectPath> = objs
|
let mut aura_paths = Vec::new();
|
||||||
.iter()
|
for v in interfaces.iter() {
|
||||||
.filter(|(_, ifaces)| ifaces.keys().any(|k| k.as_str() == "xyz.ljones.Aura"))
|
for k in v.1.keys() {
|
||||||
.map(|(p, _)| p.clone())
|
if k.as_str() == "xyz.ljones.Aura" {
|
||||||
.collect();
|
println!("Found aura device at {}, {}", v.0, k);
|
||||||
if paths.len() > 1 {
|
aura_paths.push(v.0.clone());
|
||||||
log::debug!("Multiple aura devices: {paths:?}");
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let path = paths.pop().ok_or("No Aura interface")?;
|
if aura_paths.len() > 1 {
|
||||||
AuraProxy::builder(&conn)
|
println!("Multiple aura devices found: {aura_paths:?}");
|
||||||
.path(path)?
|
println!("TODO: enable selection");
|
||||||
.destination("xyz.ljones.Asusd")?
|
}
|
||||||
.build()
|
if let Some(path) = aura_paths.first() {
|
||||||
.await
|
return Ok(AuraProxy::builder(&conn)
|
||||||
.map_err(Into::into)
|
.path(path.clone())?
|
||||||
|
.destination("xyz.ljones.Asusd")?
|
||||||
|
.build()
|
||||||
|
.await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("No Aura interface".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn prefetch_supported_basic_modes() -> Option<Vec<i32>> {
|
pub fn setup_aura_page(ui: &MainWindow, _states: Arc<Mutex<Config>>) {
|
||||||
let proxy = find_aura_iface().await.ok()?;
|
ui.global::<AuraPageData>().on_cb_hex_from_colour(|c| {
|
||||||
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| {
|
|
||||||
format!("#{:02X}{:02X}{:02X}", c.red(), c.green(), c.blue()).into()
|
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();
|
let handle = ui.as_weak();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let Ok(aura) = find_aura_iface().await else {
|
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>(());
|
return Ok::<(), zbus::Error>(());
|
||||||
};
|
};
|
||||||
|
|
||||||
set_ui_props_async!(handle, aura, AuraPageData, brightness);
|
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, led_power);
|
||||||
set_ui_props_async!(handle, aura, AuraPageData, device_type);
|
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 {
|
if let Ok(mut pow3r) = aura.supported_power_zones().await {
|
||||||
let dev = aura
|
let dev_type = aura
|
||||||
.device_type()
|
.device_type()
|
||||||
.await
|
.await
|
||||||
.unwrap_or(AuraDeviceType::LaptopKeyboard2021);
|
.unwrap_or(AuraDeviceType::LaptopKeyboard2021);
|
||||||
|
log::debug!("Available LED power modes {pow3r:?}");
|
||||||
handle
|
handle
|
||||||
.upgrade_in_event_loop(move |handle| {
|
.upgrade_in_event_loop(move |handle| {
|
||||||
let names: Vec<SharedString> = handle
|
let names: Vec<SharedString> = handle
|
||||||
@@ -143,103 +99,282 @@ pub fn setup_aura_page(
|
|||||||
.get_power_zone_names()
|
.get_power_zone_names()
|
||||||
.iter()
|
.iter()
|
||||||
.collect();
|
.collect();
|
||||||
if dev.is_old_laptop() {
|
|
||||||
|
if dev_type.is_old_laptop() {
|
||||||
|
// Need to add the specific KeyboardAndLightbar
|
||||||
if pow3r.contains(&PowerZones::Keyboard)
|
if pow3r.contains(&PowerZones::Keyboard)
|
||||||
&& pow3r.contains(&PowerZones::Lightbar)
|
&& pow3r.contains(&PowerZones::Lightbar)
|
||||||
{
|
{
|
||||||
pow3r.push(PowerZones::KeyboardAndLightbar);
|
pow3r.push(PowerZones::KeyboardAndLightbar);
|
||||||
}
|
}
|
||||||
let n: Vec<SharedString> =
|
let names: Vec<SharedString> =
|
||||||
pow3r.iter().map(|z| names[(*z) as usize].clone()).collect();
|
pow3r.iter().map(|n| names[(*n) as usize].clone()).collect();
|
||||||
handle
|
handle
|
||||||
.global::<AuraPageData>()
|
.global::<AuraPageData>()
|
||||||
.set_power_zone_names_old(n.as_slice().into());
|
.set_power_zone_names_old(names.as_slice().into());
|
||||||
} else {
|
} else {
|
||||||
let p: Vec<SlintPowerZones> = pow3r.iter().map(|z| (*z).into()).collect();
|
let power: Vec<SlintPowerZones> =
|
||||||
|
pow3r.iter().map(|p| (*p).into()).collect();
|
||||||
|
|
||||||
handle
|
handle
|
||||||
.global::<AuraPageData>()
|
.global::<AuraPageData>()
|
||||||
.set_supported_power_zones(p.as_slice().into());
|
.set_supported_power_zones(power.as_slice().into());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
let proxy = aura.clone();
|
if let Ok(modes) = aura.supported_basic_modes().await {
|
||||||
let weak = handle.clone();
|
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
|
handle
|
||||||
.upgrade_in_event_loop(move |h| {
|
.upgrade_in_event_loop(move |handle| {
|
||||||
set_ui_callbacks!(h,
|
set_ui_callbacks!(handle,
|
||||||
AuraPageData(.into()),
|
AuraPageData(.into()),
|
||||||
proxy.brightness(.into()),
|
proxy_copy.brightness(.into()),
|
||||||
"Brightness set to {}",
|
"Keyboard LED brightness successfully set to {}",
|
||||||
"Brightness failed"
|
"Setting keyboard LED brightness failed"
|
||||||
);
|
);
|
||||||
|
|
||||||
let p = proxy.clone();
|
set_ui_callbacks!(handle,
|
||||||
let w = weak.clone();
|
AuraPageData(.into()),
|
||||||
h.global::<AuraPageData>().on_apply_led_mode_data(move || {
|
proxy_copy.led_mode(.into()),
|
||||||
let Some(ui) = w.upgrade() else { return };
|
"Keyboard LED mode successfully set to {}",
|
||||||
let slint_effect = ui.global::<AuraPageData>().get_led_mode_data();
|
"Setting keyboard LEDmode failed"
|
||||||
let raw: rog_aura::AuraEffect = slint_effect.into();
|
);
|
||||||
let pp = p.clone();
|
|
||||||
let t = w.clone();
|
let proxy_data = proxy_copy.clone();
|
||||||
tokio::spawn(async move {
|
let aura_soft = proxy_copy.clone();
|
||||||
let r = pp.set_led_mode_data(raw).await;
|
let handle_weak = handle.as_weak();
|
||||||
show_toast("LED mode applied".into(), "LED mode failed".into(), t, r);
|
|
||||||
|
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();
|
.ok();
|
||||||
|
|
||||||
let weak_power = handle.clone();
|
let handle_copy = handle.clone();
|
||||||
let proxy_power = aura.clone();
|
let proxy_copy = aura.clone();
|
||||||
handle
|
handle
|
||||||
.upgrade_in_event_loop(|h| {
|
.upgrade_in_event_loop(|handle| {
|
||||||
h.global::<AuraPageData>().on_cb_led_power(move |power| {
|
handle
|
||||||
let w = weak_power.clone();
|
.global::<AuraPageData>()
|
||||||
let p = proxy_power.clone();
|
.on_cb_led_power(move |power| {
|
||||||
let pw: LaptopAuraPower = power.into();
|
let handle_copy = handle_copy.clone();
|
||||||
tokio::spawn(async move {
|
let proxy_copy = aura.clone();
|
||||||
show_toast(
|
let power: LaptopAuraPower = power.into();
|
||||||
"Aura power updated".into(),
|
tokio::spawn(async move {
|
||||||
"Aura power failed".into(),
|
show_toast(
|
||||||
w,
|
"Aura power settings changed".into(),
|
||||||
p.set_led_power(pw).await,
|
"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();
|
.ok();
|
||||||
|
|
||||||
let stream_handle = handle.clone();
|
// Need to update the UI if the mode changes
|
||||||
let aura_stream = aura.clone();
|
let handle_copy = handle.clone();
|
||||||
|
// spawn required since the while let never exits
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
let mut x = proxy_copy.receive_led_mode_data_changed().await;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
let mut stream = aura_stream.receive_led_mode_data_changed().await;
|
while let Some(e) = x.next().await {
|
||||||
while let Some(e) = stream.next().await {
|
|
||||||
if let Ok(out) = e.get().await {
|
if let Ok(out) = e.get().await {
|
||||||
let raw: i32 = out.mode.into();
|
handle_copy
|
||||||
let data = out.into();
|
.upgrade_in_event_loop(move |handle| {
|
||||||
stream_handle
|
handle
|
||||||
.upgrade_in_event_loop(move |h| {
|
|
||||||
h.global::<AuraPageData>().invoke_update_led_mode_data(data);
|
|
||||||
let supported: Vec<i32> = h
|
|
||||||
.global::<AuraPageData>()
|
.global::<AuraPageData>()
|
||||||
.get_supported_basic_modes()
|
.invoke_update_led_mode_data(out.into());
|
||||||
.iter()
|
handle.invoke_external_colour_change();
|
||||||
.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();
|
|
||||||
})
|
})
|
||||||
.map_err(|e| error!("{e}"))
|
.map_err(|e| error!("{e:}"))
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
debug!("Aura setup done");
|
debug!("Aura setup tasks complete");
|
||||||
Ok(())
|
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 log::error;
|
||||||
use rog_dbus::zbus_fan_curves::FanCurvesProxy;
|
use rog_dbus::zbus_fan_curves::FanCurvesProxy;
|
||||||
@@ -8,7 +10,25 @@ use rog_profiles::fan_curve_set::CurveData;
|
|||||||
use slint::{ComponentHandle, Model, Weak};
|
use slint::{ComponentHandle, Model, Weak};
|
||||||
|
|
||||||
use crate::config::Config;
|
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(
|
pub fn update_fan_data(
|
||||||
handle: Weak<MainWindow>,
|
handle: Weak<MainWindow>,
|
||||||
@@ -19,7 +39,7 @@ pub fn update_fan_data(
|
|||||||
handle
|
handle
|
||||||
.upgrade_in_event_loop(move |handle| {
|
.upgrade_in_event_loop(move |handle| {
|
||||||
let global = handle.global::<FanPageData>();
|
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
|
let tmp: Vec<Node> = temp
|
||||||
.iter()
|
.iter()
|
||||||
.zip(pwm.iter())
|
.zip(pwm.iter())
|
||||||
@@ -33,61 +53,100 @@ pub fn update_fan_data(
|
|||||||
|
|
||||||
for fan in bal {
|
for fan in bal {
|
||||||
global.set_balanced_available(true);
|
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 {
|
match fan.fan {
|
||||||
rog_profiles::FanCurvePU::CPU => {
|
rog_profiles::FanCurvePU::CPU => {
|
||||||
global.set_cpu_fan_available(true);
|
global.set_cpu_fan_available(true);
|
||||||
global.set_balanced_cpu_enabled(fan.enabled);
|
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 => {
|
rog_profiles::FanCurvePU::GPU => {
|
||||||
global.set_gpu_fan_available(true);
|
global.set_gpu_fan_available(true);
|
||||||
global.set_balanced_gpu_enabled(fan.enabled);
|
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 => {
|
rog_profiles::FanCurvePU::MID => {
|
||||||
global.set_mid_fan_available(true);
|
global.set_mid_fan_available(true);
|
||||||
global.set_balanced_mid_enabled(fan.enabled);
|
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 {
|
for fan in perf {
|
||||||
global.set_performance_available(true);
|
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 {
|
match fan.fan {
|
||||||
rog_profiles::FanCurvePU::CPU => {
|
rog_profiles::FanCurvePU::CPU => {
|
||||||
global.set_cpu_fan_available(true);
|
global.set_cpu_fan_available(true);
|
||||||
global.set_performance_cpu_enabled(fan.enabled);
|
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 => {
|
rog_profiles::FanCurvePU::GPU => {
|
||||||
global.set_gpu_fan_available(true);
|
global.set_gpu_fan_available(true);
|
||||||
global.set_performance_gpu_enabled(fan.enabled);
|
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 => {
|
rog_profiles::FanCurvePU::MID => {
|
||||||
global.set_mid_fan_available(true);
|
global.set_mid_fan_available(true);
|
||||||
global.set_performance_mid_enabled(fan.enabled);
|
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 {
|
for fan in quiet {
|
||||||
global.set_quiet_available(true);
|
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 {
|
match fan.fan {
|
||||||
rog_profiles::FanCurvePU::CPU => {
|
rog_profiles::FanCurvePU::CPU => {
|
||||||
global.set_cpu_fan_available(true);
|
global.set_cpu_fan_available(true);
|
||||||
global.set_quiet_cpu_enabled(fan.enabled);
|
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 => {
|
rog_profiles::FanCurvePU::GPU => {
|
||||||
global.set_gpu_fan_available(true);
|
global.set_gpu_fan_available(true);
|
||||||
global.set_quiet_gpu_enabled(fan.enabled);
|
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 => {
|
rog_profiles::FanCurvePU::MID => {
|
||||||
global.set_mid_fan_available(true);
|
global.set_mid_fan_available(true);
|
||||||
global.set_quiet_mid_enabled(fan.enabled);
|
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 choices_for_ui = platform_profile_choices.clone();
|
||||||
let handle_next1 = handle_copy.clone();
|
let handle_next1 = handle_copy.clone();
|
||||||
if let Err(e) = handle_copy.upgrade_in_event_loop(move |handle| {
|
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 global = handle.global::<FanPageData>();
|
||||||
let fans1 = fans.clone();
|
let fans1 = fans.clone();
|
||||||
let choices = choices_for_ui.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);
|
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| {
|
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 fans = fans.clone();
|
||||||
let data: Vec<Node> = data.iter().collect();
|
let handle_weak = handle_weak_for_fans.clone();
|
||||||
let data = fan_data_for(fan, enabled, data);
|
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 {
|
tokio::spawn(async move {
|
||||||
fans.set_fan_curve(profile.into(), data)
|
show_toast(
|
||||||
.await
|
"Fan curve applied".into(),
|
||||||
.map_err(|e| error!("{e:}"))
|
"Failed to apply fan curve".into(),
|
||||||
.ok()
|
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:?}");
|
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_screen_auto_brightness(-1);
|
||||||
ui.global::<SystemPageData>().set_mcu_powersave(-1);
|
ui.global::<SystemPageData>().set_mcu_powersave(-1);
|
||||||
ui.global::<SystemPageData>().set_mini_led_mode(-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_pl1_spl(MINMAX);
|
||||||
ui.global::<SystemPageData>().set_ppt_pl2_sppt(MINMAX);
|
ui.global::<SystemPageData>().set_ppt_pl2_sppt(MINMAX);
|
||||||
ui.global::<SystemPageData>().set_ppt_pl3_fppt(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);
|
log::error!("Failed to create platform proxy: {}", e);
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let backlight = BacklightProxy::builder(&conn)
|
let _backlight = BacklightProxy::builder(&conn)
|
||||||
.build()
|
.build()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.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, platform, SystemPageData, enable_ppt_group);
|
||||||
|
|
||||||
set_ui_props_async!(handle, backlight, SystemPageData, screenpad_brightness);
|
set_ui_props_async!(handle, platform, SystemPageData, enable_ppt_group);
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
let platform_copy = platform.clone();
|
let platform_copy = platform.clone();
|
||||||
handle
|
handle
|
||||||
@@ -536,25 +519,11 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc<Mutex<Config>>)
|
|||||||
"Setting Throttle policy on AC failed"
|
"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,
|
set_ui_callbacks!(handle,
|
||||||
SystemPageData(as bool),
|
SystemPageData(as bool),
|
||||||
backlight.screenpad_sync_with_primary(as bool),
|
platform_copy.change_platform_profile_on_battery(.into()),
|
||||||
"Screenpad successfully set to {}",
|
"Throttle policy on battery enabled: {}",
|
||||||
"Setting screenpad brightness failed"
|
"Setting Throttle policy on AC 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"
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.ok();
|
.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