mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
Tray icons
This commit is contained in:
@@ -12,6 +12,10 @@ 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"
|
||||
|
||||
daemon = { path = "../daemon" }
|
||||
rog_anime = { path = "../rog-anime" }
|
||||
rog_dbus = { path = "../rog-dbus" }
|
||||
@@ -21,6 +25,9 @@ rog_platform = { path = "../rog-platform" }
|
||||
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", default-features = false }
|
||||
#supergfxctl = { path = "../../supergfxctl", default-features = false }
|
||||
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
toml.workspace = true
|
||||
@@ -30,5 +37,7 @@ zbus.workspace = true
|
||||
dirs.workspace = true
|
||||
notify-rust.workspace = true
|
||||
|
||||
nix = "^0.20.0"
|
||||
png_pong.workspace = true
|
||||
|
||||
nix = "^0.25"
|
||||
tempfile = "3.2.0"
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
@@ -2,16 +2,18 @@ use std::{
|
||||
f64::consts::PI,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||
mpsc::Receiver,
|
||||
Arc,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
time::{Duration, Instant}, io::Write,
|
||||
};
|
||||
|
||||
use egui::{Button, RichText};
|
||||
use rog_platform::supported::SupportedFunctions;
|
||||
|
||||
use crate::{
|
||||
config::Config, error::Result, page_states::PageDataStates, Page, RogDbusClientBlocking,
|
||||
config::Config, error::Result, page_states::PageDataStates, tray::TrayToApp, Page,
|
||||
RogDbusClientBlocking, get_ipc_file, SHOW_GUI,
|
||||
};
|
||||
|
||||
pub struct RogApp<'a> {
|
||||
@@ -29,6 +31,7 @@ pub struct RogApp<'a> {
|
||||
pub oscillator_freq: Arc<AtomicU8>,
|
||||
/// A toggle that toggles true/false when the oscillator reaches 0
|
||||
pub oscillator_toggle: Arc<AtomicBool>,
|
||||
pub app_cmd: Arc<Receiver<TrayToApp>>,
|
||||
}
|
||||
|
||||
impl<'a> RogApp<'a> {
|
||||
@@ -36,6 +39,7 @@ impl<'a> RogApp<'a> {
|
||||
pub fn new(
|
||||
config: Config,
|
||||
states: PageDataStates,
|
||||
app_cmd: Arc<Receiver<TrayToApp>>,
|
||||
_cc: &eframe::CreationContext<'_>,
|
||||
) -> Result<Self> {
|
||||
let (dbus, _) = RogDbusClientBlocking::new()?;
|
||||
@@ -98,20 +102,38 @@ impl<'a> RogApp<'a> {
|
||||
oscillator3,
|
||||
oscillator_toggle,
|
||||
oscillator_freq,
|
||||
app_cmd,
|
||||
})
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> eframe::App for RogApp<'a> {
|
||||
/// 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) {
|
||||
self.check_app_cmds(ctx, frame);
|
||||
|
||||
let Self {
|
||||
supported,
|
||||
asus_dbus: dbus,
|
||||
states,
|
||||
..
|
||||
} = self;
|
||||
|
||||
states
|
||||
.refresh_if_notfied(supported, dbus)
|
||||
.map(|repaint| {
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod page_states;
|
||||
pub mod pages;
|
||||
pub mod startup_error;
|
||||
pub mod widgets;
|
||||
pub mod tray;
|
||||
|
||||
#[cfg(feature = "mocking")]
|
||||
pub use mocking::RogDbusClientBlocking;
|
||||
@@ -96,7 +97,7 @@ 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)) {
|
||||
if !matches!(e, nix::errno::Errno::EEXIST) {
|
||||
return Err(e)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,48 @@
|
||||
use eframe::NativeOptions;
|
||||
use eframe::{IconData, NativeOptions};
|
||||
use log::{error, LevelFilter};
|
||||
use rog_aura::layouts::KeyLayout;
|
||||
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,
|
||||
page_states::PageDataStates, print_versions, startup_error::AppErrorShow, RogApp,
|
||||
RogDbusClientBlocking, SHOWING_GUI, SHOW_GUI,
|
||||
page_states::PageDataStates, print_versions, startup_error::AppErrorShow, tray::init_tray,
|
||||
RogApp, RogDbusClientBlocking, SHOWING_GUI, SHOW_GUI,
|
||||
};
|
||||
use rog_platform::supported::SupportedFunctions;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::sync::Mutex;
|
||||
use std::{
|
||||
fs::OpenOptions,
|
||||
io::{Read, Write},
|
||||
path::PathBuf,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
#[cfg(not(feature = "mocking"))]
|
||||
const DATA_DIR: &str = "/usr/share/rog-gui/";
|
||||
#[cfg(feature = "mocking")]
|
||||
const DATA_DIR: &str = env!("CARGO_MANIFEST_DIR");
|
||||
const BOARD_NAME: &str = "/sys/class/dmi/id/board_name";
|
||||
const APP_ICON_PATH: &str = "/usr/share/icons/hicolor/512x512/apps/rog-control-center.png";
|
||||
|
||||
fn main() -> Result<()> {
|
||||
print_versions();
|
||||
let mut logger = env_logger::Builder::new();
|
||||
logger
|
||||
.target(env_logger::Target::Stdout)
|
||||
.format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args()))
|
||||
.filter(None, LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
// start tokio
|
||||
let rt = Runtime::new().expect("Unable to create Runtime");
|
||||
// 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,
|
||||
@@ -36,6 +50,7 @@ fn main() -> Result<()> {
|
||||
min_window_size: Some(egui::vec2(840.0, 600.0)),
|
||||
max_window_size: Some(egui::vec2(840.0, 600.0)),
|
||||
run_and_return: true,
|
||||
icon_data: Some(load_icon()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -93,13 +108,18 @@ fn main() -> Result<()> {
|
||||
Err(_) => on_tmp_dir_exists().unwrap(),
|
||||
};
|
||||
|
||||
let states =
|
||||
setup_page_state_and_notifs(layout.clone(), &config, native_options.clone(), &dbus)
|
||||
.unwrap();
|
||||
let states = setup_page_state_and_notifs(
|
||||
layout.clone(),
|
||||
&config,
|
||||
native_options.clone(),
|
||||
&dbus,
|
||||
update_tray,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
if !start_closed {
|
||||
start_app(states.clone(), native_options.clone())?;
|
||||
start_app(states.clone(), native_options.clone(), app_cmd.clone())?;
|
||||
}
|
||||
|
||||
let config = Config::load().unwrap();
|
||||
@@ -130,6 +150,7 @@ fn setup_page_state_and_notifs(
|
||||
config: &Config,
|
||||
native_options: NativeOptions,
|
||||
dbus: &RogDbusClientBlocking,
|
||||
update_tray: Arc<Mutex<Sender<AppToTray>>>,
|
||||
) -> Result<PageDataStates> {
|
||||
// Cheap method to alert to notifications rather than spinning a thread for each
|
||||
// This is quite different when done in a retained mode app
|
||||
@@ -149,6 +170,7 @@ fn setup_page_state_and_notifs(
|
||||
profiles_notified.clone(),
|
||||
fans_notified.clone(),
|
||||
notifs_enabled.clone(),
|
||||
update_tray,
|
||||
)?;
|
||||
|
||||
let supported = match dbus.proxies().supported().supported_functions() {
|
||||
@@ -177,13 +199,57 @@ fn setup_page_state_and_notifs(
|
||||
)
|
||||
}
|
||||
|
||||
fn start_app(states: PageDataStates, native_options: NativeOptions) -> Result<()> {
|
||||
fn start_app(
|
||||
states: PageDataStates,
|
||||
native_options: NativeOptions,
|
||||
app_cmd: Arc<Receiver<TrayToApp>>,
|
||||
) -> Result<()> {
|
||||
let mut ipc_file = get_ipc_file().unwrap();
|
||||
ipc_file.write_all(&[SHOWING_GUI]).unwrap();
|
||||
eframe::run_native(
|
||||
"ROG Control Center",
|
||||
native_options,
|
||||
Box::new(move |cc| Box::new(RogApp::new(Config::load().unwrap(), states, cc).unwrap())),
|
||||
Box::new(move |cc| {
|
||||
Box::new(RogApp::new(Config::load().unwrap(), states, app_cmd, cc).unwrap())
|
||||
}),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Bah.. the icon dosn't work on wayland anyway, but we'll leave it in for now.
|
||||
fn load_icon() -> IconData {
|
||||
let path = PathBuf::from(APP_ICON_PATH);
|
||||
let mut buf = Vec::new();
|
||||
let mut rgba = Vec::new();
|
||||
let mut height = 512;
|
||||
let mut width = 512;
|
||||
if path.exists() {
|
||||
if let Ok(mut file) = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(path)
|
||||
.map_err(|e| error!("Error opening app icon: {e:?}"))
|
||||
{
|
||||
file.read_to_end(&mut buf)
|
||||
.map_err(|e| error!("Error reading app icon: {e:?}"))
|
||||
.ok();
|
||||
|
||||
let data = std::io::Cursor::new(buf);
|
||||
let decoder = png_pong::Decoder::new(data).unwrap().into_steps();
|
||||
let png_pong::Step { raster, delay: _ } = decoder.last().unwrap().unwrap();
|
||||
|
||||
if let png_pong::PngRaster::Rgba8(ras) = raster {
|
||||
rgba = ras.as_u8_slice().to_vec();
|
||||
width = ras.width();
|
||||
height = ras.height();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Missing {APP_ICON_PATH}")
|
||||
}
|
||||
|
||||
IconData {
|
||||
height,
|
||||
width,
|
||||
rgba,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::error::Result;
|
||||
use crate::{error::Result, tray::AppToTray};
|
||||
use notify_rust::{Hint, Notification, NotificationHandle};
|
||||
use rog_dbus::{
|
||||
zbus_anime::AnimeProxy, zbus_led::LedProxy, zbus_platform::RogBiosProxy,
|
||||
@@ -10,6 +10,7 @@ use std::{
|
||||
fmt::Display,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::Sender,
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
@@ -73,6 +74,7 @@ pub fn start_notifications(
|
||||
profiles_notified: Arc<AtomicBool>,
|
||||
_fans_notified: Arc<AtomicBool>,
|
||||
notifs_enabled: Arc<AtomicBool>,
|
||||
update_tray: Arc<Mutex<Sender<AppToTray>>>,
|
||||
) -> Result<()> {
|
||||
let last_notification: SharedHandle = Arc::new(Mutex::new(None));
|
||||
|
||||
@@ -227,6 +229,9 @@ pub fn start_notifications(
|
||||
),
|
||||
lock
|
||||
);
|
||||
if let Ok(lock) = update_tray.try_lock() {
|
||||
lock.send(AppToTray::DgpuStatus(*status)).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user