diff --git a/diagnose.sh b/diagnose.sh new file mode 100755 index 00000000..42e70c6f --- /dev/null +++ b/diagnose.sh @@ -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 diff --git a/rog-control-center/Cargo.toml b/rog-control-center/Cargo.toml index 3b0eddf9..5c1613b9 100644 --- a/rog-control-center/Cargo.toml +++ b/rog-control-center/Cargo.toml @@ -29,6 +29,7 @@ rog_dbus = { path = "../rog-dbus" } rog_aura = { path = "../rog-aura" } rog_profiles = { path = "../rog-profiles" } rog_platform = { path = "../rog-platform" } +rog_slash = { path = "../rog-slash" } supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", default-features = false } dmi_id = { path = "../dmi-id" } @@ -39,12 +40,14 @@ env_logger.workspace = true tokio.workspace = true serde.workspace = true zbus.workspace = true +ron.workspace = true dirs.workspace = true notify-rust.workspace = true concat-idents.workspace = true futures-util.workspace = true versions.workspace = true +serde_json = "1.0.149" [dependencies.slint] git = "https://github.com/slint-ui/slint.git" diff --git a/rog-control-center/src/main.rs b/rog-control-center/src/main.rs index 24a905b7..5b63a3b9 100644 --- a/rog-control-center/src/main.rs +++ b/rog-control-center/src/main.rs @@ -11,16 +11,21 @@ use gumdrop::Options; use log::{debug, info, warn, LevelFilter}; use rog_control_center::cli_options::CliStart; use rog_control_center::config::Config; +use tokio::runtime::Runtime; + +thread_local! { + pub static UI: std::cell::RefCell> = Default::default(); + pub static TRAY_TOOLTIP: std::cell::RefCell> = Default::default(); +} use rog_control_center::error::Result; use rog_control_center::notify::start_notifications; use rog_control_center::slint::ComponentHandle; -use rog_control_center::tray::init_tray; +use rog_control_center::tray::{init_tray, TrayEvent, TrayStats}; use rog_control_center::ui::setup_window; use rog_control_center::zbus_proxies::{ AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH, }; -use rog_control_center::{print_versions, MainWindow}; -use tokio::runtime::Runtime; +use rog_control_center::{print_versions, MainWindow, TrayTooltip}; #[tokio::main] async fn main() -> Result<()> { @@ -165,17 +170,33 @@ async fn main() -> Result<()> { start_notifications(config.clone(), &rt)?; - if enable_tray_icon { - init_tray(supported_properties, config.clone()); - } + let (tray_tx, mut tray_rx) = tokio::sync::mpsc::unbounded_channel(); + // Channel for broadcasting system stats to the tray tooltip + let (stats_tx, stats_rx) = tokio::sync::watch::channel(TrayStats::default()); - thread_local! { pub static UI: std::cell::RefCell> = Default::default()}; + if enable_tray_icon { + init_tray(supported_properties, config.clone(), tray_tx, stats_rx); + } // i_slint_backend_selector::with_platform(|_| Ok(())).unwrap(); if !startup_in_background { if let Ok(mut app_state) = app_state.lock() { *app_state = AppState::MainWindowShouldOpen; } + } else { + // Even in background, we need the UI handle for status polling and tray sync + let config_copy = config.clone(); + let stats_tx_copy = stats_tx.clone(); + slint::invoke_from_event_loop(move || { + UI.with(|ui_cell| { + let mut ui = ui_cell.borrow_mut(); + if ui.is_none() { + let newui = setup_window(config_copy, stats_tx_copy.clone()); + ui.replace(newui); + } + }); + }) + .ok(); } if std::env::var("RUST_TRANSLATIONS").is_ok() { @@ -187,18 +208,21 @@ async fn main() -> Result<()> { slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/")); } - // Prefetch supported Aura modes once at startup and move into the - // spawned UI thread so the UI uses a stable, immutable list. - let prefetched_supported: std::sync::Arc>> = std::sync::Arc::new( - rog_control_center::ui::setup_aura::prefetch_supported_basic_modes().await, - ); - thread::spawn(move || { let mut state = AppState::StartingUp; loop { + // Handle tray events + while let Ok(event) = tray_rx.try_recv() { + match event { + TrayEvent::ToggleTooltip(_, _) => { + // Native tooltip handled by ksni, no custom window needed + } + } + } + if is_rog_ally { let config_copy_2 = config.clone(); - let newui = setup_window(config.clone(), prefetched_supported.clone()); + let newui = setup_window(config.clone(), stats_tx.clone()); newui.window().on_close_requested(move || { exit(0); }); @@ -239,9 +263,7 @@ async fn main() -> Result<()> { let config_copy = config.clone(); let app_state_copy = app_state.clone(); - // Avoid moving the original `prefetched_supported` into the - // closure — clone an Arc for the closure to capture. - let pref_for_invoke = prefetched_supported.clone(); + let stats_tx_loop = stats_tx.clone(); slint::invoke_from_event_loop(move || { UI.with(|ui| { let app_state_copy = app_state_copy.clone(); @@ -256,7 +278,7 @@ async fn main() -> Result<()> { }); } else { let config_copy_2 = config_copy.clone(); - let newui = setup_window(config_copy, pref_for_invoke.clone()); + let newui = setup_window(config_copy, stats_tx_loop.clone()); newui.window().on_close_requested(move || { if let Ok(mut app_state) = app_state_copy.lock() { *app_state = AppState::MainWindowClosed; diff --git a/rog-control-center/src/mocking.rs b/rog-control-center/src/mocking.rs index 7059c170..5a124b79 100644 --- a/rog-control-center/src/mocking.rs +++ b/rog-control-center/src/mocking.rs @@ -8,7 +8,6 @@ use rog_platform::supported::{ PlatformProfileFunctions, RogBiosSupportedFunctions, SupportedFunctions, }; use rog_profiles::fan_curve_set::{CurveData, FanCurveSet}; -use supergfxctl::pci_device::{GfxMode, GfxPower}; use crate::error::Result; diff --git a/rog-control-center/src/notify.rs b/rog-control-center/src/notify.rs index 7b1f1bfd..d529624b 100644 --- a/rog-control-center/src/notify.rs +++ b/rog-control-center/src/notify.rs @@ -1,4 +1,4 @@ -//! `update_and_notify` is responsible for both notifications *and* updating +//! update_and_notify is responsible for both notifications and updating //! stored statuses about the system state. This is done through either direct, //! intoify, zbus notifications or similar methods. //! @@ -11,9 +11,10 @@ use std::time::Duration; use log::{debug, error, info, warn}; use notify_rust::{Hint, Notification, Timeout}; +use rog_dbus::zbus_platform::PlatformProxy; +use rog_platform::platform::PlatformProfile; use rog_platform::power::AsusPower; use serde::{Deserialize, Serialize}; -use supergfxctl::pci_device::GfxPower; use tokio::runtime::Runtime; use tokio::task::JoinHandle; @@ -26,64 +27,67 @@ const NOTIF_HEADER: &str = "ROG Control"; #[serde(default)] pub struct EnabledNotifications { pub enabled: bool, - pub receive_notify_gfx: bool, - pub receive_notify_gfx_status: bool, + pub receive_notify_platform_profile: bool, } impl Default for EnabledNotifications { fn default() -> Self { Self { enabled: true, - receive_notify_gfx: true, - receive_notify_gfx_status: true, + receive_notify_platform_profile: true, } } } -fn start_dpu_status_mon(config: Arc>) { - use supergfxctl::pci_device::Device; - let dev = Device::find().unwrap_or_default(); - let mut found_dgpu = false; // just for logging - for dev in dev { - if dev.is_dgpu() { - info!( - "Found dGPU: {}, starting status notifications", - dev.pci_id() - ); - let enabled_notifications_copy = config.clone(); - // Plain old thread is perfectly fine since most of this is potentially blocking - std::thread::spawn(move || { - let mut last_status = GfxPower::Unknown; - loop { - std::thread::sleep(Duration::from_millis(1500)); - if let Ok(status) = dev.get_runtime_status() { - if status != GfxPower::Unknown && status != last_status { - if let Ok(config) = enabled_notifications_copy.lock() { - if !config.notifications.receive_notify_gfx_status - || !config.notifications.enabled - { - continue; - } - } - // Required check because status cycles through - // active/unknown/suspended - do_gpu_status_notif("dGPU status changed:", &status) - .show() - .unwrap() - .on_close(|_| ()); - debug!("dGPU status changed: {:?}", &status); - } - last_status = status; - } +/// Start monitoring for platform profile changes (triggered by Fn+F5 or software) +/// and display an OSD notification when the profile changes. +fn start_platform_profile_mon(config: Arc>, rt: &Runtime) { + let enabled_notifications_copy = config.clone(); + rt.spawn(async move { + let conn = match zbus::Connection::system().await { + Ok(c) => c, + Err(e) => { + error!("zbus signal: platform_profile_mon: {e}"); + return; + } + }; + let proxy = match PlatformProxy::builder(&conn).build().await { + Ok(p) => p, + Err(e) => { + error!("zbus signal: platform_profile_mon proxy: {e}"); + return; + } + }; + + // Get initial profile to avoid notification on startup + let mut last_profile = proxy.platform_profile().await.ok(); + + info!("Started platform profile change monitor"); + use futures_util::StreamExt; + let mut stream = proxy.receive_platform_profile_changed().await; + while let Some(e) = stream.next().await { + if let Ok(config) = enabled_notifications_copy.lock() { + if !config.notifications.enabled + || !config.notifications.receive_notify_platform_profile + { + continue; } - }); - found_dgpu = true; - break; + } + if let Ok(new_profile) = e.get().await { + // Only show notification if profile actually changed + if last_profile != Some(new_profile) { + debug!("Platform profile changed to: {:?}", new_profile); + if let Err(e) = do_platform_profile_notif("Power Profile:", &new_profile) + .show() + .map(|n| n.on_close(|_| ())) + { + warn!("Failed to show platform profile notification: {e}"); + } + last_profile = Some(new_profile); + } + } } - } - if !found_dgpu { - warn!("Did not find a dGPU on this system, dGPU status won't be avilable"); - } + }); } pub fn start_notifications( @@ -141,45 +145,8 @@ pub fn start_notifications( } }); - info!("Attempting to start plain dgpu status monitor"); - start_dpu_status_mon(config.clone()); - - // GPU MUX Mode notif - // TODO: need to get armoury attrs and iter to find - // let enabled_notifications_copy = config.clone(); - // tokio::spawn(async move { - // let conn = zbus::Connection::system().await.map_err(|e| { - // error!("zbus signal: receive_notify_gpu_mux_mode: {e}"); - // e - // })?; - // let proxy = PlatformProxy::new(&conn).await.map_err(|e| { - // error!("zbus signal: receive_notify_gpu_mux_mode: {e}"); - // e - // })?; - - // let mut actual_mux_mode = GpuMode::Error; - // if let Ok(mode) = proxy.gpu_mux_mode().await { - // actual_mux_mode = GpuMode::from(mode); - // } - - // info!("Started zbus signal thread: receive_notify_gpu_mux_mode"); - // while let Some(e) = - // proxy.receive_gpu_mux_mode_changed().await.next().await { if let - // Ok(config) = enabled_notifications_copy.lock() { if - // !config.notifications.enabled || !config.notifications.receive_notify_gfx { - // continue; - // } - // } - // if let Ok(out) = e.get().await { - // let mode = GpuMode::from(out); - // if mode == actual_mux_mode { - // continue; - // } - // do_mux_notification("Reboot required. BIOS GPU MUX mode set to", - // &mode).ok(); } - // } - // Ok::<(), zbus::Error>(()) - // }); + info!("Starting platform profile change monitor"); + start_platform_profile_mon(config.clone(), rt); Ok(vec![blocking]) } @@ -197,14 +164,26 @@ where notif } -fn do_gpu_status_notif(message: &str, data: &GfxPower) -> Notification { - let mut notif = base_notification(message, &<&str>::from(data).to_owned()); - let icon = match data { - GfxPower::Suspended => "asus_notif_blue", - GfxPower::Off => "asus_notif_green", - GfxPower::AsusDisabled => "asus_notif_white", - GfxPower::AsusMuxDiscreet | GfxPower::Active => "asus_notif_red", - GfxPower::Unknown => "gpu-integrated", +/// Create a notification for platform profile (power mode) changes. +/// Uses profile-specific icons and user-friendly names. +fn do_platform_profile_notif(message: &str, profile: &PlatformProfile) -> Notification { + let profile_name = match profile { + PlatformProfile::Balanced => "Balanced", + PlatformProfile::Performance => "Performance", + PlatformProfile::Quiet => "Quiet", + PlatformProfile::LowPower => "Low Power", + PlatformProfile::Custom => "Custom", + }; + let mut notif = base_notification(message, &profile_name.to_owned()); + + // Use appropriate icons for each profile + // These icons should be available in the system or ROG icon pack + let icon = match profile { + PlatformProfile::Balanced => "asus_notif_blue", // Blue for balanced + PlatformProfile::Performance => "asus_notif_red", // Red for performance + PlatformProfile::Quiet => "asus_notif_green", // Green for quiet/power saving + PlatformProfile::LowPower => "asus_notif_green", // Green for low power + PlatformProfile::Custom => "asus_notif_white", // White for custom }; notif.icon(icon); notif diff --git a/rog-control-center/src/tray.rs b/rog-control-center/src/tray.rs index ea806711..feab04fc 100644 --- a/rog-control-center/src/tray.rs +++ b/rog-control-center/src/tray.rs @@ -6,12 +6,9 @@ use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, OnceLock}; use std::time::Duration; -use ksni::{Handle, Icon, TrayMethods}; -use log::{info, warn}; +use ksni::{Icon, TrayMethods}; +use log::info; use rog_platform::platform::Properties; -use supergfxctl::pci_device::{Device, GfxMode, GfxPower}; -use supergfxctl::zbus_proxy::DaemonProxy as GfxProxy; -use versions::Versioning; use crate::config::Config; use crate::zbus_proxies::{AppState, ROGCCZbusProxyBlocking}; @@ -20,11 +17,8 @@ const TRAY_LABEL: &str = "ROG Control Center"; const TRAY_ICON_PATH: &str = "/usr/share/icons/hicolor/512x512/apps/"; struct Icons { - rog_blue: Icon, + #[allow(dead_code)] rog_red: Icon, - rog_green: Icon, - rog_white: Icon, - gpu_integrated: Icon, } static ICONS: OnceLock = OnceLock::new(); @@ -59,6 +53,29 @@ struct AsusTray { current_title: String, current_icon: Icon, proxy: ROGCCZbusProxyBlocking<'static>, + tray_channel: Option>, + // System stats for native tooltip + cpu_temp: String, + gpu_temp: String, + cpu_fan: String, + gpu_fan: String, + power_w: String, + power_profile: String, +} + +#[derive(Debug, Clone, Copy)] +pub enum TrayEvent { + ToggleTooltip(i32, i32), +} + +#[derive(Debug, Clone, Default)] +pub struct TrayStats { + pub cpu_temp: String, + pub gpu_temp: String, + pub cpu_fan: String, + pub gpu_fan: String, + pub power_w: String, + pub power_profile: String, } impl ksni::Tray for AsusTray { @@ -78,6 +95,26 @@ impl ksni::Tray for AsusTray { ksni::Status::Active } + fn activate(&mut self, x: i32, y: i32) { + if let Some(tx) = &self.tray_channel { + let _ = tx.send(TrayEvent::ToggleTooltip(x, y)); + } + } + + fn tool_tip(&self) -> ksni::ToolTip { + ksni::ToolTip { + title: "ROG Control Center".into(), + description: format!( + "Profile: {}\nCPU: {}°C | GPU: {}°C\nFans: {} / {} RPM\nPower: {} 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> { use ksni::menu::*; vec![ @@ -102,60 +139,13 @@ impl ksni::Tray for AsusTray { } } -async fn set_tray_icon_and_tip( - mode: GfxMode, - power: GfxPower, - tray: &mut Handle, - 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 { - 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>` -pub fn init_tray(_supported_properties: Vec, config: Arc>) { +pub fn init_tray( + _supported_properties: Vec, + config: Arc>, + tray_channel: tokio::sync::mpsc::UnboundedSender, + mut stats_rx: tokio::sync::watch::Receiver, +) { tokio::spawn(async move { let user_con = zbus::blocking::Connection::session().unwrap(); let proxy = ROGCCZbusProxyBlocking::new(&user_con).unwrap(); @@ -166,10 +156,18 @@ pub fn init_tray(_supported_properties: Vec, config: Arc tray = t, Err(e) => { @@ -181,72 +179,32 @@ pub fn init_tray(_supported_properties: Vec, config: Arc { - has_supergfx = true; - if let Ok(version) = gfx_proxy.version().await { - if let Some(version) = Versioning::new(&version) { - let curr_gfx = Versioning::new("5.2.0").unwrap(); - warn!("supergfxd version = {version}"); - if version < curr_gfx { - // Don't allow mode changing if too old a version - warn!("supergfxd found but is too old to use"); - has_supergfx = false; - } - } - } - } - Err(e) => match e { - zbus::Error::MethodError(_, _, message) => { - warn!( - "Couldn't get mode from supergfxd: {message:?}, the supergfxd service \ - may not be running or installed" - ) - } - _ => warn!("Couldn't get mode from supergfxd: {e:?}"), - }, - } + info!("Started ROGTray"); - info!("Started ROGTray"); - let mut last_power = GfxPower::Unknown; - let dev = find_dgpu(); - loop { - tokio::time::sleep(Duration::from_millis(1000)).await; - if let Ok(lock) = config.try_lock() { - if !lock.enable_tray_icon { - return; - } + loop { + tokio::select! { + _ = stats_rx.changed() => { + let stats = stats_rx.borrow().clone(); + let tray_update = tray.clone(); + tokio::spawn(async move { + tray_update.update(move |t| { + t.cpu_temp = stats.cpu_temp; + t.gpu_temp = stats.gpu_temp; + t.cpu_fan = stats.cpu_fan; + t.gpu_fan = stats.gpu_fan; + t.power_w = stats.power_w; + t.power_profile = stats.power_profile; + }).await; + }); } - if has_supergfx { - if let Ok(mode) = gfx_proxy.mode().await { - if let Ok(power) = gfx_proxy.power().await { - if last_power != power { - set_tray_icon_and_tip(mode, power, &mut tray, has_supergfx).await; - last_power = power; - } - } - } - } else if let Some(dev) = dev.as_ref() { - if let Ok(power) = dev.get_runtime_status() { - if last_power != power { - set_tray_icon_and_tip(GfxMode::Hybrid, power, &mut tray, has_supergfx) - .await; - last_power = power; + _ = tokio::time::sleep(Duration::from_millis(1000)) => { + if let Ok(lock) = config.try_lock() { + if !lock.enable_tray_icon { + return; } } } diff --git a/rog-control-center/src/ui/mod.rs b/rog-control-center/src/ui/mod.rs index 81a45e82..bf4d82b3 100644 --- a/rog-control-center/src/ui/mod.rs +++ b/rog-control-center/src/ui/mod.rs @@ -1,6 +1,10 @@ pub mod setup_anime; pub mod setup_aura; +pub mod setup_fan_curve_custom; pub mod setup_fans; +pub mod setup_screenpad; +pub mod setup_slash; +pub mod setup_status; pub mod setup_system; use std::sync::{Arc, Mutex}; @@ -11,9 +15,13 @@ use rog_dbus::list_iface_blocking; use slint::{ComponentHandle, SharedString, Weak}; use crate::config::Config; +use crate::tray::TrayStats; use crate::ui::setup_anime::setup_anime_page; use crate::ui::setup_aura::setup_aura_page; use crate::ui::setup_fans::setup_fan_curve_page; +use crate::ui::setup_screenpad::setup_screenpad; +use crate::ui::setup_slash::setup_slash; +use crate::ui::setup_status::setup_status; use crate::ui::setup_system::{setup_system_page, setup_system_page_callbacks}; use crate::{AppSettingsPageData, MainWindow}; @@ -84,7 +92,7 @@ pub fn show_toast( pub fn setup_window( config: Arc>, - prefetched_supported: std::sync::Arc>>, + stats_tx: tokio::sync::watch::Sender, ) -> MainWindow { slint::set_xdg_app_id("rog-control-center") .map_err(|e| warn!("Couldn't set application ID: {e:?}")) @@ -104,10 +112,14 @@ pub fn setup_window( available.contains(&"xyz.ljones.Platform".to_string()), available.contains(&"xyz.ljones.Aura".to_string()), available.contains(&"xyz.ljones.Anime".to_string()), + available.contains(&"xyz.ljones.Slash".to_string()), + false, + // Screenpad check (Backlight interface) + available.contains(&"xyz.ljones.Backlight".to_string()), available.contains(&"xyz.ljones.FanCurves".to_string()), - true, // GPU Configuration - true, // App Settings - true, // About + true, + true, + true, ] .into(), ); @@ -116,21 +128,60 @@ pub fn setup_window( slint::quit_event_loop().unwrap(); }); + // Auto-hide toast logic + let toast_gen = Arc::new(Mutex::new(0u64)); + let ui_weak = ui.as_weak(); + ui.on_start_toast_timer(move || { + let toast_gen_clone = toast_gen.clone(); + let ui_weak_clone = ui_weak.clone(); + let my_gen = { + if let Ok(mut g) = toast_gen.lock() { + *g += 1; + *g + } else { + 0 + } + }; + if my_gen > 0 { + tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_secs(4)).await; + if let Ok(g) = toast_gen_clone.lock() { + if *g == my_gen { + let _ = + ui_weak_clone.upgrade_in_event_loop(move |ui| ui.invoke_hide_toast()); + } + } + }); + } + }); + setup_app_settings_page(&ui, config.clone()); if available.contains(&"xyz.ljones.Platform".to_string()) { setup_system_page(&ui, config.clone()); setup_system_page_callbacks(&ui, config.clone()); } if available.contains(&"xyz.ljones.Aura".to_string()) { - setup_aura_page(&ui, config.clone(), prefetched_supported.as_ref().clone()); + setup_aura_page(&ui, config.clone()); } if available.contains(&"xyz.ljones.Anime".to_string()) { setup_anime_page(&ui, config.clone()); } - if available.contains(&"xyz.ljones.FanCurves".to_string()) { - setup_fan_curve_page(&ui, config); + if available.contains(&"xyz.ljones.Slash".to_string()) { + setup_slash(&ui, config.clone()); } + // We didn't capture the boolean above. Let's just run it, it handles its own availability check internally via async proxy creation. + + if available.contains(&"xyz.ljones.Backlight".to_string()) { + setup_screenpad(&ui, config.clone()); + } + + if available.contains(&"xyz.ljones.FanCurves".to_string()) { + setup_fan_curve_page(&ui, config.clone()); + } + + setup_status(&ui, config, stats_tx); + ui } @@ -157,18 +208,31 @@ pub fn setup_app_settings_page(ui: &MainWindow, config: Arc>) { lock.write(); } }); + + // Master notifications toggle let config_copy = config.clone(); - global.on_set_enable_dgpu_notifications(move |enable| { + global.on_set_notifications_enabled(move |enable| { if let Ok(mut lock) = config_copy.try_lock() { lock.notifications.enabled = enable; lock.write(); } }); + // Granular notification toggles + let config_copy = config.clone(); + global.on_set_notify_platform_profile(move |enable| { + if let Ok(mut lock) = config_copy.try_lock() { + lock.notifications.receive_notify_platform_profile = enable; + lock.write(); + } + }); + + // Initialize UI values from config if let Ok(lock) = config.try_lock() { global.set_run_in_background(lock.run_in_background); global.set_startup_in_background(lock.startup_in_background); global.set_enable_tray_icon(lock.enable_tray_icon); - global.set_enable_dgpu_notifications(lock.notifications.enabled); + global.set_notifications_enabled(lock.notifications.enabled); + global.set_notify_platform_profile(lock.notifications.receive_notify_platform_profile); } } diff --git a/rog-control-center/src/ui/setup_aura.rs b/rog-control-center/src/ui/setup_aura.rs index 5ef41040..1e1466e3 100644 --- a/rog-control-center/src/ui/setup_aura.rs +++ b/rog-control-center/src/ui/setup_aura.rs @@ -1,8 +1,9 @@ use std::sync::{Arc, Mutex}; use log::{debug, error, info}; +use rog_aura::animation::AnimationMode; use rog_aura::keyboard::LaptopAuraPower; -use rog_aura::{AuraDeviceType, PowerZones}; +use rog_aura::{AuraDeviceType, Colour, PowerZones}; use rog_dbus::zbus_aura::AuraProxy; use slint::{ComponentHandle, Model, RgbaColor, SharedString}; @@ -34,108 +35,63 @@ fn decode_hex(s: &str) -> RgbaColor { } } +/// Returns the first available Aura interface +// TODO: return all async fn find_aura_iface() -> Result, Box> { let conn = zbus::Connection::system().await?; - let mgr = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?; - let objs = mgr.get_managed_objects().await?; - let mut paths: Vec = objs - .iter() - .filter(|(_, ifaces)| ifaces.keys().any(|k| k.as_str() == "xyz.ljones.Aura")) - .map(|(p, _)| p.clone()) - .collect(); - if paths.len() > 1 { - log::debug!("Multiple aura devices: {paths:?}"); + let f = zbus::fdo::ObjectManagerProxy::new(&conn, "xyz.ljones.Asusd", "/").await?; + let interfaces = f.get_managed_objects().await?; + let mut aura_paths = Vec::new(); + for v in interfaces.iter() { + for k in v.1.keys() { + if k.as_str() == "xyz.ljones.Aura" { + println!("Found aura device at {}, {}", v.0, k); + aura_paths.push(v.0.clone()); + } + } } - let path = paths.pop().ok_or("No Aura interface")?; - AuraProxy::builder(&conn) - .path(path)? - .destination("xyz.ljones.Asusd")? - .build() - .await - .map_err(Into::into) + if aura_paths.len() > 1 { + println!("Multiple aura devices found: {aura_paths:?}"); + println!("TODO: enable selection"); + } + if let Some(path) = aura_paths.first() { + return Ok(AuraProxy::builder(&conn) + .path(path.clone())? + .destination("xyz.ljones.Asusd")? + .build() + .await?); + } + + Err("No Aura interface".into()) } -pub async fn prefetch_supported_basic_modes() -> Option> { - let proxy = find_aura_iface().await.ok()?; - let modes = proxy.supported_basic_modes().await.ok()?; - Some(modes.iter().map(|n| (*n).into()).collect()) -} - -pub fn setup_aura_page( - ui: &MainWindow, - _states: Arc>, - prefetched_supported: Option>, -) { - let g = ui.global::(); - g.on_cb_hex_from_colour(|c| { +pub fn setup_aura_page(ui: &MainWindow, _states: Arc>) { + ui.global::().on_cb_hex_from_colour(|c| { format!("#{:02X}{:02X}{:02X}", c.red(), c.green(), c.blue()).into() }); - g.on_cb_hex_to_colour(|s| decode_hex(s.as_str()).into()); + + ui.global::() + .on_cb_hex_to_colour(|s| decode_hex(s.as_str()).into()); let handle = ui.as_weak(); tokio::spawn(async move { let Ok(aura) = find_aura_iface().await else { - info!("No aura interfaces"); + info!("This device appears to have no aura interfaces"); return Ok::<(), zbus::Error>(()); }; set_ui_props_async!(handle, aura, AuraPageData, brightness); + set_ui_props_async!(handle, aura, AuraPageData, led_mode); + set_ui_props_async!(handle, aura, AuraPageData, led_mode_data); set_ui_props_async!(handle, aura, AuraPageData, led_power); set_ui_props_async!(handle, aura, AuraPageData, device_type); - if let Ok(data) = aura.led_mode_data().await { - let d = data.into(); - handle - .upgrade_in_event_loop(move |h| { - h.global::().invoke_update_led_mode_data(d); - }) - .ok(); - } - - let modes_vec: Vec = 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 = aura.led_mode().await.ok().map(|m| m.into()); - - handle - .upgrade_in_event_loop(move |handle| { - let names = handle.global::().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::() - .set_supported_basic_modes(raws.as_slice().into()); - handle - .global::() - .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::() - .set_current_available_mode(idx); - } - }) - .map_err(|e| error!("{e}")) - .ok(); - if let Ok(mut pow3r) = aura.supported_power_zones().await { - let dev = aura + let dev_type = aura .device_type() .await .unwrap_or(AuraDeviceType::LaptopKeyboard2021); + log::debug!("Available LED power modes {pow3r:?}"); handle .upgrade_in_event_loop(move |handle| { let names: Vec = handle @@ -143,103 +99,282 @@ pub fn setup_aura_page( .get_power_zone_names() .iter() .collect(); - if dev.is_old_laptop() { + + if dev_type.is_old_laptop() { + // Need to add the specific KeyboardAndLightbar if pow3r.contains(&PowerZones::Keyboard) && pow3r.contains(&PowerZones::Lightbar) { pow3r.push(PowerZones::KeyboardAndLightbar); } - let n: Vec = - pow3r.iter().map(|z| names[(*z) as usize].clone()).collect(); + let names: Vec = + pow3r.iter().map(|n| names[(*n) as usize].clone()).collect(); handle .global::() - .set_power_zone_names_old(n.as_slice().into()); + .set_power_zone_names_old(names.as_slice().into()); } else { - let p: Vec = pow3r.iter().map(|z| (*z).into()).collect(); + let power: Vec = + pow3r.iter().map(|p| (*p).into()).collect(); + handle .global::() - .set_supported_power_zones(p.as_slice().into()); + .set_supported_power_zones(power.as_slice().into()); } }) .ok(); } - let proxy = aura.clone(); - let weak = handle.clone(); + if let Ok(modes) = aura.supported_basic_modes().await { + log::debug!("Available LED modes {modes:?}"); + + // Check if only Static mode is available (enable software animation) + let static_only = modes.len() == 1 && modes.iter().any(|m| *m == 0.into()); + + // Clone proxy for callbacks + let aura_for_animation = aura.clone(); + + handle + .upgrade_in_event_loop(move |handle| { + let m: Vec = modes.iter().map(|n| (*n).into()).collect(); + handle + .global::() + .set_supported_basic_modes(m.as_slice().into()); + // Get the translated names + let names = handle.global::().get_mode_names(); + + let res: Vec = names + .iter() + .enumerate() + .filter(|(n, _)| modes.contains(&(*n as i32).into()) && *n != 9) + .map(|(_, i)| i) + .collect(); + handle + .global::() + .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::() + .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::() + .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::().get_led_mode_data(); + let c1 = data.colour1; + let c2 = data.colour2; + + let c1_rog = Colour { + r: c1.red(), + g: c1.green(), + b: c1.blue(), + }; + let c2_rog = Colour { + r: c2.red(), + g: c2.green(), + b: c2.blue(), + }; + + let anim_mode = match mode { + 1 => AnimationMode::Rainbow { speed_ms: 100 }, + 2 => AnimationMode::ColorCycle { + speed_ms: 200, + colors: vec![ + Colour { r: 255, g: 0, b: 0 }, + Colour { r: 0, g: 255, b: 0 }, + Colour { r: 0, g: 0, b: 255 }, + ], + }, + 3 => AnimationMode::Breathe { + speed_ms: 100, + color1: c1_rog, + color2: c2_rog, + }, + 4 => AnimationMode::Pulse { + speed_ms: 50, + color: c1_rog, + min_brightness: 0.2, + max_brightness: 1.0, + }, + _ => AnimationMode::None, + }; + tokio::spawn(async move { + let json = + serde_json::to_string(&anim_mode).unwrap_or_default(); + if anim_mode == AnimationMode::None { + if let Err(e) = aura_inner.stop_animation().await { + error!("Failed to stop animation: {e}"); + } + } else { + if let Err(e) = aura_inner.start_animation(json).await { + error!("Failed to start animation: {e}"); + } + } + }); + }); + } + }) + .map_err(|e| error!("{e:}")) + .ok(); + } + + let proxy_copy = aura.clone(); handle - .upgrade_in_event_loop(move |h| { - set_ui_callbacks!(h, + .upgrade_in_event_loop(move |handle| { + set_ui_callbacks!(handle, AuraPageData(.into()), - proxy.brightness(.into()), - "Brightness set to {}", - "Brightness failed" + proxy_copy.brightness(.into()), + "Keyboard LED brightness successfully set to {}", + "Setting keyboard LED brightness failed" ); - let p = proxy.clone(); - let w = weak.clone(); - h.global::().on_apply_led_mode_data(move || { - let Some(ui) = w.upgrade() else { return }; - let slint_effect = ui.global::().get_led_mode_data(); - let raw: rog_aura::AuraEffect = slint_effect.into(); - let pp = p.clone(); - let t = w.clone(); - tokio::spawn(async move { - let r = pp.set_led_mode_data(raw).await; - show_toast("LED mode applied".into(), "LED mode failed".into(), t, r); + set_ui_callbacks!(handle, + AuraPageData(.into()), + proxy_copy.led_mode(.into()), + "Keyboard LED mode successfully set to {}", + "Setting keyboard LEDmode failed" + ); + + let proxy_data = proxy_copy.clone(); + let aura_soft = proxy_copy.clone(); + let handle_weak = handle.as_weak(); + + handle + .global::() + .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::().get_soft_animation_mode(); + if soft_mode != 0 { + let c1 = data.colour1; + let c2 = data.colour2; + let c1_rog = Colour { + r: c1.red(), + g: c1.green(), + b: c1.blue(), + }; + let c2_rog = Colour { + r: c2.red(), + g: c2.green(), + b: c2.blue(), + }; + + let anim_mode = match soft_mode { + 1 => AnimationMode::Rainbow { speed_ms: 100 }, + 2 => AnimationMode::ColorCycle { + speed_ms: 200, + colors: vec![ + Colour { r: 255, g: 0, b: 0 }, + Colour { r: 0, g: 255, b: 0 }, + Colour { r: 0, g: 0, b: 255 }, + ], + }, + 3 => AnimationMode::Breathe { + speed_ms: 100, + color1: c1_rog, + color2: c2_rog, + }, + 4 => AnimationMode::Pulse { + speed_ms: 50, + color: c1_rog, + min_brightness: 0.2, + max_brightness: 1.0, + }, + _ => AnimationMode::None, + }; + + let aura_s = aura_soft.clone(); + tokio::spawn(async move { + if let Ok(json) = serde_json::to_string(&anim_mode) { + if let Err(e) = aura_s.start_animation(json).await { + error!("Failed to update software animation: {e}"); + } + } + }); + } }); - }); - h.invoke_external_colour_change(); + + // set_ui_callbacks!(handle, + // AuraPageData(.clone().into()), + // proxy_copy.led_power(.into()), + // "Keyboard LED power successfully set to {:?}", + // "Setting keyboard power failed" + // ); + + handle.invoke_external_colour_change(); }) .ok(); - let weak_power = handle.clone(); - let proxy_power = aura.clone(); + let handle_copy = handle.clone(); + let proxy_copy = aura.clone(); handle - .upgrade_in_event_loop(|h| { - h.global::().on_cb_led_power(move |power| { - let w = weak_power.clone(); - let p = proxy_power.clone(); - let pw: LaptopAuraPower = power.into(); - tokio::spawn(async move { - show_toast( - "Aura power updated".into(), - "Aura power failed".into(), - w, - p.set_led_power(pw).await, - ); + .upgrade_in_event_loop(|handle| { + handle + .global::() + .on_cb_led_power(move |power| { + let handle_copy = handle_copy.clone(); + let proxy_copy = aura.clone(); + let power: LaptopAuraPower = power.into(); + tokio::spawn(async move { + show_toast( + "Aura power settings changed".into(), + "Failed to set Aura power settings".into(), + handle_copy, + proxy_copy.set_led_power(power).await, + ); + }); }); - }); }) - .map_err(|e| error!("{e}")) + .map_err(|e| error!("{e:}")) .ok(); - let stream_handle = handle.clone(); - let aura_stream = aura.clone(); + // Need to update the UI if the mode changes + let handle_copy = handle.clone(); + // spawn required since the while let never exits tokio::spawn(async move { + let mut x = proxy_copy.receive_led_mode_data_changed().await; use futures_util::StreamExt; - let mut stream = aura_stream.receive_led_mode_data_changed().await; - while let Some(e) = stream.next().await { + while let Some(e) = x.next().await { if let Ok(out) = e.get().await { - let raw: i32 = out.mode.into(); - let data = out.into(); - stream_handle - .upgrade_in_event_loop(move |h| { - h.global::().invoke_update_led_mode_data(data); - let supported: Vec = h + handle_copy + .upgrade_in_event_loop(move |handle| { + handle .global::() - .get_supported_basic_modes() - .iter() - .collect(); - let idx = supported.iter().position(|&x| x == raw).unwrap_or(0) as i32; - h.global::().set_current_available_mode(idx); - h.invoke_external_colour_change(); + .invoke_update_led_mode_data(out.into()); + handle.invoke_external_colour_change(); }) - .map_err(|e| error!("{e}")) + .map_err(|e| error!("{e:}")) .ok(); } } }); - debug!("Aura setup done"); + debug!("Aura setup tasks complete"); Ok(()) }); } diff --git a/rog-control-center/src/ui/setup_fan_curve_custom.rs b/rog-control-center/src/ui/setup_fan_curve_custom.rs new file mode 100644 index 00000000..da89e0b4 --- /dev/null +++ b/rog-control-center/src/ui/setup_fan_curve_custom.rs @@ -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, + pub gpu_curve: Vec, + pub enabled: bool, +} + +#[derive(Clone)] +struct SysfsPaths { + root: PathBuf, +} + +impl SysfsPaths { + fn new() -> Option { + 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 { + let s = fs::read_to_string(path)?; + s.trim() + .parse::() + .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, + fan_type: FanType, + enabled: bool, + nodes: Vec, +) { + // 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 = 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); + } + }); +} diff --git a/rog-control-center/src/ui/setup_fans.rs b/rog-control-center/src/ui/setup_fans.rs index 5ee7f647..bca587af 100644 --- a/rog-control-center/src/ui/setup_fans.rs +++ b/rog-control-center/src/ui/setup_fans.rs @@ -1,4 +1,6 @@ -use std::sync::{Arc, Mutex}; +use crate::ui::show_toast; +use std::collections::HashMap; +use std::sync::{Arc, Mutex, OnceLock}; use log::error; use rog_dbus::zbus_fan_curves::FanCurvesProxy; @@ -8,7 +10,25 @@ use rog_profiles::fan_curve_set::CurveData; use slint::{ComponentHandle, Model, Weak}; use crate::config::Config; -use crate::{FanPageData, FanType, MainWindow, Node}; +use crate::{FanPageData, FanType, MainWindow, Node, Profile}; + +// Isolated Rust-side cache for fan curves (not affected by Slint reactivity) +type FanCacheKey = (i32, i32); // (Profile as i32, FanType as i32) +static FAN_CACHE: OnceLock>>> = OnceLock::new(); + +fn fan_cache() -> &'static Mutex>> { + FAN_CACHE.get_or_init(|| Mutex::new(HashMap::new())) +} + +fn cache_fan_curve(profile: Profile, fan_type: FanType, nodes: Vec) { + 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> { + let key = (profile as i32, fan_type as i32); + fan_cache().lock().unwrap().get(&key).cloned() +} pub fn update_fan_data( handle: Weak, @@ -19,7 +39,7 @@ pub fn update_fan_data( handle .upgrade_in_event_loop(move |handle| { let global = handle.global::(); - let collect = |temp: &[u8], pwm: &[u8]| -> slint::ModelRc { + let _collect = |temp: &[u8], pwm: &[u8]| -> slint::ModelRc { let tmp: Vec = temp .iter() .zip(pwm.iter()) @@ -33,61 +53,100 @@ pub fn update_fan_data( for fan in bal { global.set_balanced_available(true); + let nodes_vec: Vec = fan + .temp + .iter() + .zip(fan.pwm.iter()) + .map(|(x, y)| Node { + x: *x as f32, + y: *y as f32, + }) + .collect(); + let nodes: slint::ModelRc = nodes_vec.as_slice().into(); match fan.fan { rog_profiles::FanCurvePU::CPU => { global.set_cpu_fan_available(true); global.set_balanced_cpu_enabled(fan.enabled); - global.set_balanced_cpu(collect(&fan.temp, &fan.pwm)) + global.set_balanced_cpu(nodes.clone()); + cache_fan_curve(Profile::Balanced, FanType::CPU, nodes_vec); } rog_profiles::FanCurvePU::GPU => { global.set_gpu_fan_available(true); global.set_balanced_gpu_enabled(fan.enabled); - global.set_balanced_gpu(collect(&fan.temp, &fan.pwm)) + global.set_balanced_gpu(nodes.clone()); + cache_fan_curve(Profile::Balanced, FanType::GPU, nodes_vec); } rog_profiles::FanCurvePU::MID => { global.set_mid_fan_available(true); global.set_balanced_mid_enabled(fan.enabled); - global.set_balanced_mid(collect(&fan.temp, &fan.pwm)) + global.set_balanced_mid(nodes.clone()); + cache_fan_curve(Profile::Balanced, FanType::Middle, nodes_vec); } } } for fan in perf { global.set_performance_available(true); + let nodes_vec: Vec = fan + .temp + .iter() + .zip(fan.pwm.iter()) + .map(|(x, y)| Node { + x: *x as f32, + y: *y as f32, + }) + .collect(); + let nodes: slint::ModelRc = nodes_vec.as_slice().into(); match fan.fan { rog_profiles::FanCurvePU::CPU => { global.set_cpu_fan_available(true); global.set_performance_cpu_enabled(fan.enabled); - global.set_performance_cpu(collect(&fan.temp, &fan.pwm)) + global.set_performance_cpu(nodes.clone()); + cache_fan_curve(Profile::Performance, FanType::CPU, nodes_vec); } rog_profiles::FanCurvePU::GPU => { global.set_gpu_fan_available(true); global.set_performance_gpu_enabled(fan.enabled); - global.set_performance_gpu(collect(&fan.temp, &fan.pwm)) + global.set_performance_gpu(nodes.clone()); + cache_fan_curve(Profile::Performance, FanType::GPU, nodes_vec); } rog_profiles::FanCurvePU::MID => { global.set_mid_fan_available(true); global.set_performance_mid_enabled(fan.enabled); - global.set_performance_mid(collect(&fan.temp, &fan.pwm)) + global.set_performance_mid(nodes.clone()); + cache_fan_curve(Profile::Performance, FanType::Middle, nodes_vec); } } } for fan in quiet { global.set_quiet_available(true); + let nodes_vec: Vec = fan + .temp + .iter() + .zip(fan.pwm.iter()) + .map(|(x, y)| Node { + x: *x as f32, + y: *y as f32, + }) + .collect(); + let nodes: slint::ModelRc = nodes_vec.as_slice().into(); match fan.fan { rog_profiles::FanCurvePU::CPU => { global.set_cpu_fan_available(true); global.set_quiet_cpu_enabled(fan.enabled); - global.set_quiet_cpu(collect(&fan.temp, &fan.pwm)) + global.set_quiet_cpu(nodes.clone()); + cache_fan_curve(Profile::Quiet, FanType::CPU, nodes_vec); } rog_profiles::FanCurvePU::GPU => { global.set_gpu_fan_available(true); global.set_quiet_gpu_enabled(fan.enabled); - global.set_quiet_gpu(collect(&fan.temp, &fan.pwm)) + global.set_quiet_gpu(nodes.clone()); + cache_fan_curve(Profile::Quiet, FanType::GPU, nodes_vec); } rog_profiles::FanCurvePU::MID => { global.set_mid_fan_available(true); global.set_quiet_mid_enabled(fan.enabled); - global.set_quiet_mid(collect(&fan.temp, &fan.pwm)) + global.set_quiet_mid(nodes.clone()); + cache_fan_curve(Profile::Quiet, FanType::Middle, nodes_vec); } } } @@ -171,6 +230,7 @@ pub fn setup_fan_curve_page(ui: &MainWindow, _config: Arc>) { let choices_for_ui = platform_profile_choices.clone(); let handle_next1 = handle_copy.clone(); if let Err(e) = handle_copy.upgrade_in_event_loop(move |handle| { + let handle_weak_for_fans = handle.as_weak(); let global = handle.global::(); let fans1 = fans.clone(); let choices = choices_for_ui.clone(); @@ -212,17 +272,103 @@ pub fn setup_fan_curve_page(ui: &MainWindow, _config: Arc>) { update_fan_data(handle_next, balanced, perf, quiet); }); }); + + let handle_weak_for_cancel = handle_weak_for_fans.clone(); global.on_set_fan_data(move |fan, profile, enabled, data| { + if crate::ui::setup_fan_curve_custom::is_custom_fan_supported() { + let handle_weak = handle_weak_for_fans.clone(); + let data: Vec = 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::(); + match fan { + FanType::CPU => g.set_is_busy_cpu(false), + FanType::GPU => g.set_is_busy_gpu(false), + FanType::Middle => g.set_is_busy_mid(false), + } + info!("MainThread: cleared busy flag for {:?}", fan); + }); + }); + + return; + } + let fans = fans.clone(); - let data: Vec = data.iter().collect(); - let data = fan_data_for(fan, enabled, data); + let handle_weak = handle_weak_for_fans.clone(); + let nodes_vec: Vec = data.iter().collect(); + let _data_copy = nodes_vec.clone(); + let cache_copy = nodes_vec.clone(); // Clone for cache update + let fan_data = fan_data_for(fan, enabled, nodes_vec); tokio::spawn(async move { - fans.set_fan_curve(profile.into(), data) - .await - .map_err(|e| error!("{e:}")) - .ok() + show_toast( + "Fan curve applied".into(), + "Failed to apply fan curve".into(), + handle_weak.clone(), + fans.set_fan_curve(profile.into(), fan_data).await, + ); + let _ = handle_weak.upgrade_in_event_loop(move |h| { + let g = h.global::(); + // 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::(); + + // 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 = 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:?}"); } diff --git a/rog-control-center/src/ui/setup_screenpad.rs b/rog-control-center/src/ui/setup_screenpad.rs new file mode 100644 index 00000000..2babee35 --- /dev/null +++ b/rog-control-center/src/ui/setup_screenpad.rs @@ -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>) { + 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::().set_brightness(val); + // Assume power is on if brightness > 0 + h.global::().set_power(val > 0); + }) + .ok(); + } + + if let Ok(gamma_str) = backlight.screenpad_gamma().await { + if let Ok(gamma) = gamma_str.parse::() { + handle + .upgrade_in_event_loop(move |h| { + h.global::().set_gamma(gamma); + }) + .ok(); + } + } + + if let Ok(sync) = backlight.screenpad_sync_with_primary().await { + handle + .upgrade_in_event_loop(move |h| { + h.global::().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::(); + + // 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::().set_brightness(target); + }) + .ok(); + }); + }); + }) + .ok(); + + // Optional: Value watches for external changes + // (Similar to setup_system.rs if needed) + }); +} diff --git a/rog-control-center/src/ui/setup_slash.rs b/rog-control-center/src/ui/setup_slash.rs new file mode 100644 index 00000000..e9428bc7 --- /dev/null +++ b/rog-control-center/src/ui/setup_slash.rs @@ -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>) { + let ui_weak = ui.as_weak(); + + tokio::spawn(async move { + // Find the Slash interface proxy + let proxies = match find_iface_async::("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::(); + + 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::(); + 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::(); + 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::(); + let model = d.get_modes(); + for (i, m) in model.iter().enumerate() { + if m == mode_str { + d.set_mode_index(i as i32); + break; + } + } + } + }); + } + }); +} diff --git a/rog-control-center/src/ui/setup_status.rs b/rog-control-center/src/ui/setup_status.rs new file mode 100644 index 00000000..f3c98c22 --- /dev/null +++ b/rog-control-center/src/ui/setup_status.rs @@ -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>, + stats_tx: tokio::sync::watch::Sender, +) { + let ui_weak = ui.as_weak(); + + tokio::spawn(async move { + let mut power_history: VecDeque = 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::(); + 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 { + let s = fs::read_to_string(path).await.map_err(|_| ())?; + s.trim().parse::().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 +} diff --git a/rog-control-center/src/ui/setup_system.rs b/rog-control-center/src/ui/setup_system.rs index fb41168c..96b8029e 100644 --- a/rog-control-center/src/ui/setup_system.rs +++ b/rog-control-center/src/ui/setup_system.rs @@ -47,7 +47,6 @@ pub fn setup_system_page(ui: &MainWindow, _config: Arc>) { ui.global::().set_screen_auto_brightness(-1); ui.global::().set_mcu_powersave(-1); ui.global::().set_mini_led_mode(-1); - ui.global::().set_screenpad_brightness(-1); ui.global::().set_ppt_pl1_spl(MINMAX); ui.global::().set_ppt_pl2_sppt(MINMAX); ui.global::().set_ppt_pl3_fppt(MINMAX); @@ -296,7 +295,7 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc>) log::error!("Failed to create platform proxy: {}", e); }) .unwrap(); - let backlight = BacklightProxy::builder(&conn) + let _backlight = BacklightProxy::builder(&conn) .build() .await .map_err(|e| { @@ -397,23 +396,7 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc>) set_ui_props_async!(handle, platform, SystemPageData, enable_ppt_group); - set_ui_props_async!(handle, backlight, SystemPageData, screenpad_brightness); - if let Ok(value) = backlight.screenpad_gamma().await { - handle - .upgrade_in_event_loop(move |handle| { - handle - .global::() - .set_screenpad_gamma(value.parse().unwrap_or(1.0)); - }) - .ok(); - } - - set_ui_props_async!( - handle, - backlight, - SystemPageData, - screenpad_sync_with_primary - ); + set_ui_props_async!(handle, platform, SystemPageData, enable_ppt_group); let platform_copy = platform.clone(); handle @@ -536,25 +519,11 @@ pub fn setup_system_page_callbacks(ui: &MainWindow, _states: Arc>) "Setting Throttle policy on AC failed" ); - set_ui_callbacks!(handle, - SystemPageData(as i32), - backlight.screenpad_brightness(as i32), - "Screenpad successfully set to {}", - "Setting screenpad brightness failed" - ); - set_ui_callbacks!(handle, SystemPageData(as bool), - backlight.screenpad_sync_with_primary(as bool), - "Screenpad successfully set to {}", - "Setting screenpad brightness failed" - ); - - set_ui_callbacks!(handle, - SystemPageData(.parse().unwrap_or(1.0)), - backlight.screenpad_gamma(.to_string().as_str()), - "Screenpad successfully set to {}", - "Setting screenpad brightness failed" + platform_copy.change_platform_profile_on_battery(.into()), + "Throttle policy on battery enabled: {}", + "Setting Throttle policy on AC failed" ); }) .ok(); diff --git a/status.txt b/status.txt new file mode 100644 index 00000000..2c6e41b2 --- /dev/null +++ b/status.txt @@ -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 ..." 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 ..." to unstage) + (use "git add ..." 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 ..." to include in what will be committed) + diagnose.sh + status.txt + +--- LOG --- +commit 55b7c245568e9eef869a3b69a57ef28b90000ab0 +Author: mihai2mn +Date: Sat Jan 24 16:53:08 2026 +0100 + + feat(asusd): Implement threaded Aura animator and hardware coordination +--- REBASE DIR --- +.git/rebase-merge