diff --git a/Cargo.lock b/Cargo.lock index 762de6ac..fcf5ee9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,22 +116,22 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ - "concurrent-queue", + "concurrent-queue 1.2.4", "event-listener", "futures-core", ] [[package]] name = "async-executor" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" dependencies = [ + "async-lock", "async-task", - "concurrent-queue", + "concurrent-queue 2.0.0", "fastrand", "futures-lite", - "once_cell", "slab", ] @@ -155,7 +155,7 @@ checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7" dependencies = [ "async-lock", "autocfg", - "concurrent-queue", + "concurrent-queue 1.2.4", "futures-lite", "libc", "log", @@ -399,9 +399,9 @@ dependencies = [ [[package]] name = "calloop" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee" +checksum = "595eb0438b3c6d262395fe30e6de9a61beb57ea56290b00a07f227fe6e20cbf2" dependencies = [ "log", "nix 0.24.2", @@ -412,9 +412,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.74" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" [[package]] name = "cesu8" @@ -532,6 +532,15 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "concurrent-queue" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -603,6 +612,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossfont" version = "0.5.1" @@ -1786,9 +1804,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] @@ -2086,12 +2104,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "padlock" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10569378a1dacd9f30dbe7ae49e054d2c45dc2f8ee49899903e09c3924e8b6f" - [[package]] name = "pango" version = "0.15.10" @@ -2411,7 +2423,6 @@ dependencies = [ "tempfile", "tokio", "toml", - "tray-item", "zbus", ] @@ -2794,7 +2805,7 @@ dependencies = [ [[package]] name = "supergfxctl" version = "5.0.2" -source = "git+https://gitlab.com/asus-linux/supergfxctl.git#3ee8fdfaa1cc79d0fe116cbd67269603e4e7bafe" +source = "git+https://gitlab.com/asus-linux/supergfxctl.git#fa022efb54b752a3672a64c640bd7aebeb842e05" dependencies = [ "log", "logind-zbus", @@ -3029,24 +3040,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tray-item" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0914b62e00e8f51241806cb9f9c4ea6b10c75d94cae02c89278de6f4b98c7d0f" -dependencies = [ - "cocoa", - "core-graphics", - "gtk", - "libappindicator", - "libc", - "objc", - "objc-foundation", - "objc_id", - "padlock", - "winapi", -] - [[package]] name = "ttf-parser" version = "0.17.1" @@ -3138,9 +3131,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version-compare" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" @@ -3332,9 +3325,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f7aa8c655ad0b020787afb0b8f0656b2cfbef3452d619ea930e6b1aa1c5c1e9" +checksum = "2a0cc7962b5aaa0dfcebaeef0161eec6edf5f4606c12e6777fd7d392f52033a5" dependencies = [ "jni", "ndk-context", diff --git a/Cargo.toml b/Cargo.toml index ab0e19f3..a66d6857 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ version = "4.5.1" [workspace.dependencies] async-trait = "^0.1" -tokio = { version = "^1.21.2", features = ["macros", "rt-multi-thread", "time"]} +tokio = { version = "^1.21.2", features = ["macros", "rt-multi-thread", "time", "sync"]} concat-idents = "^1.1" dirs = "^4.0" smol = "^1.2" diff --git a/daemon/src/config.rs b/daemon/src/config.rs index a95d22f9..cf48e8c6 100644 --- a/daemon/src/config.rs +++ b/daemon/src/config.rs @@ -61,7 +61,7 @@ impl Config { pub fn read(&mut self) { let mut file = OpenOptions::new() .read(true) - .open(&CONFIG_PATH) + .open(CONFIG_PATH) .unwrap_or_else(|err| panic!("Error reading {}: {}", CONFIG_PATH, err)); let mut buf = String::new(); if let Ok(l) = file.read_to_string(&mut buf) { diff --git a/daemon/src/laptops.rs b/daemon/src/laptops.rs index 63860110..745a48d2 100644 --- a/daemon/src/laptops.rs +++ b/daemon/src/laptops.rs @@ -90,7 +90,7 @@ impl LedSupportFile { let mut loaded = false; let mut data = LedSupportFile::default(); // Load user configs first so they are first to be checked - if let Ok(mut file) = OpenOptions::new().read(true).open(&ASUS_LED_MODE_USER_CONF) { + if let Ok(mut file) = OpenOptions::new().read(true).open(ASUS_LED_MODE_USER_CONF) { let mut buf = String::new(); if let Ok(l) = file.read_to_string(&mut buf) { if l == 0 { @@ -107,7 +107,7 @@ impl LedSupportFile { } } // Load and append the default LED support data - if let Ok(mut file) = OpenOptions::new().read(true).open(&ASUS_LED_MODE_CONF) { + if let Ok(mut file) = OpenOptions::new().read(true).open(ASUS_LED_MODE_CONF) { let mut buf = String::new(); if let Ok(l) = file.read_to_string(&mut buf) { if l == 0 { diff --git a/rog-control-center/Cargo.toml b/rog-control-center/Cargo.toml index 8f06ce75..933f80ea 100644 --- a/rog-control-center/Cargo.toml +++ b/rog-control-center/Cargo.toml @@ -12,7 +12,6 @@ egui = { git = "https://github.com/flukejones/egui" } eframe= { git = "https://github.com/flukejones/egui" } #eframe= { git = "https://github.com/emilk/egui", default-features = false, features = ["dark-light", "default_fonts", "wgpu"] } -tray-item = "0.7.1" libappindicator = "0.7" # Tray icon gtk = "0.15" diff --git a/rog-control-center/src/app.rs b/rog-control-center/src/app.rs index 51d71789..470cc73b 100644 --- a/rog-control-center/src/app.rs +++ b/rog-control-center/src/app.rs @@ -1,19 +1,20 @@ use std::{ f64::consts::PI, + io::Write, sync::{ atomic::{AtomicBool, AtomicU8, Ordering}, mpsc::Receiver, Arc, }, - time::{Duration, Instant}, io::Write, + time::{Duration, Instant}, }; use egui::{Button, RichText}; use rog_platform::supported::SupportedFunctions; use crate::{ - config::Config, error::Result, page_states::PageDataStates, tray::TrayToApp, Page, - RogDbusClientBlocking, get_ipc_file, SHOW_GUI, + config::Config, error::Result, get_ipc_file, page_states::PageDataStates, tray::TrayToApp, + Page, RogDbusClientBlocking, SHOW_GUI, }; pub struct RogApp<'a> { @@ -108,13 +109,13 @@ impl<'a> RogApp<'a> { fn check_app_cmds(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) { let Self { app_cmd, .. } = self; - + if let Ok(cmd) = app_cmd.try_recv() { match cmd { TrayToApp::Open => { dbg!(); get_ipc_file().unwrap().write_all(&[SHOW_GUI]).ok(); - }, + } TrayToApp::Quit => _frame.close(), } } @@ -126,7 +127,7 @@ impl<'a> eframe::App for RogApp<'a> { /// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`. fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { self.check_app_cmds(ctx, frame); - + let Self { supported, asus_dbus: dbus, diff --git a/rog-control-center/src/config.rs b/rog-control-center/src/config.rs index 69bd7d0c..90b20b32 100644 --- a/rog-control-center/src/config.rs +++ b/rog-control-center/src/config.rs @@ -6,17 +6,18 @@ use std::{ use serde_derive::{Deserialize, Serialize}; //use log::{error, info, warn}; -use crate::error::Error; +use crate::{error::Error, notify::EnabledNotifications}; const CFG_DIR: &str = "rog"; const CFG_FILE_NAME: &str = "rog-control-center.cfg"; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(default)] pub struct Config { pub run_in_background: bool, pub startup_in_background: bool, pub enable_notifications: bool, + pub enabled_notifications: EnabledNotifications, } impl Default for Config { @@ -25,6 +26,7 @@ impl Default for Config { run_in_background: true, startup_in_background: false, enable_notifications: true, + enabled_notifications: EnabledNotifications::default(), } } } @@ -65,7 +67,7 @@ impl Config { Err(Error::ConfigLoadFail) } - pub fn save(&self) -> Result<(), Error> { + pub fn save(&mut self, enabled_notifications: &EnabledNotifications) -> Result<(), Error> { let mut path = if let Some(dir) = dirs::config_dir() { dir } else { @@ -85,6 +87,7 @@ impl Config { .truncate(true) .open(&path)?; + self.enabled_notifications = enabled_notifications.clone(); let t = toml::to_string_pretty(&self).unwrap(); file.write_all(t.as_bytes())?; Ok(()) diff --git a/rog-control-center/src/lib.rs b/rog-control-center/src/lib.rs index 76c926db..f0324a8a 100644 --- a/rog-control-center/src/lib.rs +++ b/rog-control-center/src/lib.rs @@ -17,8 +17,8 @@ pub mod notify; pub mod page_states; pub mod pages; pub mod startup_error; -pub mod widgets; pub mod tray; +pub mod widgets; #[cfg(feature = "mocking")] pub use mocking::RogDbusClientBlocking; diff --git a/rog-control-center/src/main.rs b/rog-control-center/src/main.rs index 514a4a6f..b667b9cf 100644 --- a/rog-control-center/src/main.rs +++ b/rog-control-center/src/main.rs @@ -1,6 +1,7 @@ use eframe::{IconData, NativeOptions}; use log::{error, LevelFilter}; use rog_aura::layouts::KeyLayout; +use rog_control_center::notify::EnabledNotifications; use rog_control_center::tray::{AppToTray, TrayToApp}; use rog_control_center::{ config::Config, error::Result, get_ipc_file, notify::start_notifications, on_tmp_dir_exists, @@ -39,10 +40,6 @@ fn main() -> Result<()> { // Enter the runtime so that `tokio::spawn` is available immediately. let _enter = rt.enter(); - let (send, recv) = channel(); - let update_tray = Arc::new(Mutex::new(send)); - let app_cmd = Arc::new(init_tray(recv)); - let native_options = eframe::NativeOptions { vsync: true, decorated: true, @@ -64,14 +61,32 @@ fn main() -> Result<()> { }) .unwrap(); + let supported = match dbus.proxies().supported().supported_functions() { + Ok(s) => s, + Err(e) => { + eframe::run_native( + "ROG Control Center", + native_options.clone(), + Box::new(move |_| Box::new(AppErrorShow::new(e.to_string()))), + ); + SupportedFunctions::default() + } + }; + + let (send, recv) = channel(); + let update_tray = Arc::new(Mutex::new(send)); + let app_cmd = Arc::new(init_tray(supported.clone(), recv)); + // Startup let mut config = Config::load()?; let mut start_closed = config.startup_in_background; if config.startup_in_background { config.run_in_background = true; - config.save()?; + let tmp = config.enabled_notifications.clone(); // ends up being a double clone, oh well. + config.save(&tmp)?; } + let enabled_notifications = EnabledNotifications::tokio_mutex(&config); // Find and load a matching layout for laptop let mut file = OpenOptions::new() @@ -109,11 +124,11 @@ fn main() -> Result<()> { }; let states = setup_page_state_and_notifs( - layout.clone(), - &config, - native_options.clone(), + layout, &dbus, + enabled_notifications, update_tray, + &supported, ) .unwrap(); @@ -147,10 +162,10 @@ fn main() -> Result<()> { fn setup_page_state_and_notifs( keyboard_layout: KeyLayout, - config: &Config, - native_options: NativeOptions, dbus: &RogDbusClientBlocking, + enabled_notifications: Arc>, update_tray: Arc>>, + supported: &SupportedFunctions, ) -> Result { // Cheap method to alert to notifications rather than spinning a thread for each // This is quite different when done in a retained mode app @@ -160,7 +175,6 @@ fn setup_page_state_and_notifs( let anime_notified = Arc::new(AtomicBool::new(false)); let profiles_notified = Arc::new(AtomicBool::new(false)); let fans_notified = Arc::new(AtomicBool::new(false)); - let notifs_enabled = Arc::new(AtomicBool::new(config.enable_notifications)); start_notifications( charge_notified.clone(), @@ -169,33 +183,21 @@ fn setup_page_state_and_notifs( anime_notified.clone(), profiles_notified.clone(), fans_notified.clone(), - notifs_enabled.clone(), + enabled_notifications.clone(), update_tray, )?; - let supported = match dbus.proxies().supported().supported_functions() { - Ok(s) => s, - Err(e) => { - eframe::run_native( - "ROG Control Center", - native_options, - Box::new(move |_| Box::new(AppErrorShow::new(e.to_string()))), - ); - SupportedFunctions::default() - } - }; - PageDataStates::new( keyboard_layout, - notifs_enabled.clone(), - charge_notified.clone(), - bios_notified.clone(), - aura_notified.clone(), - anime_notified.clone(), - profiles_notified.clone(), - fans_notified.clone(), - &supported, - &dbus, + enabled_notifications, + charge_notified, + bios_notified, + aura_notified, + anime_notified, + profiles_notified, + fans_notified, + supported, + dbus, ) } diff --git a/rog-control-center/src/notify.rs b/rog-control-center/src/notify.rs index a6704d07..d39c8954 100644 --- a/rog-control-center/src/notify.rs +++ b/rog-control-center/src/notify.rs @@ -1,24 +1,71 @@ -use crate::{error::Result, tray::AppToTray}; -use notify_rust::{Hint, Notification, NotificationHandle}; +use crate::{config::Config, error::Result, tray::AppToTray}; +use notify_rust::{Hint, Notification, NotificationHandle, Urgency}; use rog_dbus::{ zbus_anime::AnimeProxy, zbus_led::LedProxy, zbus_platform::RogBiosProxy, zbus_power::PowerProxy, zbus_profile::ProfileProxy, }; use rog_platform::platform::GpuMode; use rog_profiles::Profile; +use serde::{Deserialize, Serialize}; use std::{ fmt::Display, + process::Command, sync::{ atomic::{AtomicBool, Ordering}, mpsc::Sender, Arc, Mutex, }, }; -use supergfxctl::pci_device::GfxPower; +use supergfxctl::{pci_device::GfxPower, zbus_proxy::DaemonProxy as SuperProxy}; use zbus::export::futures_util::{future, StreamExt}; const NOTIF_HEADER: &str = "ROG Control"; +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(default)] +pub struct EnabledNotifications { + pub receive_notify_post_boot_sound: bool, + pub receive_notify_panel_od: bool, + pub receive_notify_dgpu_disable: bool, + pub receive_notify_egpu_enable: bool, + pub receive_notify_gpu_mux_mode: bool, + pub receive_notify_charge_control_end_threshold: bool, + pub receive_notify_mains_online: bool, + pub receive_notify_profile: bool, + pub receive_notify_led: bool, + /// Anime + pub receive_power_states: bool, + pub receive_notify_gfx: bool, + pub receive_notify_gfx_status: bool, + pub all_enabled: bool, +} + +impl Default for EnabledNotifications { + fn default() -> Self { + Self { + receive_notify_post_boot_sound: false, + receive_notify_panel_od: true, + receive_notify_dgpu_disable: true, + receive_notify_egpu_enable: true, + receive_notify_gpu_mux_mode: true, + receive_notify_charge_control_end_threshold: true, + receive_notify_mains_online: false, + receive_notify_profile: true, + receive_notify_led: false, + receive_power_states: false, + receive_notify_gfx: false, + receive_notify_gfx_status: false, + all_enabled: false, + } + } +} + +impl EnabledNotifications { + pub fn tokio_mutex(config: &Config) -> Arc> { + Arc::new(Mutex::new(config.enabled_notifications.clone())) + } +} + macro_rules! notify { ($notifier:expr, $last_notif:ident) => { if let Some(notif) = $last_notif.take() { @@ -46,19 +93,19 @@ macro_rules! recv_notif { tokio::spawn(async move { let conn = zbus::Connection::system().await.unwrap(); let proxy = $proxy::new(&conn).await.unwrap(); - if let Ok(p) = proxy.$signal().await { - p.for_each(|e| { + if let Ok(mut p) = proxy.$signal().await { + while let Some(e) = p.next().await { if let Ok(out) = e.args() { - if notifs_enabled1.load(Ordering::SeqCst) { - if let Ok(ref mut lock) = last_notif.try_lock() { - notify!($notifier($msg, &out$(.$out_arg)+()), lock); + if let Ok(config) = notifs_enabled1.lock() { + if config.all_enabled && config.$signal { + if let Ok(ref mut lock) = last_notif.try_lock() { + notify!($notifier($msg, &out$(.$out_arg)+()), lock); + } + notified.store(true, Ordering::SeqCst); } } - notified.store(true, Ordering::SeqCst); } - future::ready(()) - }) - .await; + } }; }); }; @@ -73,8 +120,8 @@ pub fn start_notifications( anime_notified: Arc, profiles_notified: Arc, _fans_notified: Arc, - notifs_enabled: Arc, - update_tray: Arc>>, + enabled_notifications: Arc>, + update_tray: Arc>>, ) -> Result<()> { let last_notification: SharedHandle = Arc::new(Mutex::new(None)); @@ -84,7 +131,7 @@ pub fn start_notifications( receive_notify_post_boot_sound, bios_notified, last_notification, - notifs_enabled, + enabled_notifications, [on], "BIOS Post sound", do_notification @@ -95,7 +142,7 @@ pub fn start_notifications( receive_notify_panel_od, bios_notified, last_notification, - notifs_enabled, + enabled_notifications, [overdrive], "Panel Overdrive enabled:", do_notification @@ -106,7 +153,7 @@ pub fn start_notifications( receive_notify_dgpu_disable, bios_notified, last_notification, - notifs_enabled, + enabled_notifications, [disable], "BIOS dGPU disabled", do_notification @@ -117,7 +164,7 @@ pub fn start_notifications( receive_notify_egpu_enable, bios_notified, last_notification, - notifs_enabled, + enabled_notifications, [enable], "BIOS eGPU enabled", do_notification @@ -128,10 +175,10 @@ pub fn start_notifications( receive_notify_gpu_mux_mode, bios_notified, last_notification, - notifs_enabled, + enabled_notifications, [mode], - "BIOS GPU MUX mode (reboot required)", - mux_notification + "Reboot required. BIOS GPU MUX mode set to", + do_mux_notification ); // Charge notif @@ -140,7 +187,7 @@ pub fn start_notifications( receive_notify_charge_control_end_threshold, charge_notified, last_notification, - notifs_enabled, + enabled_notifications, [limit], "Battery charge limit changed to", do_notification @@ -151,7 +198,7 @@ pub fn start_notifications( receive_notify_mains_online, bios_notified, last_notification, - notifs_enabled, + enabled_notifications, [on], "AC Power power is", ac_power_notification @@ -163,7 +210,7 @@ pub fn start_notifications( receive_notify_profile, profiles_notified, last_notification, - notifs_enabled, + enabled_notifications, [profile], "Profile changed to", do_thermal_notif @@ -176,7 +223,7 @@ pub fn start_notifications( receive_notify_led, aura_notified, last_notification, - notifs_enabled, + enabled_notifications, [data mode_name], "Keyboard LED mode changed to", do_notification @@ -206,40 +253,73 @@ pub fn start_notifications( }; }); - let notifs_enabled1 = notifs_enabled.clone(); - let last_notif = last_notification.clone(); + recv_notif!( + SuperProxy, + receive_notify_gfx, + bios_notified, + last_notification, + enabled_notifications, + [mode], + "Gfx mode changed to", + do_notification + ); + + // recv_notif!( + // SuperProxy, + // receive_notify_action, + // bios_notified, + // last_gfx_action_notif, + // enabled_notifications, + // [action], + // "Gfx mode change requires", + // do_gfx_action_notif + // ); + let bios_notified1 = bios_notified.clone(); tokio::spawn(async move { let conn = zbus::Connection::system().await.unwrap(); - let proxy = supergfxctl::zbus_proxy::DaemonProxy::new(&conn) - .await - .unwrap(); - if let Ok(p) = proxy.receive_notify_gfx_status().await { - p.for_each(|e| { + let proxy = SuperProxy::new(&conn).await.unwrap(); + if let Ok(mut p) = proxy.receive_notify_action().await { + while let Some(e) = p.next().await { if let Ok(out) = e.args() { - if notifs_enabled1.load(Ordering::SeqCst) { - let status = out.status(); - if *status != GfxPower::Unknown { - // Required check because status cycles through active/unknown/suspended - if let Ok(ref mut lock) = last_notif.try_lock() { - notify!( - do_notification( - "dGPU status changed:", - &format!("{status:?}",) - ), - lock - ); - if let Ok(lock) = update_tray.try_lock() { - lock.send(AppToTray::DgpuStatus(*status)).ok(); + let action = out.action(); + do_gfx_action_notif("Gfx mode change requires", &format!("{action:?}",)) + .unwrap(); + } + } + bios_notified1.store(true, Ordering::SeqCst); + }; + }); + + let notifs_enabled1 = enabled_notifications; + let last_notif = last_notification; + let bios_notified1 = bios_notified; + tokio::spawn(async move { + let conn = zbus::Connection::system().await.unwrap(); + let proxy = SuperProxy::new(&conn).await.unwrap(); + if let Ok(mut p) = proxy.receive_notify_gfx_status().await { + while let Some(e) = p.next().await { + if let Ok(out) = e.args() { + let status = out.status(); + if *status != GfxPower::Unknown { + if let Ok(config) = notifs_enabled1.lock() { + if config.all_enabled && config.receive_notify_gfx_status { + // Required check because status cycles through active/unknown/suspended + if let Ok(ref mut lock) = last_notif.try_lock() { + notify!( + do_gpu_status_notif("dGPU status changed:", status), + lock + ); } } } + if let Ok(lock) = update_tray.try_lock() { + lock.send(AppToTray::DgpuStatus(*status)).ok(); + } } } - bios_notified1.store(true, Ordering::SeqCst); - future::ready(()) - }) - .await; + } + bios_notified1.store(true, Ordering::SeqCst); }; }); @@ -278,11 +358,6 @@ fn ac_power_notification(message: &str, on: &bool) -> Result Ok(base_notification(message, &data).show()?) } -/// Actual GpuMode unused as data is never correct until switched by reboot -fn mux_notification(message: &str, _: &GpuMode) -> Result { - Ok(base_notification(message, &"").show()?) -} - fn do_thermal_notif(message: &str, profile: &Profile) -> Result { let icon = match profile { Profile::Balanced => "asus_notif_yellow", @@ -293,3 +368,49 @@ fn do_thermal_notif(message: &str, profile: &Profile) -> Result Result { + // eww + let mut notif = base_notification(message, &<&str>::from(data).to_string()); + let icon = match data { + GfxPower::Active => "asus_notif_red", + GfxPower::Suspended => "asus_notif_blue", + GfxPower::Off => "asus_notif_green", + GfxPower::AsusDisabled => "asus_notif_white", + GfxPower::AsusMuxDiscreet => "asus_notif_red", + GfxPower::Unknown => "gpu-integrated", + }; + notif.icon(icon); + Ok(Notification::show(¬if)?) +} + +fn do_gfx_action_notif(message: &str, data: &T) -> Result<()> +where + T: Display, +{ + let mut notif = base_notification(message, data); + notif.action("gnome-session-quit", "Logout"); + notif.urgency(Urgency::Critical); + notif.timeout(3000); + notif.icon("dialog-warning"); + notif.hint(Hint::Transient(true)); + let handle = notif.show()?; + handle.wait_for_action(|id| { + if id == "gnome-session-quit" { + let mut cmd = Command::new("gnome-session-quit"); + cmd.spawn().ok(); + } else if id == "__closed" { + // TODO: cancel the switching + } + }); + Ok(()) +} + +/// Actual GpuMode unused as data is never correct until switched by reboot +fn do_mux_notification(message: &str, _: &GpuMode) -> Result { + let mut notif = base_notification(message, &""); + notif.urgency(Urgency::Critical); + notif.icon("system-reboot-symbolic"); + notif.hint(Hint::Transient(true)); + Ok(notif.show()?) +} diff --git a/rog-control-center/src/page_states.rs b/rog-control-center/src/page_states.rs index a9afd3bd..6fcfdb88 100644 --- a/rog-control-center/src/page_states.rs +++ b/rog-control-center/src/page_states.rs @@ -2,7 +2,7 @@ use std::{ collections::{BTreeMap, HashSet}, sync::{ atomic::{AtomicBool, Ordering}, - Arc, + Arc, Mutex, }, }; @@ -11,7 +11,7 @@ use rog_aura::{layouts::KeyLayout, usb::AuraPowerDev, AuraEffect, AuraModeNum}; use rog_platform::{platform::GpuMode, supported::SupportedFunctions}; use rog_profiles::{fan_curve_set::FanCurveSet, FanCurvePU, Profile}; -use crate::{error::Result, RogDbusClientBlocking}; +use crate::{error::Result, notify::EnabledNotifications, RogDbusClientBlocking}; #[derive(Clone, Debug)] pub struct BiosState { @@ -249,7 +249,7 @@ impl AnimeState { #[derive(Debug, Clone)] pub struct PageDataStates { pub keyboard_layout: KeyLayout, - pub notifs_enabled: Arc, + pub enabled_notifications: Arc>, pub was_notified: Arc, /// Because much of the app state here is the same as `RogBiosSupportedFunctions` /// we can re-use that structure. @@ -266,7 +266,7 @@ pub struct PageDataStates { impl PageDataStates { pub fn new( keyboard_layout: KeyLayout, - notifs_enabled: Arc, + enabled_notifications: Arc>, charge_notified: Arc, bios_notified: Arc, aura_notified: Arc, @@ -278,7 +278,7 @@ impl PageDataStates { ) -> Result { Ok(Self { keyboard_layout, - notifs_enabled, + enabled_notifications, was_notified: charge_notified, charge_limit: dbus.proxies().charge().charge_control_end_threshold()?, bios: BiosState::new(bios_notified, supported, dbus)?, @@ -335,7 +335,7 @@ impl Default for PageDataStates { fn default() -> Self { Self { keyboard_layout: KeyLayout::ga401_layout(), - notifs_enabled: Default::default(), + enabled_notifications: Default::default(), was_notified: Default::default(), bios: BiosState { was_notified: Default::default(), diff --git a/rog-control-center/src/pages/system_page.rs b/rog-control-center/src/pages/system_page.rs index b5941ad9..a3e4b4f8 100644 --- a/rog-control-center/src/pages/system_page.rs +++ b/rog-control-center/src/pages/system_page.rs @@ -25,25 +25,23 @@ impl<'a> RogApp<'a> { .min_col_width(rect.width() / 2.0) .show(ui, |ui| { /******************************************************/ - ui.vertical(|ui| { - ui.separator(); - app_settings(config, states, ui); - }); - ui.vertical(|ui| { ui.separator(); if supported.platform_profile.platform_profile { platform_profile(states, dbus, ui); } }); + ui.vertical(|ui| { + ui.separator(); + aura_power_group(supported, states, dbus, ui); + }); ui.end_row(); /******************************************************/ ui.vertical(|ui| { ui.separator(); - aura_power_group(supported, states, dbus, ui); + app_settings(config, states, ui); }); - ui.vertical(|ui| { ui.separator(); rog_bios_group(supported, states, dbus, ui); diff --git a/rog-control-center/src/tray.rs b/rog-control-center/src/tray.rs index af4f2d0b..69de70fe 100644 --- a/rog-control-center/src/tray.rs +++ b/rog-control-center/src/tray.rs @@ -2,17 +2,23 @@ //! commands over an MPSC channel. use std::{ + io::Write, sync::{ mpsc::{channel, Receiver}, Arc, Mutex, }, time::Duration, - io::Write, }; -use supergfxctl::pci_device::GfxPower; -use tray_item::TrayItem; -use crate::{SHOW_GUI, get_ipc_file}; +use gtk::{gio::Icon, prelude::*, MenuItem}; +use rog_platform::{platform::GpuMode, supported::SupportedFunctions}; + +use crate::{error::Result, get_ipc_file, SHOW_GUI}; +use libappindicator::{AppIndicator, AppIndicatorStatus}; +use supergfxctl::pci_device::{GfxMode, GfxPower}; + +const TRAY_APP_ICON: &str = "rog-control-center"; +const TRAY_LABEL: &str = "ROG Control Center"; pub enum AppToTray { DgpuStatus(GfxPower), @@ -23,26 +29,214 @@ pub enum TrayToApp { Quit, } -pub fn init_tray(recv_command: Receiver) -> Receiver { +pub struct RadioGroup(Vec); + +impl RadioGroup { + /// Add new radio menu item. `set_no_show_all()` is true until added to menu + /// to prevent teh callback from running + pub fn new(first_label: &str, cb: F) -> Self + where + F: Fn(>k::RadioMenuItem) + Send + 'static, + { + let item = gtk::RadioMenuItem::with_label(first_label); + item.set_active(false); + item.set_no_show_all(true); + item.connect_activate(move |this| { + if this.is_active() && !this.is_no_show_all() { + cb(this); + } + }); + Self(vec![item]) + } + + /// Add new radio menu item. `set_no_show_all()` is true until added to menu + /// to prevent teh callback from running + pub fn add(&mut self, label: &str, cb: F) + where + F: Fn(>k::RadioMenuItem) + Send + 'static, + { + debug_assert!(!self.0.is_empty()); + let group = self.0[0].group(); + + let item = gtk::RadioMenuItem::with_label_from_widget(&group[0], Some(label)); + item.set_active(false); + item.set_no_show_all(true); + item.connect_activate(move |this| { + if this.is_active() && !this.is_no_show_all() { + cb(this); + } + }); + self.0.push(item); + } +} + +pub struct ROGTray { + tray: AppIndicator, + menu: gtk::Menu, + items: Vec, +} + +impl ROGTray { + pub fn new() -> Result { + let mut rog_tray = Self { + tray: AppIndicator::new(TRAY_LABEL, TRAY_APP_ICON), + menu: gtk::Menu::new(), + items: Vec::new(), + }; + // rog_tray.set_icon(TRAY_APP_ICON); + + rog_tray.add_icon_menu_item("Open app", "asus_notif_red", move || { + get_ipc_file().unwrap().write_all(&[SHOW_GUI]).ok(); + }); + + rog_tray.add_inactive_label("----"); + + Ok(rog_tray) + } + + pub fn set_icon(&mut self, icon: &str) { + self.tray.set_icon(icon); + self.tray.set_status(AppIndicatorStatus::Active); + } + + /// Add a non-interactive label + fn add_inactive_label(&mut self, label: &str) { + let item = gtk::MenuItem::with_label(label); + item.set_sensitive(false); + self.menu.append(&item); + self.menu.show_all(); + self.tray.set_menu(&mut self.menu); + } + + fn add_radio_sub_menu(&mut self, header_label: &str, active_label: &str, sub_menu: RadioGroup) { + let header_item = gtk::MenuItem::with_label(header_label); + header_item.show_all(); + self.menu.add(&header_item); + + let menu = gtk::Menu::new(); + for item in sub_menu.0.iter() { + item.set_active(item.label().unwrap() == active_label); + item.set_no_show_all(false); + item.show_all(); + menu.add(item); + } + menu.show_all(); + header_item.set_submenu(Some(&menu)); + } + + fn add_menu_item(&mut self, label: &str, cb: F) + where + F: Fn() + Send + 'static, + { + let item = gtk::MenuItem::with_label(label); + self.items.push(item); + let item = self.items.last().unwrap(); + item.connect_activate(move |_| { + cb(); + }); + self.menu.append(item); + self.menu.show_all(); + self.tray.set_menu(&mut self.menu); + } + + /// Add a menu item with an icon to the right + fn add_icon_menu_item(&mut self, label: &str, icon: &str, cb: F) + where + F: Fn() + Send + 'static, + { + let g_box = gtk::Box::new(gtk::Orientation::Horizontal, 6); + let icon = gtk::Image::from_gicon(&Icon::for_string(icon).unwrap(), gtk::IconSize::Menu); + let label = gtk::Label::new(Some(label)); + let menu_item = gtk::MenuItem::new(); + g_box.add(&icon); + g_box.add(&label); + + menu_item.add(&g_box); + menu_item.show_all(); + + self.items.push(menu_item); + let item = self.items.last().unwrap(); + item.connect_activate(move |_| { + cb(); + }); + self.menu.append(item); + self.menu.show_all(); + self.tray.set_menu(&mut self.menu); + } + + fn set_status(&mut self, status: AppIndicatorStatus) { + self.tray.set_status(status) + } +} + +fn init_gpu_menu(supported: &SupportedFunctions, tray: &mut ROGTray) { + let conn = zbus::blocking::Connection::system().unwrap(); + let gfx_dbus = supergfxctl::zbus_proxy::DaemonProxyBlocking::new(&conn).unwrap(); + + let mode = gfx_dbus.mode().unwrap(); + let mut gpu_menu = RadioGroup::new("Integrated", move |_| { + let mode = gfx_dbus.mode().unwrap(); + if mode != GfxMode::Integrated { + gfx_dbus.set_mode(&GfxMode::Integrated).unwrap(); + } + }); + let gfx_dbus = supergfxctl::zbus_proxy::DaemonProxyBlocking::new(&conn).unwrap(); + gpu_menu.add("Hybrid", move |_| { + let mode = gfx_dbus.mode().unwrap(); + if mode != GfxMode::Hybrid { + gfx_dbus.set_mode(&GfxMode::Hybrid).unwrap(); + } + }); + if supported.rog_bios_ctrl.gpu_mux { + let gfx_dbus = rog_dbus::zbus_platform::RogBiosProxyBlocking::new(&conn).unwrap(); + gpu_menu.add("Ultimate (Reboot required)", move |_| { + let mode = gfx_dbus.gpu_mux_mode().unwrap(); + if mode != GpuMode::Discrete { + gfx_dbus.set_gpu_mux_mode(GpuMode::Discrete).unwrap(); + } + }); + } + if supported.rog_bios_ctrl.egpu_enable { + let gfx_dbus = supergfxctl::zbus_proxy::DaemonProxyBlocking::new(&conn).unwrap(); + gpu_menu.add("eGPU", move |_| { + let mode = gfx_dbus.mode().unwrap(); + if mode != GfxMode::Egpu { + gfx_dbus.set_mode(&GfxMode::Egpu).unwrap(); + } + }); + } + + let active = match mode { + GfxMode::AsusMuxDiscreet => "Discreet".to_string(), + _ => mode.to_string(), + }; + tray.add_radio_sub_menu("GPU Mode", active.as_str(), gpu_menu); +} + +pub fn init_tray( + supported: SupportedFunctions, + recv_command: Receiver, +) -> Receiver { let (send, recv) = channel(); - let send = Arc::new(Mutex::new(send)); + let _send = Arc::new(Mutex::new(send)); std::thread::spawn(move || { gtk::init().unwrap(); // Make this the main thread for gtk - let mut tray = TrayItem::new("ROG Control Center", "rog-control-center").unwrap(); - - tray.add_menu_item("Open app", move || { - get_ipc_file().unwrap().write_all(&[SHOW_GUI]).ok(); - }) - .ok(); - - let s1 = send.clone(); - tray.add_menu_item("Quit", move || { - let lock = s1.lock().unwrap(); - lock.send(TrayToApp::Quit).ok(); - }) - .ok(); + let mut tray = ROGTray::new().unwrap(); + + init_gpu_menu(&supported, &mut tray); + // let s1 = send.clone(); + // tray.add_menu_item("Quit", move || { + // let lock = s1.lock().unwrap(); + // lock.send(TrayToApp::Quit).ok(); + // }) + // .ok(); + + //*********************************** + // finally do + tray.tray.set_menu(&mut tray.menu); + //*********************************** loop { if let Ok(command) = recv_command.try_recv() { @@ -55,8 +249,7 @@ pub fn init_tray(recv_command: Receiver) -> Receiver { GfxPower::AsusDisabled => tray.set_icon("asus_notif_white"), GfxPower::AsusMuxDiscreet => tray.set_icon("asus_notif_red"), GfxPower::Unknown => tray.set_icon("gpu-integrated"), - } - .ok(); + }; } } } diff --git a/rog-control-center/src/widgets/app_settings.rs b/rog-control-center/src/widgets/app_settings.rs index 9a1a2fbc..9e870ab1 100644 --- a/rog-control-center/src/widgets/app_settings.rs +++ b/rog-control-center/src/widgets/app_settings.rs @@ -1,5 +1,3 @@ -use std::sync::atomic::Ordering; - use egui::Ui; use crate::{config::Config, page_states::PageDataStates}; @@ -8,6 +6,12 @@ pub fn app_settings(config: &mut Config, states: &mut PageDataStates, ui: &mut U ui.heading("ROG GUI Settings"); // ui.label("Options are incomplete. Awake + Boot should work"); + let mut enabled_notifications = if let Ok(lock) = states.enabled_notifications.lock() { + lock.clone() + } else { + Default::default() + }; + if ui .checkbox(&mut config.run_in_background, "Run in Background") .clicked() @@ -15,17 +19,70 @@ pub fn app_settings(config: &mut Config, states: &mut PageDataStates, ui: &mut U .checkbox(&mut config.startup_in_background, "Startup Hidden") .clicked() || ui - .checkbox(&mut config.enable_notifications, "Enable Notifications") + .checkbox( + &mut enabled_notifications.all_enabled, + "Enable Notifications", + ) + .clicked() + || ui + .checkbox( + &mut enabled_notifications.receive_notify_gfx_status, + "Enable dGPU status notification", + ) + .clicked() + || ui + .checkbox( + &mut enabled_notifications.receive_notify_dgpu_disable, + "Enable dGPU disablement notification", + ) + .clicked() + || ui + .checkbox( + &mut enabled_notifications.receive_notify_egpu_enable, + "Enable eGPU enablement notification", + ) + .clicked() + || ui + .checkbox( + &mut enabled_notifications.receive_notify_mains_online, + "Enable mains (AC) power notification", + ) + .clicked() + || ui + .checkbox( + &mut enabled_notifications.receive_notify_charge_control_end_threshold, + "Enable charge threshold notification", + ) + .clicked() + || ui + .checkbox( + &mut enabled_notifications.receive_notify_profile, + "Enable profile change notification", + ) + .clicked() + || ui + .checkbox( + &mut enabled_notifications.receive_notify_panel_od, + "Enable panel overdrive notification", + ) + .clicked() + || ui + .checkbox( + &mut enabled_notifications.receive_notify_post_boot_sound, + "Enable BIOS post sound notification", + ) .clicked() { - states - .notifs_enabled - .store(config.enable_notifications, Ordering::SeqCst); - config - .save() - .map_err(|err| { - states.error = Some(err.to_string()); - }) - .ok(); + if let Ok(mut lock) = states.enabled_notifications.lock() { + // Replace inner content before save + *lock = enabled_notifications; + + config + .save(&lock) + .map_err(|err| { + states.error = Some(err.to_string()); + }) + .ok(); + } } } diff --git a/rog-platform/src/lib.rs b/rog-platform/src/lib.rs index 1a120207..bb86ab3b 100644 --- a/rog-platform/src/lib.rs +++ b/rog-platform/src/lib.rs @@ -78,7 +78,7 @@ pub fn write_attr_u8_array(device: &mut Device, attr: &str, values: &[u8]) -> Re let tmp: String = values.iter().map(|v| format!("{} ", v)).collect(); let tmp = tmp.trim(); device - .set_attribute_value(attr, &tmp) + .set_attribute_value(attr, tmp) .map_err(|e| PlatformError::IoPath(attr.into(), e)) }