mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Add rog-control-center to the workspace
This commit is contained in:
127
rog-control-center/src/app.rs
Normal file
127
rog-control-center/src/app.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use std::{
|
||||
io::Write,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use egui::{Button, RichText};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use rog_supported::SupportedFunctions;
|
||||
|
||||
use crate::{config::Config, get_ipc_file, page_states::PageDataStates, Page, SHOWING_GUI};
|
||||
|
||||
pub struct RogApp<'a> {
|
||||
pub page: Page,
|
||||
pub states: PageDataStates,
|
||||
pub supported: SupportedFunctions,
|
||||
/// Should the app begin showing the GUI
|
||||
pub begin_show_gui: Arc<AtomicBool>,
|
||||
/// Is the app GUI closed (and running in bg)
|
||||
pub running_in_bg: bool,
|
||||
// TODO: can probably just open and read whenever
|
||||
pub config: Config,
|
||||
pub asus_dbus: RogDbusClientBlocking<'a>,
|
||||
}
|
||||
|
||||
impl<'a> RogApp<'a> {
|
||||
/// Called once before the first frame.
|
||||
pub fn new(
|
||||
start_closed: bool,
|
||||
config: Config,
|
||||
show_gui: Arc<AtomicBool>,
|
||||
states: PageDataStates,
|
||||
_cc: &eframe::CreationContext<'_>,
|
||||
) -> Self {
|
||||
let (dbus, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let supported = dbus.proxies().supported().supported_functions().unwrap();
|
||||
|
||||
Self {
|
||||
supported,
|
||||
states,
|
||||
page: Page::System,
|
||||
begin_show_gui: show_gui,
|
||||
running_in_bg: start_closed,
|
||||
config,
|
||||
asus_dbus: dbus,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> eframe::App for RogApp<'a> {
|
||||
fn on_exit_event(&mut self) -> bool {
|
||||
if self.config.run_in_background {
|
||||
self.running_in_bg = true;
|
||||
get_ipc_file().unwrap().write_all(&[0]).unwrap();
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
let Self {
|
||||
begin_show_gui: should_show_gui,
|
||||
supported,
|
||||
asus_dbus: dbus,
|
||||
states,
|
||||
..
|
||||
} = self;
|
||||
if states.refresh_if_notfied(supported, dbus) {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
let page = self.page;
|
||||
|
||||
if should_show_gui.load(Ordering::SeqCst) {
|
||||
let mut ipc_file = get_ipc_file().unwrap();
|
||||
ipc_file.write_all(&[SHOWING_GUI]).unwrap();
|
||||
should_show_gui.store(false, Ordering::SeqCst);
|
||||
frame.set_visible(true);
|
||||
self.running_in_bg = false;
|
||||
}
|
||||
if self.running_in_bg {
|
||||
// Request to draw nothing at all
|
||||
ctx.request_repaint_after(Duration::from_millis(500));
|
||||
frame.set_visible(false);
|
||||
return;
|
||||
}
|
||||
// Do all GUI display after this point
|
||||
|
||||
self.top_bar(ctx, frame);
|
||||
self.side_panel(ctx);
|
||||
|
||||
if let Some(err) = self.states.error.clone() {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading(RichText::new("Error!").size(28.0));
|
||||
|
||||
ui.centered_and_justified(|ui| {
|
||||
ui.label(RichText::new(format!("The error was: {:?}", err)).size(22.0));
|
||||
});
|
||||
});
|
||||
egui::TopBottomPanel::bottom("error_bar")
|
||||
.default_height(26.0)
|
||||
.show(ctx, |ui| {
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||
if ui
|
||||
.add(Button::new(RichText::new("Okay").size(20.0)))
|
||||
.clicked()
|
||||
{
|
||||
self.states.error = None;
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if page == Page::System {
|
||||
self.system_page(ctx);
|
||||
} else if page == Page::AuraEffects {
|
||||
self.aura_page(ctx);
|
||||
} else if page == Page::AnimeMatrix {
|
||||
self.anime_page(ctx);
|
||||
} else if page == Page::FanCurves {
|
||||
self.fan_curve_page(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
92
rog-control-center/src/config.rs
Normal file
92
rog-control-center/src/config.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use std::{
|
||||
fs::{create_dir, OpenOptions},
|
||||
io::{Read, Write},
|
||||
};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
//use log::{error, info, warn};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
const CFG_DIR: &str = "rog";
|
||||
const CFG_FILE_NAME: &str = "app-template.cfg";
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub run_in_background: bool,
|
||||
pub startup_in_background: bool,
|
||||
pub enable_notifications: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
run_in_background: true,
|
||||
startup_in_background: false,
|
||||
enable_notifications: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Result<Config, Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(Error::XdgVars);
|
||||
};
|
||||
|
||||
path.push(CFG_DIR);
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
|
||||
path.push(CFG_FILE_NAME);
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(&path)?;
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
if let Ok(read_len) = file.read_to_string(&mut buf) {
|
||||
if read_len == 0 {
|
||||
let default = Config::default();
|
||||
let t = toml::to_string_pretty(&default).unwrap();
|
||||
file.write_all(t.as_bytes())?;
|
||||
return Ok(default);
|
||||
} else if let Ok(data) = toml::from_str::<Config>(&buf) {
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
Err(Error::ConfigLoadFail)
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Result<(), Error> {
|
||||
let mut path = if let Some(dir) = dirs::config_dir() {
|
||||
dir
|
||||
} else {
|
||||
return Err(Error::XdgVars);
|
||||
};
|
||||
|
||||
path.push(CFG_DIR);
|
||||
if !path.exists() {
|
||||
create_dir(path.clone())?;
|
||||
}
|
||||
|
||||
path.push(CFG_FILE_NAME);
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(&path)?;
|
||||
|
||||
let t = toml::to_string_pretty(&self).unwrap();
|
||||
file.write_all(t.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
43
rog-control-center/src/error.rs
Normal file
43
rog-control-center/src/error.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Io(std::io::Error),
|
||||
Nix(nix::Error),
|
||||
ConfigLoadFail,
|
||||
ConfigLockFail,
|
||||
XdgVars,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
// This trait requires `fmt` with this exact signature.
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::Io(err) => write!(f, "Failed to open: {}", err),
|
||||
Error::Nix(err) => write!(f, "Error: {}", err),
|
||||
Error::ConfigLoadFail => write!(f, "Failed to load user config"),
|
||||
Error::ConfigLockFail => write!(f, "Failed to lock user config"),
|
||||
Error::XdgVars => write!(f, "XDG environment vars appear unset"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for zbus::fdo::Error {
|
||||
fn from(err: Error) -> Self {
|
||||
zbus::fdo::Error::Failed(format!("Anime zbus error: {}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nix::Error> for Error {
|
||||
fn from(err: nix::Error) -> Self {
|
||||
Error::Nix(err)
|
||||
}
|
||||
}
|
||||
77
rog-control-center/src/lib.rs
Normal file
77
rog-control-center/src/lib.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
pub mod app;
|
||||
use std::{
|
||||
fs::{remove_dir_all, File, OpenOptions},
|
||||
io::{Read, Write},
|
||||
process::exit,
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub use app::RogApp;
|
||||
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod notify;
|
||||
pub mod page_states;
|
||||
pub mod widgets;
|
||||
|
||||
use nix::{sys::stat, unistd};
|
||||
use tempfile::TempDir;
|
||||
//use log::{error, info, warn};
|
||||
|
||||
pub const SHOWING_GUI: u8 = 1;
|
||||
pub const SHOW_GUI: u8 = 2;
|
||||
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub enum Page {
|
||||
System,
|
||||
AuraEffects,
|
||||
AnimeMatrix,
|
||||
FanCurves,
|
||||
}
|
||||
|
||||
/// Either exit the process, or return with a refreshed tmp-dir
|
||||
pub fn on_tmp_dir_exists() -> Result<TempDir, std::io::Error> {
|
||||
let mut buf = [0u8; 4];
|
||||
let path = std::env::temp_dir().join("rog-gui");
|
||||
|
||||
let mut ipc_file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(false)
|
||||
.open(path.join("ipc.pipe"))?;
|
||||
|
||||
// If the app is running this ends up stacked on top of SHOWING_GUI
|
||||
ipc_file.write_all(&[SHOW_GUI])?;
|
||||
// tiny sleep to give the app a chance to respond
|
||||
sleep(Duration::from_millis(10));
|
||||
ipc_file.read(&mut buf).ok();
|
||||
|
||||
// First entry is the actual state
|
||||
if buf[0] == SHOWING_GUI {
|
||||
ipc_file.write_all(&[SHOWING_GUI])?; // Store state again as we drained the fifo
|
||||
exit(0);
|
||||
} else if buf[0] == SHOW_GUI {
|
||||
remove_dir_all(&path)?;
|
||||
return tempfile::Builder::new()
|
||||
.prefix("rog-gui")
|
||||
.rand_bytes(0)
|
||||
.tempdir();
|
||||
}
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
pub fn get_ipc_file() -> Result<File, crate::error::Error> {
|
||||
let tmp_dir = std::env::temp_dir().join("rog-gui");
|
||||
let fifo_path = tmp_dir.join("ipc.pipe");
|
||||
if let Err(e) = unistd::mkfifo(&fifo_path, stat::Mode::S_IRWXU) {
|
||||
if !matches!(e, nix::Error::Sys(nix::errno::Errno::EEXIST)) {
|
||||
return Err(e)?;
|
||||
}
|
||||
}
|
||||
Ok(OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&fifo_path)?)
|
||||
}
|
||||
94
rog-control-center/src/main.rs
Normal file
94
rog-control-center/src/main.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use rog_control_center::{
|
||||
config::Config, get_ipc_file, notify::start_notifications, on_tmp_dir_exists,
|
||||
page_states::PageDataStates, RogApp, SHOW_GUI,
|
||||
};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use std::{
|
||||
io::Read,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread::spawn,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Startup
|
||||
let mut config = Config::load()?;
|
||||
let start_closed = config.startup_in_background;
|
||||
|
||||
if config.startup_in_background {
|
||||
config.run_in_background = true;
|
||||
config.save()?;
|
||||
}
|
||||
|
||||
let (dbus, _) = RogDbusClientBlocking::new().unwrap();
|
||||
let supported = dbus.proxies().supported().supported_functions().unwrap();
|
||||
// Cheap method to alert to notifications rather than spinning a thread for each
|
||||
// This is quite different when done in a retained mode app
|
||||
let charge_notified = Arc::new(AtomicBool::new(false));
|
||||
let bios_notified = Arc::new(AtomicBool::new(false));
|
||||
let aura_notified = Arc::new(AtomicBool::new(false));
|
||||
let anime_notified = Arc::new(AtomicBool::new(false));
|
||||
let profiles_notified = Arc::new(AtomicBool::new(false));
|
||||
let fans_notified = Arc::new(AtomicBool::new(false));
|
||||
// TODO: change this to an error instead of the nested unwraps, then use to
|
||||
// display a bare box app with error message.
|
||||
let states = PageDataStates::new(
|
||||
charge_notified.clone(),
|
||||
bios_notified.clone(),
|
||||
aura_notified.clone(),
|
||||
anime_notified.clone(),
|
||||
profiles_notified.clone(),
|
||||
fans_notified.clone(),
|
||||
&supported,
|
||||
&dbus,
|
||||
);
|
||||
|
||||
if config.enable_notifications {
|
||||
start_notifications(
|
||||
charge_notified,
|
||||
bios_notified,
|
||||
aura_notified,
|
||||
anime_notified,
|
||||
profiles_notified,
|
||||
fans_notified,
|
||||
)?;
|
||||
}
|
||||
|
||||
// tmp-dir must live to the end of program life
|
||||
let _tmp_dir = match tempfile::Builder::new()
|
||||
.prefix("rog-gui")
|
||||
.rand_bytes(0)
|
||||
.tempdir()
|
||||
{
|
||||
Ok(tmp) => tmp,
|
||||
Err(_) => on_tmp_dir_exists().unwrap(),
|
||||
};
|
||||
|
||||
let native_options = eframe::NativeOptions {
|
||||
decorated: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let should_show_gui = Arc::new(AtomicBool::new(!start_closed));
|
||||
let should = should_show_gui.clone();
|
||||
spawn(move || {
|
||||
// Loop is blocked here until a single byte is read
|
||||
loop {
|
||||
let mut buf = [0u8; 4];
|
||||
if get_ipc_file().unwrap().read(&mut buf).is_ok() && buf[0] == SHOW_GUI {
|
||||
should_show_gui.store(true, Ordering::SeqCst);
|
||||
// Give the starting app a change to read or we'll race it
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
eframe::run_native(
|
||||
"ROG Control Center",
|
||||
native_options,
|
||||
Box::new(move |cc| Box::new(RogApp::new(start_closed, config, should, states, cc))),
|
||||
);
|
||||
}
|
||||
222
rog-control-center/src/notify.rs
Normal file
222
rog-control-center/src/notify.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
//TODO: a lot of app state refresh depends on this so there needs
|
||||
// to be an extra AtomicBool for checking if notifications are enabled
|
||||
|
||||
use notify_rust::{Hint, Notification, NotificationHandle};
|
||||
use rog_aura::AuraEffect;
|
||||
use rog_dbus::{
|
||||
zbus_anime::AnimeProxy, zbus_charge::ChargeProxy, zbus_led::LedProxy,
|
||||
zbus_profile::ProfileProxy, zbus_rogbios::RogBiosProxy,
|
||||
};
|
||||
use rog_profiles::Profile;
|
||||
use smol::{future, Executor};
|
||||
use std::{
|
||||
error::Error,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread::spawn,
|
||||
};
|
||||
use zbus::export::futures_util::StreamExt;
|
||||
|
||||
const NOTIF_HEADER: &str = "ROG Control";
|
||||
|
||||
macro_rules! notify {
|
||||
($notifier:ident, $last_notif:ident, $data:expr) => {
|
||||
if let Some(notif) = $last_notif.take() {
|
||||
notif.close();
|
||||
}
|
||||
if let Ok(x) = $notifier($data) {
|
||||
$last_notif.replace(x);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! base_notification {
|
||||
($body:expr) => {
|
||||
Notification::new()
|
||||
.summary(NOTIF_HEADER)
|
||||
.body($body)
|
||||
.timeout(2000)
|
||||
.show()
|
||||
};
|
||||
}
|
||||
|
||||
type SharedHandle = Arc<Mutex<Option<NotificationHandle>>>;
|
||||
|
||||
pub fn start_notifications(
|
||||
charge_notified: Arc<AtomicBool>,
|
||||
bios_notified: Arc<AtomicBool>,
|
||||
aura_notified: Arc<AtomicBool>,
|
||||
anime_notified: Arc<AtomicBool>,
|
||||
profiles_notified: Arc<AtomicBool>,
|
||||
_fans_notified: Arc<AtomicBool>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let last_notification: SharedHandle = Arc::new(Mutex::new(None));
|
||||
|
||||
let executor = Executor::new();
|
||||
// BIOS notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = RogBiosProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_post_boot_sound().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_post_sound_notif, lock, &out.sound());
|
||||
}
|
||||
bios_notified.store(true, Ordering::SeqCst);
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
// Charge notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = ChargeProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_charge().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_charge_notif, lock, &out.limit);
|
||||
}
|
||||
charge_notified.store(true, Ordering::SeqCst);
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
// Profile notif
|
||||
let x = last_notification.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = ProfileProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_profile().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_thermal_notif, lock, &out.profile);
|
||||
}
|
||||
profiles_notified.store(true, Ordering::SeqCst);
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
// LED notif
|
||||
let x = last_notification.clone();
|
||||
let a = aura_notified.clone();
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = LedProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_led().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(out) = e.args() {
|
||||
if let Ok(ref mut lock) = x.try_lock() {
|
||||
notify!(do_led_notif, lock, &out.data);
|
||||
}
|
||||
a.store(true, Ordering::SeqCst);
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = LedProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_notify_power_states().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(_out) = e.args() {
|
||||
// if let Ok(ref mut lock) = last_notification.try_lock() {
|
||||
// notify!(do_led_notif, lock, &out.data);
|
||||
// }
|
||||
aura_notified.store(true, Ordering::SeqCst);
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
executor
|
||||
.spawn(async move {
|
||||
let conn = zbus::Connection::system().await.unwrap();
|
||||
let proxy = AnimeProxy::new(&conn).await.unwrap();
|
||||
if let Ok(p) = proxy.receive_power_states().await {
|
||||
p.for_each(|e| {
|
||||
if let Ok(_out) = e.args() {
|
||||
// if let Ok(ref mut lock) = last_notification.try_lock() {
|
||||
// notify!(do_led_notif, lock, &out.data);
|
||||
// }
|
||||
anime_notified.store(true, Ordering::SeqCst);
|
||||
}
|
||||
future::ready(())
|
||||
})
|
||||
.await;
|
||||
};
|
||||
})
|
||||
.detach();
|
||||
|
||||
spawn(move || loop {
|
||||
smol::block_on(executor.tick());
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn do_thermal_notif(profile: &Profile) -> Result<NotificationHandle, Box<dyn Error>> {
|
||||
let icon = match profile {
|
||||
Profile::Balanced => "asus_notif_yellow",
|
||||
Profile::Performance => "asus_notif_red",
|
||||
Profile::Quiet => "asus_notif_green",
|
||||
};
|
||||
let profile: &str = (*profile).into();
|
||||
let x = Notification::new()
|
||||
.summary("ASUS ROG")
|
||||
.body(&format!(
|
||||
"Thermal profile changed to {}",
|
||||
profile.to_uppercase(),
|
||||
))
|
||||
.hint(Hint::Resident(true))
|
||||
.timeout(2000)
|
||||
.hint(Hint::Category("device".into()))
|
||||
//.hint(Hint::Transient(true))
|
||||
.icon(icon)
|
||||
.show()?;
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
fn do_led_notif(ledmode: &AuraEffect) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!(
|
||||
"Keyboard LED mode changed to {}",
|
||||
ledmode.mode_name()
|
||||
))
|
||||
}
|
||||
|
||||
fn do_charge_notif(limit: &u8) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!("Battery charge limit changed to {}", limit))
|
||||
}
|
||||
|
||||
fn do_post_sound_notif(on: &bool) -> Result<NotificationHandle, notify_rust::error::Error> {
|
||||
base_notification!(&format!("BIOS Post sound {}", on))
|
||||
}
|
||||
294
rog-control-center/src/page_states.rs
Normal file
294
rog-control-center/src/page_states.rs
Normal file
@@ -0,0 +1,294 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use egui::Vec2;
|
||||
use rog_aura::{usb::AuraPowerDev, AuraEffect, AuraModeNum};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use rog_profiles::{fan_curve_set::FanCurveSet, FanCurvePU, Profile};
|
||||
use rog_supported::SupportedFunctions;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BiosState {
|
||||
/// To be shared to a thread that checks notifications.
|
||||
/// It's a bit general in that it won't provide *what* was
|
||||
/// updated, so the full state needs refresh
|
||||
pub was_notified: Arc<AtomicBool>,
|
||||
pub post_sound: bool,
|
||||
pub dedicated_gfx: bool,
|
||||
pub panel_overdrive: bool,
|
||||
pub dgpu_disable: bool,
|
||||
pub egpu_enable: bool,
|
||||
}
|
||||
|
||||
impl BiosState {
|
||||
pub fn new(
|
||||
was_notified: Arc<AtomicBool>,
|
||||
supported: &SupportedFunctions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
) -> Self {
|
||||
Self {
|
||||
was_notified,
|
||||
post_sound: if supported.rog_bios_ctrl.post_sound {
|
||||
dbus.proxies().rog_bios().post_boot_sound().unwrap() != 0
|
||||
} else {
|
||||
false
|
||||
},
|
||||
dedicated_gfx: if supported.rog_bios_ctrl.dedicated_gfx {
|
||||
dbus.proxies().rog_bios().dedicated_graphic_mode().unwrap() != 0
|
||||
} else {
|
||||
false
|
||||
},
|
||||
panel_overdrive: if supported.rog_bios_ctrl.panel_overdrive {
|
||||
dbus.proxies().rog_bios().panel_overdrive().unwrap() != 0
|
||||
} else {
|
||||
false
|
||||
},
|
||||
// TODO: needs supergfx
|
||||
dgpu_disable: supported.rog_bios_ctrl.dgpu_disable,
|
||||
egpu_enable: supported.rog_bios_ctrl.egpu_enable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ProfilesState {
|
||||
pub was_notified: Arc<AtomicBool>,
|
||||
pub list: Vec<Profile>,
|
||||
pub current: Profile,
|
||||
}
|
||||
|
||||
impl ProfilesState {
|
||||
pub fn new(
|
||||
was_notified: Arc<AtomicBool>,
|
||||
supported: &SupportedFunctions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
) -> Self {
|
||||
Self {
|
||||
was_notified,
|
||||
list: if supported.platform_profile.platform_profile {
|
||||
dbus.proxies().profile().profiles().unwrap()
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
current: if supported.platform_profile.platform_profile {
|
||||
dbus.proxies().profile().active_profile().unwrap()
|
||||
} else {
|
||||
Profile::Balanced
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FanCurvesState {
|
||||
pub was_notified: Arc<AtomicBool>,
|
||||
pub show_curve: Profile,
|
||||
pub show_graph: FanCurvePU,
|
||||
pub enabled: HashSet<Profile>,
|
||||
pub curves: HashMap<Profile, FanCurveSet>,
|
||||
pub drag_delta: Vec2,
|
||||
}
|
||||
|
||||
impl FanCurvesState {
|
||||
pub fn new(
|
||||
was_notified: Arc<AtomicBool>,
|
||||
supported: &SupportedFunctions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
) -> Self {
|
||||
let profiles = if supported.platform_profile.platform_profile {
|
||||
dbus.proxies().profile().profiles().unwrap()
|
||||
} else {
|
||||
vec![Profile::Balanced, Profile::Quiet, Profile::Performance]
|
||||
};
|
||||
let enabled = if supported.platform_profile.fan_curves {
|
||||
HashSet::from_iter(
|
||||
dbus.proxies()
|
||||
.profile()
|
||||
.enabled_fan_profiles()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.cloned(),
|
||||
)
|
||||
} else {
|
||||
HashSet::from([Profile::Balanced, Profile::Quiet, Profile::Performance])
|
||||
};
|
||||
|
||||
let mut curves: HashMap<Profile, FanCurveSet> = HashMap::new();
|
||||
profiles.iter().for_each(|p| {
|
||||
if supported.platform_profile.fan_curves {
|
||||
let curve = dbus.proxies().profile().fan_curve_data(*p).unwrap();
|
||||
curves.insert(*p, curve);
|
||||
} else {
|
||||
let mut curve = FanCurveSet::default();
|
||||
curve.cpu.pwm = [30, 40, 60, 100, 140, 180, 200, 250];
|
||||
curve.cpu.temp = [20, 30, 40, 50, 70, 80, 90, 100];
|
||||
curve.gpu.pwm = [40, 80, 100, 140, 170, 200, 230, 250];
|
||||
curve.gpu.temp = [20, 30, 40, 50, 70, 80, 90, 100];
|
||||
curves.insert(*p, curve);
|
||||
}
|
||||
});
|
||||
|
||||
let show_curve = if supported.platform_profile.fan_curves {
|
||||
dbus.proxies().profile().active_profile().unwrap()
|
||||
} else {
|
||||
Profile::Balanced
|
||||
};
|
||||
|
||||
Self {
|
||||
was_notified,
|
||||
show_curve,
|
||||
show_graph: FanCurvePU::CPU,
|
||||
enabled,
|
||||
curves,
|
||||
drag_delta: Vec2::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AuraState {
|
||||
pub was_notified: Arc<AtomicBool>,
|
||||
pub current_mode: AuraModeNum,
|
||||
pub modes: BTreeMap<AuraModeNum, AuraEffect>,
|
||||
pub enabled: AuraPowerDev,
|
||||
}
|
||||
|
||||
impl AuraState {
|
||||
pub fn new(
|
||||
was_notified: Arc<AtomicBool>,
|
||||
supported: &SupportedFunctions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
) -> Self {
|
||||
Self {
|
||||
was_notified,
|
||||
current_mode: if !supported.keyboard_led.stock_led_modes.is_empty() {
|
||||
dbus.proxies().led().led_mode().unwrap()
|
||||
} else {
|
||||
AuraModeNum::Static
|
||||
},
|
||||
|
||||
modes: if !supported.keyboard_led.stock_led_modes.is_empty() {
|
||||
dbus.proxies().led().led_modes().unwrap()
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
},
|
||||
enabled: dbus.proxies().led().leds_enabled().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnimeState {
|
||||
pub was_notified: Arc<AtomicBool>,
|
||||
pub bright: u8,
|
||||
pub boot: bool,
|
||||
pub awake: bool,
|
||||
pub sleep: bool,
|
||||
}
|
||||
|
||||
impl AnimeState {
|
||||
pub fn new(
|
||||
was_notified: Arc<AtomicBool>,
|
||||
supported: &SupportedFunctions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
) -> Self {
|
||||
Self {
|
||||
was_notified,
|
||||
boot: if supported.anime_ctrl.0 {
|
||||
dbus.proxies().anime().boot_enabled().unwrap()
|
||||
} else {
|
||||
false
|
||||
},
|
||||
awake: if supported.anime_ctrl.0 {
|
||||
dbus.proxies().anime().awake_enabled().unwrap()
|
||||
} else {
|
||||
false
|
||||
},
|
||||
// TODO:
|
||||
sleep: false,
|
||||
bright: 200,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PageDataStates {
|
||||
pub was_notified: Arc<AtomicBool>,
|
||||
/// Because much of the app state here is the same as `RogBiosSupportedFunctions`
|
||||
/// we can re-use that structure.
|
||||
pub bios: BiosState,
|
||||
pub aura: AuraState,
|
||||
pub anime: AnimeState,
|
||||
pub profiles: ProfilesState,
|
||||
pub fan_curves: FanCurvesState,
|
||||
pub charge_limit: i16,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl PageDataStates {
|
||||
pub fn new(
|
||||
charge_notified: Arc<AtomicBool>,
|
||||
bios_notified: Arc<AtomicBool>,
|
||||
aura_notified: Arc<AtomicBool>,
|
||||
anime_notified: Arc<AtomicBool>,
|
||||
profiles_notified: Arc<AtomicBool>,
|
||||
fans_notified: Arc<AtomicBool>,
|
||||
supported: &SupportedFunctions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
) -> Self {
|
||||
Self {
|
||||
was_notified: charge_notified,
|
||||
charge_limit: dbus.proxies().charge().limit().unwrap(),
|
||||
bios: BiosState::new(bios_notified, supported, dbus),
|
||||
aura: AuraState::new(aura_notified, supported, dbus),
|
||||
anime: AnimeState::new(anime_notified, supported, dbus),
|
||||
profiles: ProfilesState::new(profiles_notified, supported, dbus),
|
||||
fan_curves: FanCurvesState::new(fans_notified, supported, dbus),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_if_notfied(
|
||||
&mut self,
|
||||
supported: &SupportedFunctions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
) -> bool {
|
||||
let mut notified = false;
|
||||
if self.was_notified.load(Ordering::SeqCst) {
|
||||
self.charge_limit = dbus.proxies().charge().limit().unwrap();
|
||||
self.was_notified.store(false, Ordering::SeqCst);
|
||||
notified = true;
|
||||
}
|
||||
|
||||
if self.aura.was_notified.load(Ordering::SeqCst) {
|
||||
self.aura = AuraState::new(self.aura.was_notified.clone(), supported, dbus);
|
||||
self.aura.was_notified.store(false, Ordering::SeqCst);
|
||||
notified = true;
|
||||
}
|
||||
|
||||
if self.bios.was_notified.load(Ordering::SeqCst) {
|
||||
self.bios = BiosState::new(self.bios.was_notified.clone(), supported, dbus);
|
||||
self.bios.was_notified.store(false, Ordering::SeqCst);
|
||||
notified = true;
|
||||
}
|
||||
|
||||
if self.profiles.was_notified.load(Ordering::SeqCst) {
|
||||
self.profiles = ProfilesState::new(self.profiles.was_notified.clone(), supported, dbus);
|
||||
self.profiles.was_notified.store(false, Ordering::SeqCst);
|
||||
notified = true;
|
||||
}
|
||||
|
||||
if self.fan_curves.was_notified.load(Ordering::SeqCst) {
|
||||
self.fan_curves =
|
||||
FanCurvesState::new(self.fan_curves.was_notified.clone(), supported, dbus);
|
||||
self.fan_curves.was_notified.store(false, Ordering::SeqCst);
|
||||
notified = true;
|
||||
}
|
||||
notified
|
||||
}
|
||||
}
|
||||
77
rog-control-center/src/widgets/anime_page.rs
Normal file
77
rog-control-center/src/widgets/anime_page.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use egui::RichText;
|
||||
|
||||
use crate::RogApp;
|
||||
|
||||
impl<'a> RogApp<'a> {
|
||||
pub fn anime_page(&mut self, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("AniMe Matrix Settings");
|
||||
ui.label("Options are incomplete. Awake + Boot should work");
|
||||
|
||||
let Self {
|
||||
states,
|
||||
asus_dbus: dbus,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let mut changed = false;
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
let h = 16.0;
|
||||
ui.set_row_height(22.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Brightness").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Boot").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Awake").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Sleep").size(h));
|
||||
});
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.set_row_height(22.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui
|
||||
.add(egui::Slider::new(&mut states.anime.bright, 0..=254))
|
||||
.changed()
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.checkbox(&mut states.anime.boot, "Enable").changed() {
|
||||
dbus.proxies()
|
||||
.anime()
|
||||
.set_boot_on_off(states.anime.boot)
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.checkbox(&mut states.anime.awake, "Enable").changed() {
|
||||
dbus.proxies()
|
||||
.anime()
|
||||
.set_on_off(states.anime.awake)
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.checkbox(&mut states.anime.sleep, "Enable").changed() {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
455
rog-control-center/src/widgets/aura_page.rs
Normal file
455
rog-control-center/src/widgets/aura_page.rs
Normal file
@@ -0,0 +1,455 @@
|
||||
use egui::{RichText, Ui};
|
||||
use rog_aura::{
|
||||
usb::{AuraDev1866, AuraDev19b6, AuraDevice, AuraPowerDev},
|
||||
AuraModeNum, AuraZone, Colour, Speed,
|
||||
};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use rog_supported::SupportedFunctions;
|
||||
|
||||
use crate::{
|
||||
page_states::{AuraState, PageDataStates},
|
||||
RogApp,
|
||||
};
|
||||
|
||||
impl<'a> RogApp<'a> {
|
||||
pub fn aura_page(&mut self, ctx: &egui::Context) {
|
||||
let Self {
|
||||
supported,
|
||||
states,
|
||||
asus_dbus: dbus,
|
||||
..
|
||||
} = self;
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
Self::aura_power(supported, states, dbus, ui);
|
||||
ui.separator();
|
||||
Self::aura_modes(supported, states, dbus, ui);
|
||||
});
|
||||
}
|
||||
|
||||
fn aura_power(
|
||||
supported: &SupportedFunctions,
|
||||
states: &mut PageDataStates,
|
||||
dbus: &mut RogDbusClientBlocking,
|
||||
ui: &mut Ui,
|
||||
) {
|
||||
match supported.keyboard_led.prod_id {
|
||||
AuraDevice::X1854 | AuraDevice::X1869 | AuraDevice::X1866 => {
|
||||
Self::aura_power1(supported, states, dbus, ui)
|
||||
}
|
||||
AuraDevice::X19B6 => Self::aura_power2(supported, states, dbus, ui),
|
||||
AuraDevice::Unknown => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn aura_power1(
|
||||
supported: &SupportedFunctions,
|
||||
states: &mut PageDataStates,
|
||||
dbus: &mut RogDbusClientBlocking,
|
||||
ui: &mut Ui,
|
||||
) {
|
||||
let enabled_states = &mut states.aura.enabled;
|
||||
|
||||
ui.heading("Aura go brrrrr! (incomplete)");
|
||||
ui.separator();
|
||||
|
||||
let boot = &mut enabled_states.x1866.contains(&AuraDev1866::Boot);
|
||||
let sleep = &mut enabled_states.x1866.contains(&AuraDev1866::Sleep);
|
||||
let keyboard = &mut enabled_states.x1866.contains(&AuraDev1866::Keyboard);
|
||||
let lightbar = &mut enabled_states.x1866.contains(&AuraDev1866::Lightbar);
|
||||
let mut changed = false;
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
let h = 16.0;
|
||||
ui.set_row_height(22.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Boot").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Awake").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Sleep").size(h));
|
||||
});
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.set_row_height(22.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.checkbox(boot, "Enable").changed() {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.toggle_value(keyboard, "Keyboard").changed() {
|
||||
changed = true;
|
||||
}
|
||||
if !supported.keyboard_led.multizone_led_mode.is_empty() {
|
||||
if ui.toggle_value(lightbar, "Lightbar").changed() {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.checkbox(sleep, "Enable").changed() {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if changed {
|
||||
let mut enabled = Vec::new();
|
||||
let mut disabled = Vec::new();
|
||||
|
||||
let mut modify = |b: bool, a: AuraDev1866| {
|
||||
if b {
|
||||
enabled.push(a);
|
||||
if !enabled_states.x1866.contains(&a) {
|
||||
enabled_states.x1866.push(a);
|
||||
}
|
||||
} else {
|
||||
disabled.push(a);
|
||||
// This would be so much better as a hashset
|
||||
if enabled_states.x1866.contains(&a) {
|
||||
let mut idx = 0;
|
||||
for (i, n) in enabled_states.x1866.iter().enumerate() {
|
||||
if *n == a {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
enabled_states.x1866.remove(idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
modify(*boot, AuraDev1866::Boot);
|
||||
modify(*sleep, AuraDev1866::Sleep);
|
||||
modify(*keyboard, AuraDev1866::Keyboard);
|
||||
if !supported.keyboard_led.multizone_led_mode.is_empty() {
|
||||
modify(*lightbar, AuraDev1866::Lightbar);
|
||||
}
|
||||
|
||||
let mut send = |enable: bool, data: Vec<AuraDev1866>| {
|
||||
let options = AuraPowerDev {
|
||||
x1866: data,
|
||||
x19b6: vec![],
|
||||
};
|
||||
// build data to send
|
||||
dbus.proxies()
|
||||
.led()
|
||||
.set_leds_power(options, enable)
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
};
|
||||
send(true, enabled);
|
||||
send(false, disabled);
|
||||
}
|
||||
}
|
||||
|
||||
fn aura_power2(
|
||||
supported: &SupportedFunctions,
|
||||
states: &mut PageDataStates,
|
||||
dbus: &mut RogDbusClientBlocking,
|
||||
ui: &mut Ui,
|
||||
) {
|
||||
let enabled_states = &mut states.aura.enabled;
|
||||
|
||||
ui.heading("Lights go brrrrr! (incomplete)");
|
||||
ui.separator();
|
||||
|
||||
let has_logo = supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::Logo);
|
||||
let has_lightbar = supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::BarLeft)
|
||||
|| supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::BarRight);
|
||||
|
||||
let boot_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootBar);
|
||||
let boot_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootLogo);
|
||||
let boot_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::BootKeyb);
|
||||
|
||||
let awake_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeBar);
|
||||
let awake_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeLogo);
|
||||
let awake_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::AwakeKeyb);
|
||||
|
||||
let sleep_bar = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepBar);
|
||||
let sleep_logo = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepLogo);
|
||||
let sleep_keyb = &mut enabled_states.x19b6.contains(&AuraDev19b6::SleepKeyb);
|
||||
|
||||
let mut changed = false;
|
||||
|
||||
let mut item = |keyboard: &mut bool, logo: &mut bool, lightbar: &mut bool, ui: &mut Ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
if ui.checkbox(keyboard, "Keyboard").changed() {
|
||||
changed = true;
|
||||
}
|
||||
if has_logo && ui.checkbox(logo, "Logo").changed() {
|
||||
changed = true;
|
||||
}
|
||||
if has_lightbar && ui.checkbox(lightbar, "Lightbar").changed() {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
let h = 16.0;
|
||||
ui.set_row_height(22.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Boot").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Awake").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Sleep").size(h));
|
||||
});
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.set_row_height(22.0);
|
||||
item(boot_keyb, boot_logo, boot_bar, ui);
|
||||
item(awake_keyb, awake_logo, awake_bar, ui);
|
||||
item(sleep_keyb, sleep_logo, sleep_bar, ui);
|
||||
});
|
||||
});
|
||||
|
||||
if changed {
|
||||
let mut enabled = Vec::new();
|
||||
let mut disabled = Vec::new();
|
||||
|
||||
let mut modify = |b: bool, a: AuraDev19b6| {
|
||||
if b {
|
||||
enabled.push(a);
|
||||
if !enabled_states.x19b6.contains(&a) {
|
||||
enabled_states.x19b6.push(a);
|
||||
}
|
||||
} else {
|
||||
disabled.push(a);
|
||||
// This would be so much better as a hashset
|
||||
if enabled_states.x19b6.contains(&a) {
|
||||
let mut idx = 0;
|
||||
for (i, n) in enabled_states.x19b6.iter().enumerate() {
|
||||
if *n == a {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
enabled_states.x1866.remove(idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
modify(*boot_keyb, AuraDev19b6::BootKeyb);
|
||||
modify(*sleep_keyb, AuraDev19b6::SleepKeyb);
|
||||
modify(*awake_keyb, AuraDev19b6::AwakeKeyb);
|
||||
if supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::Logo)
|
||||
{
|
||||
modify(*boot_logo, AuraDev19b6::BootLogo);
|
||||
modify(*sleep_logo, AuraDev19b6::SleepLogo);
|
||||
modify(*awake_logo, AuraDev19b6::AwakeLogo);
|
||||
}
|
||||
if supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::BarLeft)
|
||||
{
|
||||
modify(*boot_bar, AuraDev19b6::BootBar);
|
||||
modify(*sleep_bar, AuraDev19b6::SleepBar);
|
||||
modify(*awake_bar, AuraDev19b6::AwakeBar);
|
||||
}
|
||||
|
||||
let mut send = |enable: bool, data: Vec<AuraDev19b6>| {
|
||||
let options = AuraPowerDev {
|
||||
x1866: vec![],
|
||||
x19b6: data,
|
||||
};
|
||||
// build data to send
|
||||
dbus.proxies()
|
||||
.led()
|
||||
.set_leds_power(options, enable)
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
};
|
||||
send(true, enabled);
|
||||
send(false, disabled);
|
||||
}
|
||||
}
|
||||
|
||||
fn aura_modes(
|
||||
supported: &SupportedFunctions,
|
||||
states: &mut PageDataStates,
|
||||
dbus: &mut RogDbusClientBlocking,
|
||||
ui: &mut Ui,
|
||||
) {
|
||||
let mut changed = false;
|
||||
let mut selected = states.aura.current_mode;
|
||||
|
||||
let has_keyzones = supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::Key2);
|
||||
let has_logo = supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::Logo);
|
||||
let has_lightbar = supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::BarLeft)
|
||||
|| supported
|
||||
.keyboard_led
|
||||
.multizone_led_mode
|
||||
.contains(&AuraZone::BarRight);
|
||||
|
||||
ui.heading("Aura modes");
|
||||
let mut item = |a: AuraModeNum, ui: &mut Ui| {
|
||||
if ui
|
||||
.selectable_value(&mut selected, a, format!("{:?}", a))
|
||||
.clicked()
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
};
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for a in states.aura.modes.keys() {
|
||||
item(*a, ui);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Need some sort of mapping to enable options only if
|
||||
// they actually work.
|
||||
if let Some(effect) = states.aura.modes.get_mut(&selected) {
|
||||
let mut zone_button = |a: AuraZone, ui: &mut Ui| {
|
||||
ui.selectable_value(&mut effect.zone, a, format!("{:?}", a));
|
||||
};
|
||||
let mut speed_button = |a: Speed, ui: &mut Ui| {
|
||||
ui.selectable_value(&mut effect.speed, a, format!("{:?}", a));
|
||||
};
|
||||
let mut dir_button = |a: rog_aura::Direction, ui: &mut Ui| {
|
||||
ui.selectable_value(&mut effect.direction, a, format!("{:?}", a));
|
||||
};
|
||||
|
||||
let mut c1: [f32; 3] = effect.colour1.into();
|
||||
let mut c2: [f32; 3] = effect.colour2.into();
|
||||
|
||||
ui.separator();
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
let h = 16.0;
|
||||
ui.set_row_height(22.0);
|
||||
if has_keyzones || has_lightbar || has_logo {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Zone").size(h));
|
||||
});
|
||||
}
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Colour 1").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Colour 2").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Speed").size(h));
|
||||
});
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.label(RichText::new("Direction").size(h));
|
||||
});
|
||||
});
|
||||
ui.vertical(|ui| {
|
||||
ui.set_row_height(22.0);
|
||||
if has_keyzones || has_lightbar || has_logo {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
zone_button(AuraZone::None, ui);
|
||||
if has_keyzones {
|
||||
zone_button(AuraZone::Key1, ui);
|
||||
zone_button(AuraZone::Key2, ui);
|
||||
zone_button(AuraZone::Key3, ui);
|
||||
zone_button(AuraZone::Key4, ui);
|
||||
}
|
||||
if has_logo {
|
||||
zone_button(AuraZone::Logo, ui);
|
||||
}
|
||||
if has_lightbar {
|
||||
zone_button(AuraZone::BarLeft, ui);
|
||||
zone_button(AuraZone::BarRight, ui);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
egui::color_picker::color_edit_button_rgb(ui, &mut c1);
|
||||
egui::color_picker::color_edit_button_rgb(ui, &mut c2);
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
speed_button(Speed::Low, ui);
|
||||
speed_button(Speed::Med, ui);
|
||||
speed_button(Speed::High, ui);
|
||||
});
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
dir_button(rog_aura::Direction::Left, ui);
|
||||
dir_button(rog_aura::Direction::Down, ui);
|
||||
dir_button(rog_aura::Direction::Right, ui);
|
||||
dir_button(rog_aura::Direction::Up, ui);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
effect.colour1 = Colour::from(&c1);
|
||||
effect.colour2 = Colour::from(&c2);
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||
if ui.add(egui::Button::new("Cancel")).clicked() {
|
||||
let notif = states.aura.was_notified.clone();
|
||||
states.aura.modes = AuraState::new(notif, supported, dbus).modes;
|
||||
}
|
||||
|
||||
if ui.add(egui::Button::new("Apply")).clicked() {
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
// egui::TopBottomPanel::bottom("error_bar")
|
||||
// .default_height(26.0)
|
||||
// .show(ctx, |ui| {
|
||||
// ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||
// if ui.add(egui::Button::new("Cancel")).clicked() {
|
||||
// let notif = states.aura.was_notified.clone();
|
||||
// states.aura.modes = AuraState::new(notif, supported, dbus).modes;
|
||||
// }
|
||||
|
||||
// if ui.add(egui::Button::new("Apply")).clicked() {
|
||||
// changed = true;
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
if changed {
|
||||
states.aura.current_mode = selected;
|
||||
|
||||
dbus.proxies()
|
||||
.led()
|
||||
.set_led_mode(states.aura.modes.get(&selected).unwrap())
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
199
rog-control-center/src/widgets/fan_curve_page.rs
Normal file
199
rog-control-center/src/widgets/fan_curve_page.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
use crate::{
|
||||
page_states::{FanCurvesState, ProfilesState},
|
||||
RogApp,
|
||||
};
|
||||
use egui::{plot::Points, Ui};
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use rog_profiles::{FanCurvePU, Profile};
|
||||
use rog_supported::SupportedFunctions;
|
||||
|
||||
impl<'a> RogApp<'a> {
|
||||
pub fn fan_curve_page(&mut self, ctx: &egui::Context) {
|
||||
let Self {
|
||||
supported,
|
||||
states,
|
||||
asus_dbus: dbus,
|
||||
..
|
||||
} = self;
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.heading("Custom fan curves");
|
||||
ui.label("A fan curve is only active when the related profile is active and the curve is enabled");
|
||||
Self::fan_curve(
|
||||
supported,
|
||||
&mut states.profiles,
|
||||
&mut states.fan_curves,
|
||||
dbus, &mut states.error,
|
||||
ui,
|
||||
);
|
||||
|
||||
Self::fan_graphs(&mut states.profiles, &mut states.fan_curves, dbus, &mut states.error, ui);
|
||||
});
|
||||
}
|
||||
|
||||
fn fan_curve(
|
||||
supported: &SupportedFunctions,
|
||||
profiles: &mut ProfilesState,
|
||||
curves: &mut FanCurvesState,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
do_error: &mut Option<String>,
|
||||
ui: &mut Ui,
|
||||
) {
|
||||
ui.separator();
|
||||
ui.label("Enabled fan-curves");
|
||||
|
||||
let mut changed = false;
|
||||
ui.horizontal(|ui| {
|
||||
let mut item = |p: Profile, _curves: &mut FanCurvesState, mut checked: bool| {
|
||||
if ui
|
||||
.add(egui::Checkbox::new(&mut checked, format!("{:?}", p)))
|
||||
.changed()
|
||||
{
|
||||
dbus.proxies()
|
||||
.profile()
|
||||
.set_fan_curve_enabled(p, checked)
|
||||
.map_err(|err| {
|
||||
*do_error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
|
||||
#[cfg(feature = "mocking")]
|
||||
if !checked {
|
||||
_curves.enabled.remove(&p);
|
||||
} else {
|
||||
_curves.enabled.insert(p);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
};
|
||||
|
||||
for f in profiles.list.iter() {
|
||||
item(*f, curves, curves.enabled.contains(f));
|
||||
}
|
||||
});
|
||||
|
||||
if changed {
|
||||
// Need to update app data if change made
|
||||
#[cfg(not(feature = "mocking"))]
|
||||
{
|
||||
let notif = curves.was_notified.clone();
|
||||
*curves = FanCurvesState::new(notif, supported, dbus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fan_graphs(
|
||||
profiles: &mut ProfilesState,
|
||||
curves: &mut FanCurvesState,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
do_error: &mut Option<String>,
|
||||
ui: &mut Ui,
|
||||
) {
|
||||
ui.separator();
|
||||
|
||||
let mut item = |p: Profile, ui: &mut Ui| {
|
||||
ui.selectable_value(&mut curves.show_curve, p, format!("{p:?}"));
|
||||
};
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for a in curves.curves.iter() {
|
||||
item(*a.0, ui);
|
||||
}
|
||||
|
||||
ui.selectable_value(
|
||||
&mut curves.show_graph,
|
||||
FanCurvePU::CPU,
|
||||
format!("{:?}", FanCurvePU::CPU),
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut curves.show_graph,
|
||||
FanCurvePU::GPU,
|
||||
format!("{:?}", FanCurvePU::GPU),
|
||||
);
|
||||
});
|
||||
|
||||
let curve = curves.curves.get_mut(&curves.show_curve).unwrap();
|
||||
|
||||
use egui::plot::{Line, Plot, PlotPoints};
|
||||
|
||||
let data = if curves.show_graph == FanCurvePU::CPU {
|
||||
&mut curve.cpu
|
||||
} else {
|
||||
&mut curve.gpu
|
||||
};
|
||||
|
||||
let points = data.temp.iter().enumerate().map(|(idx, x)| {
|
||||
let x = *x as f64;
|
||||
let y = ((data.pwm[idx] as u32) * 100 / 255) as f64;
|
||||
[x, y]
|
||||
});
|
||||
|
||||
let line = Line::new(PlotPoints::from_iter(points.clone())).width(2.0);
|
||||
let points = Points::new(PlotPoints::from_iter(points)).radius(3.0);
|
||||
|
||||
Plot::new("my_plot")
|
||||
.view_aspect(2.0)
|
||||
// .center_x_axis(true)
|
||||
// .center_y_axis(true)
|
||||
.include_x(0.0)
|
||||
.include_x(110.0)
|
||||
.include_y(0.0)
|
||||
.include_y(110.0)
|
||||
.allow_scroll(false)
|
||||
.allow_drag(false)
|
||||
.allow_boxed_zoom(false)
|
||||
.x_axis_formatter(|d, _r| format!("{}", d))
|
||||
.y_axis_formatter(|d, _r| format!("{:.*}%", 1, d))
|
||||
.label_formatter(|name, value| {
|
||||
if !name.is_empty() {
|
||||
format!("{}: {:.*}%", name, 1, value.y)
|
||||
} else {
|
||||
format!("Temp {}c\nFan {:.*}%", value.x as u8, 1, value.y)
|
||||
}
|
||||
})
|
||||
.show(ui, |plot_ui| {
|
||||
if plot_ui.plot_hovered() {
|
||||
let mut idx = 0;
|
||||
|
||||
if let Some(point) = plot_ui.pointer_coordinate() {
|
||||
let mut x: i32 = 255;
|
||||
for (i, n) in data.temp.iter().enumerate() {
|
||||
let tmp = x.min((point.x as i32 - *n as i32).abs());
|
||||
if tmp < x {
|
||||
x = tmp;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if plot_ui.plot_clicked() {
|
||||
data.temp[idx] = point.x as u8;
|
||||
data.pwm[idx] = (point.y * 255.0 / 100.0) as u8;
|
||||
} else {
|
||||
let drag = plot_ui.pointer_coordinate_drag_delta();
|
||||
if drag.length_sq() != 0.0 {
|
||||
data.temp[idx] = (point.x as f32 + drag.x) as u8;
|
||||
data.pwm[idx] = ((point.y as f32 + drag.y) * 255.0 / 100.0) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
plot_ui.line(line);
|
||||
plot_ui.points(points)
|
||||
});
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||
if ui.add(egui::Button::new("Apply Fan-curve")).clicked() {
|
||||
#[cfg(not(feature = "mocking"))]
|
||||
dbus.proxies()
|
||||
.profile()
|
||||
.set_fan_curve(profiles.current, data.clone())
|
||||
.map_err(|err| {
|
||||
*do_error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
#[cfg(feature = "mocking")]
|
||||
dbg!("Applied");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
13
rog-control-center/src/widgets/mod.rs
Normal file
13
rog-control-center/src/widgets/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
mod anime_page;
|
||||
mod aura_page;
|
||||
mod fan_curve_page;
|
||||
mod side_panel;
|
||||
mod system_page;
|
||||
mod top_bar;
|
||||
|
||||
pub use anime_page::*;
|
||||
pub use aura_page::*;
|
||||
pub use fan_curve_page::*;
|
||||
pub use side_panel::*;
|
||||
pub use system_page::*;
|
||||
pub use top_bar::*;
|
||||
62
rog-control-center/src/widgets/side_panel.rs
Normal file
62
rog-control-center/src/widgets/side_panel.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use crate::{Page, RogApp};
|
||||
|
||||
impl<'a> RogApp<'a> {
|
||||
pub fn side_panel(&mut self, ctx: &egui::Context) {
|
||||
egui::SidePanel::left("side_panel")
|
||||
.resizable(false)
|
||||
.default_width(60.0) // TODO: set size to match icon buttons when done
|
||||
.show(ctx, |ui| {
|
||||
let Self { page, .. } = self;
|
||||
|
||||
ui.heading("Functions");
|
||||
|
||||
ui.separator();
|
||||
if ui
|
||||
.selectable_value(page, Page::System, "System Settings")
|
||||
.clicked()
|
||||
{
|
||||
*page = Page::System;
|
||||
}
|
||||
|
||||
if self.supported.platform_profile.fan_curves || cfg!(feature = "mocking") {
|
||||
ui.separator();
|
||||
if ui
|
||||
.selectable_value(page, Page::FanCurves, "Fan Curves")
|
||||
.clicked()
|
||||
{
|
||||
*page = Page::FanCurves;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.supported.keyboard_led.stock_led_modes.is_empty()
|
||||
|| cfg!(feature = "mocking")
|
||||
{
|
||||
ui.separator();
|
||||
if ui
|
||||
.selectable_value(page, Page::AuraEffects, "Keyboard Aura")
|
||||
.clicked()
|
||||
{
|
||||
*page = Page::AuraEffects;
|
||||
}
|
||||
}
|
||||
|
||||
if self.supported.anime_ctrl.0 || cfg!(feature = "mocking") {
|
||||
ui.separator();
|
||||
if ui
|
||||
.selectable_value(page, Page::AnimeMatrix, "AniMe Matrix")
|
||||
.clicked()
|
||||
{
|
||||
*page = Page::AnimeMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("Source code ");
|
||||
ui.hyperlink_to("rog-gui.", "https://gitlab.com/asus-linux/rog-gui");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
117
rog-control-center/src/widgets/system_page.rs
Normal file
117
rog-control-center/src/widgets/system_page.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use crate::{page_states::PageDataStates, RogApp};
|
||||
use egui::Ui;
|
||||
use rog_dbus::RogDbusClientBlocking;
|
||||
use rog_profiles::Profile;
|
||||
|
||||
impl<'a> RogApp<'a> {
|
||||
pub fn system_page(&mut self, ctx: &egui::Context) {
|
||||
let Self {
|
||||
supported,
|
||||
states,
|
||||
asus_dbus: dbus,
|
||||
..
|
||||
} = self;
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
// The central panel the region left after adding TopPanel's and SidePanel's
|
||||
|
||||
ui.heading("Experimental application for asusd");
|
||||
ui.horizontal(|ui| {
|
||||
egui::global_dark_light_mode_buttons(ui);
|
||||
egui::warn_if_debug_build(ui);
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
ui.heading("Charge control");
|
||||
let slider = egui::Slider::new(&mut states.charge_limit, 20..=100)
|
||||
.text("Limit")
|
||||
.step_by(1.0);
|
||||
if ui.add(slider).drag_released() {
|
||||
dbus.proxies()
|
||||
.charge()
|
||||
.set_limit(states.charge_limit as u8)
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
ui.heading("Bios options");
|
||||
|
||||
if supported.rog_bios_ctrl.post_sound {
|
||||
if ui
|
||||
.add(egui::Checkbox::new(
|
||||
&mut states.bios.post_sound,
|
||||
"POST sound",
|
||||
))
|
||||
.changed()
|
||||
{
|
||||
dbus.proxies()
|
||||
.rog_bios()
|
||||
.set_post_boot_sound(states.bios.post_sound)
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
if supported.rog_bios_ctrl.dedicated_gfx {
|
||||
if ui
|
||||
.add(egui::Checkbox::new(
|
||||
&mut states.bios.dedicated_gfx,
|
||||
"G-Sync Dedicated GPU mode",
|
||||
))
|
||||
.changed()
|
||||
{
|
||||
dbus.proxies()
|
||||
.rog_bios()
|
||||
.set_dedicated_graphic_mode(states.bios.dedicated_gfx)
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
if supported.platform_profile.platform_profile {
|
||||
Self::platform_profile(states, dbus, ui);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn platform_profile(states: &mut PageDataStates, dbus: &RogDbusClientBlocking, ui: &mut Ui) {
|
||||
ui.separator();
|
||||
ui.heading("Platform profile");
|
||||
|
||||
let mut changed = false;
|
||||
let mut item = |p: Profile, ui: &mut Ui| {
|
||||
if ui
|
||||
.selectable_value(&mut states.profiles.current, p, format!("{p:?}"))
|
||||
.clicked()
|
||||
{
|
||||
changed = true;
|
||||
}
|
||||
};
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for a in states.profiles.list.iter() {
|
||||
item(*a, ui);
|
||||
}
|
||||
});
|
||||
|
||||
if changed {
|
||||
dbus.proxies()
|
||||
.profile()
|
||||
.set_active_profile(states.profiles.current)
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
};
|
||||
}
|
||||
}
|
||||
53
rog-control-center/src/widgets/top_bar.rs
Normal file
53
rog-control-center/src/widgets/top_bar.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use crate::RogApp;
|
||||
|
||||
impl<'a> RogApp<'a> {
|
||||
pub fn top_bar(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
let Self { states, config, .. } = self;
|
||||
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
// The top panel is often a good place for a menu bar:
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Quit").clicked() {
|
||||
frame.quit();
|
||||
}
|
||||
});
|
||||
ui.menu_button("Settings", |ui| {
|
||||
let (mut in_bg, mut hidden) =
|
||||
{ (config.run_in_background, config.startup_in_background) };
|
||||
if ui.checkbox(&mut in_bg, "Run in Background").clicked() {
|
||||
config.run_in_background = in_bg;
|
||||
config
|
||||
.save()
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
if ui.checkbox(&mut hidden, "Startup Hidden").clicked() {
|
||||
config.startup_in_background = in_bg;
|
||||
config
|
||||
.save()
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
if ui
|
||||
.checkbox(&mut config.enable_notifications, "Enable Notifications")
|
||||
.clicked()
|
||||
{
|
||||
config.enable_notifications = in_bg;
|
||||
// TODO: set an atomicbool used in the notif thread
|
||||
config
|
||||
.save()
|
||||
.map_err(|err| {
|
||||
states.error = Some(err.to_string());
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user