From 903b978e866047b88b09dbd92626757a800745db Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Thu, 18 Apr 2024 13:55:02 +1200 Subject: [PATCH] Add missing files --- rog-control-center/src/notify.rs | 352 ++++++++++++++++++ .../translations/en/rog-control-center.po | 2 +- 2 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 rog-control-center/src/notify.rs diff --git a/rog-control-center/src/notify.rs b/rog-control-center/src/notify.rs new file mode 100644 index 00000000..2a06e87b --- /dev/null +++ b/rog-control-center/src/notify.rs @@ -0,0 +1,352 @@ +//! `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. +//! +//! This module very much functions like a stand-alone app on its own thread. + +use std::fmt::Display; +use std::process::Command; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use log::{error, info, warn}; +use notify_rust::{Hint, Notification, NotificationHandle, Urgency}; +use rog_dbus::zbus_platform::PlatformProxy; +use rog_platform::platform::GpuMode; +use rog_platform::power::AsusPower; +use serde::{Deserialize, Serialize}; +use supergfxctl::actions::UserActionRequired as GfxUserAction; +use supergfxctl::pci_device::{GfxMode, GfxPower}; +use supergfxctl::zbus_proxy::DaemonProxy as SuperProxy; +use tokio::time::sleep; +use zbus::export::futures_util::StreamExt; + +use crate::config::Config; +use crate::error::Result; + +const NOTIF_HEADER: &str = "ROG Control"; + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(default)] +pub struct EnabledNotifications { + pub enabled: bool, + pub receive_notify_gfx: bool, + pub receive_notify_gfx_status: bool, +} + +impl Default for EnabledNotifications { + fn default() -> Self { + Self { + enabled: true, + receive_notify_gfx: true, + receive_notify_gfx_status: true, + } + } +} + +pub fn start_notifications(config: Arc>) -> Result<()> { + // Setup the AC/BAT commands that will run on power status change + let config_copy = config.clone(); + tokio::task::spawn_blocking(move || { + let power = AsusPower::new() + .map_err(|e| { + error!("AsusPower: {e}"); + e + }) + .unwrap(); + + let mut last_state = power.get_online().unwrap_or_default(); + loop { + if let Ok(p) = power.get_online() { + let mut ac = String::new(); + let mut bat = String::new(); + if let Ok(config) = config_copy.lock() { + ac = config.ac_command.clone(); + bat = config.bat_command.clone(); + } + + if p == 0 && p != last_state { + let prog: Vec<&str> = bat.split_whitespace().collect(); + if prog.len() > 1 { + let mut cmd = Command::new(prog[0]); + + for arg in prog.iter().skip(1) { + cmd.arg(*arg); + } + cmd.spawn() + .map_err(|e| error!("AC command error: {e:?}")) + .ok(); + } + } else if p != last_state { + let prog: Vec<&str> = ac.split_whitespace().collect(); + if prog.len() > 1 { + let mut cmd = Command::new(prog[0]); + + for arg in prog.iter().skip(1) { + cmd.arg(*arg); + } + cmd.spawn() + .map_err(|e| error!("AC command error: {e:?}")) + .ok(); + } + } + last_state = p; + } + std::thread::sleep(Duration::from_millis(500)); + } + }); + + // GPU MUX Mode notif + 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 + }) + .unwrap(); + let proxy = PlatformProxy::new(&conn) + .await + .map_err(|e| { + error!("zbus signal: receive_notify_gpu_mux_mode: {e}"); + e + }) + .unwrap(); + + 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(); + } + } + }); + + 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() { + let enabled_notifications_copy = config.clone(); + // Plain old thread is perfectly fine since most of this is potentially blocking + tokio::spawn(async move { + let mut last_status = GfxPower::Unknown; + loop { + 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).ok(); + } + last_status = status; + } + sleep(Duration::from_millis(500)).await; + } + }); + found_dgpu = true; + break; + } + } + if !found_dgpu { + warn!("Did not find a dGPU on this system, dGPU status won't be avilable"); + } + + // GPU Mode change/action notif + tokio::spawn(async move { + let conn = zbus::Connection::system() + .await + .map_err(|e| { + error!("zbus signal: receive_notify_action: {e}"); + e + }) + .unwrap(); + let proxy = SuperProxy::builder(&conn) + .build() + .await + .map_err(|e| { + error!("zbus signal: receive_notify_action: {e}"); + e + }) + .unwrap(); + + if proxy.mode().await.is_err() { + info!("supergfxd not running or not responding"); + return; + } + + if let Ok(mut p) = proxy.receive_notify_action().await { + info!("Started zbus signal thread: receive_notify_action"); + while let Some(e) = p.next().await { + if let Ok(out) = e.args() { + let action = out.action(); + let mode = convert_gfx_mode(proxy.mode().await.unwrap_or_default()); + match action { + supergfxctl::actions::UserActionRequired::Reboot => { + do_mux_notification("Graphics mode change requires reboot", &mode) + } + _ => do_gfx_action_notif(<&str>::from(action), *action, mode), + } + .map_err(|e| { + error!("zbus signal: do_gfx_action_notif: {e}"); + e + }) + .ok(); + } + } + }; + }); + + Ok(()) +} + +fn convert_gfx_mode(gfx: GfxMode) -> GpuMode { + match gfx { + GfxMode::Hybrid => GpuMode::Optimus, + GfxMode::Integrated => GpuMode::Integrated, + GfxMode::NvidiaNoModeset => GpuMode::Optimus, + GfxMode::Vfio => GpuMode::Vfio, + GfxMode::AsusEgpu => GpuMode::Egpu, + GfxMode::AsusMuxDgpu => GpuMode::Ultimate, + GfxMode::None => GpuMode::Error, + } +} + +fn base_notification(message: &str, data: &T) -> Notification +where + T: Display, +{ + let mut notif = Notification::new(); + + notif + .summary(NOTIF_HEADER) + .body(&format!("{message} {data}")) + .timeout(-1) + //.hint(Hint::Resident(true)) + .hint(Hint::Category("device".into())); + + notif +} + +fn do_gpu_status_notif(message: &str, data: &GfxPower) -> Result { + // eww + let mut notif = base_notification(message, &<&str>::from(data).to_owned()); + let icon = match data { + GfxPower::Suspended => "asus_notif_blue", + GfxPower::Off => "asus_notif_green", + GfxPower::AsusDisabled => "asus_notif_white", + GfxPower::AsusMuxDiscreet | GfxPower::Active => "asus_notif_red", + GfxPower::Unknown => "gpu-integrated", + }; + notif.icon(icon); + Ok(Notification::show(¬if)?) +} + +fn do_gfx_action_notif(message: &str, action: GfxUserAction, mode: GpuMode) -> Result<()> { + if matches!(action, GfxUserAction::Reboot) { + do_mux_notification("Graphics mode change requires reboot", &mode).ok(); + return Ok(()); + } + + let mut notif = Notification::new(); + notif + .summary(NOTIF_HEADER) + .body(&format!("Changing to {mode}. {message}")) + .timeout(2000) + //.hint(Hint::Resident(true)) + .hint(Hint::Category("device".into())) + .urgency(Urgency::Critical) + .timeout(-1) + .icon("dialog-warning") + .hint(Hint::Transient(true)); + + if matches!(action, GfxUserAction::Logout) { + notif.action("gfx-mode-session-action", "Logout"); + let handle = notif.show()?; + if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") { + if desktop.to_lowercase() == "gnome" { + handle.wait_for_action(|id| { + if id == "gfx-mode-session-action" { + let mut cmd = Command::new("gnome-session-quit"); + cmd.spawn().ok(); + } else if id == "__closed" { + // TODO: cancel the switching + } + }); + } else if desktop.to_lowercase() == "kde" { + handle.wait_for_action(|id| { + if id == "gfx-mode-session-action" { + let mut cmd = Command::new("qdbus"); + cmd.args(["org.kde.ksmserver", "/KSMServer", "logout", "1", "0", "0"]); + cmd.spawn().ok(); + } else if id == "__closed" { + // TODO: cancel the switching + } + }); + } else { + // todo: handle alternatives + } + } + } else { + notif.show()?; + } + Ok(()) +} + +/// Actual `GpuMode` unused as data is never correct until switched by reboot +fn do_mux_notification(message: &str, m: &GpuMode) -> Result<()> { + let mut notif = base_notification(message, &m.to_string()); + notif + .action("gfx-mode-session-action", "Reboot") + .urgency(Urgency::Critical) + .icon("system-reboot-symbolic") + .hint(Hint::Transient(true)); + let handle = notif.show()?; + + std::thread::spawn(|| { + if let Ok(desktop) = std::env::var("XDG_CURRENT_DESKTOP") { + if desktop.to_lowercase() == "gnome" { + handle.wait_for_action(|id| { + if id == "gfx-mode-session-action" { + let mut cmd = Command::new("gnome-session-quit"); + cmd.arg("--reboot"); + cmd.spawn().ok(); + } else if id == "__closed" { + // TODO: cancel the switching + } + }); + } else if desktop.to_lowercase() == "kde" { + handle.wait_for_action(|id| { + if id == "gfx-mode-session-action" { + let mut cmd = Command::new("qdbus"); + cmd.args(["org.kde.ksmserver", "/KSMServer", "logout", "1", "1", "0"]); + cmd.spawn().ok(); + } else if id == "__closed" { + // TODO: cancel the switching + } + }); + } + } + }); + Ok(()) +} diff --git a/rog-control-center/translations/en/rog-control-center.po b/rog-control-center/translations/en/rog-control-center.po index 70f69168..945c552c 100644 --- a/rog-control-center/translations/en/rog-control-center.po +++ b/rog-control-center/translations/en/rog-control-center.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-04-18 01:47+0000\n" +"POT-Creation-Date: 2024-04-18 01:48+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n"