mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
387 lines
15 KiB
Rust
387 lines
15 KiB
Rust
use std::env::{self, args};
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::exit;
|
|
use std::sync::{Arc, Mutex};
|
|
use std::thread::{self, sleep};
|
|
use std::time::Duration;
|
|
|
|
use config_traits::{StdConfig, StdConfigLoad1};
|
|
use dmi_id::DMIID;
|
|
use gumdrop::Options;
|
|
use log::{debug, info, warn, LevelFilter};
|
|
use rog_control_center::cli_options::CliStart;
|
|
use rog_control_center::config::Config;
|
|
use rog_control_center::error::Result;
|
|
use rog_control_center::notify::start_notifications;
|
|
use rog_control_center::slint::ComponentHandle;
|
|
use rog_control_center::tray::init_tray;
|
|
use rog_control_center::ui::setup_window;
|
|
use rog_control_center::zbus_proxies::{
|
|
AppState, ROGCCZbus, ROGCCZbusProxyBlocking, ZBUS_IFACE, ZBUS_PATH,
|
|
};
|
|
use rog_control_center::{print_versions, MainWindow};
|
|
use tokio::runtime::Runtime;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let mut logger = env_logger::Builder::new();
|
|
logger
|
|
.filter_level(LevelFilter::Warn)
|
|
.parse_default_env()
|
|
.target(env_logger::Target::Stdout)
|
|
.format_timestamp(None)
|
|
.init();
|
|
|
|
// If we're running under gamescope we have to set WAYLAND_DISPLAY for winit to
|
|
// use
|
|
if let Ok(gamescope) = env::var("GAMESCOPE_WAYLAND_DISPLAY") {
|
|
debug!("Gamescope detected");
|
|
if !gamescope.is_empty() {
|
|
debug!("Setting WAYLAND_DISPLAY to {}", gamescope);
|
|
env::set_var("WAYLAND_DISPLAY", gamescope);
|
|
}
|
|
// gamescope-0
|
|
else if let Ok(wayland) = env::var("WAYLAND_DISPLAY") {
|
|
debug!("Wayland display detected");
|
|
if wayland.is_empty() {
|
|
debug!("Setting WAYLAND_DISPLAY to gamescope-0");
|
|
env::set_var("WAYLAND_DISPLAY", "gamescope-0");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try to open a proxy and check for app state first
|
|
{
|
|
let user_con = zbus::blocking::Connection::session()?;
|
|
if let Ok(proxy) = ROGCCZbusProxyBlocking::new(&user_con) {
|
|
if let Ok(state) = proxy.state() {
|
|
info!("App is already running: {state:?}, opening the window");
|
|
// if there is a proxy connection assume the app is already running
|
|
proxy.set_state(AppState::MainWindowShouldOpen)?;
|
|
std::process::exit(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// version checks
|
|
let self_version = env!("CARGO_PKG_VERSION");
|
|
let zbus_con = zbus::blocking::Connection::system()?;
|
|
let platform_proxy = rog_dbus::zbus_platform::PlatformProxyBlocking::new(&zbus_con)?;
|
|
let asusd_version = platform_proxy
|
|
.version()
|
|
.map_err(|e| {
|
|
println!("Could not get asusd version: {e:?}\nIs asusd.service running?");
|
|
})
|
|
.unwrap();
|
|
if asusd_version != self_version {
|
|
println!("Version mismatch: asusctl = {self_version}, asusd = {asusd_version}");
|
|
// return Ok(());
|
|
}
|
|
|
|
// 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();
|
|
|
|
#[cfg(feature = "tokio-debug")]
|
|
console_subscriber::init();
|
|
|
|
let state_zbus = ROGCCZbus::new();
|
|
let app_state = state_zbus.clone_state();
|
|
let _conn = zbus::connection::Builder::session()?
|
|
.name(ZBUS_IFACE)?
|
|
.serve_at(ZBUS_PATH, state_zbus)?
|
|
.build()
|
|
.await
|
|
.map_err(|err| {
|
|
warn!("{}: add_to_server {}", ZBUS_PATH, err);
|
|
err
|
|
})?;
|
|
|
|
let dmi = DMIID::new().unwrap_or_default();
|
|
let board_name = dmi.board_name;
|
|
let prod_family = dmi.product_family;
|
|
info!("Running on {board_name}, product: {prod_family}");
|
|
let is_rog_ally = board_name == "RC71L" || board_name == "RC72L" || prod_family == "ROG Ally";
|
|
|
|
let args: Vec<String> = args().skip(1).collect();
|
|
|
|
let cli_parsed = match CliStart::parse_args_default(&args) {
|
|
Ok(p) => p,
|
|
Err(err) => {
|
|
panic!("source {}", err);
|
|
}
|
|
};
|
|
|
|
if do_cli_help(&cli_parsed) {
|
|
return Ok(());
|
|
}
|
|
|
|
let supported_properties = platform_proxy.supported_properties().unwrap_or_default();
|
|
|
|
// Startup
|
|
let mut config = Config::new().load();
|
|
if cli_parsed.fullscreen {
|
|
config.start_fullscreen = true;
|
|
if cli_parsed.width_fullscreen != 0 {
|
|
config.fullscreen_width = cli_parsed.width_fullscreen;
|
|
}
|
|
if cli_parsed.height_fullscreen != 0 {
|
|
config.fullscreen_height = cli_parsed.height_fullscreen;
|
|
}
|
|
} else if cli_parsed.windowed {
|
|
config.start_fullscreen = false;
|
|
}
|
|
|
|
if is_rog_ally {
|
|
config.notifications.enabled = false;
|
|
config.enable_tray_icon = false;
|
|
config.run_in_background = false;
|
|
config.startup_in_background = false;
|
|
config.start_fullscreen = true;
|
|
}
|
|
|
|
if cli_parsed.tray_mode {
|
|
config.enable_tray_icon = true;
|
|
config.run_in_background = true;
|
|
config.startup_in_background = true;
|
|
}
|
|
|
|
config.write();
|
|
|
|
let enable_tray_icon = config.enable_tray_icon;
|
|
let startup_in_background = config.startup_in_background;
|
|
let config = Arc::new(Mutex::new(config));
|
|
|
|
// shared weak handle to the UI so other threads can request UI updates
|
|
let ui_handle: Arc<Mutex<Option<slint::Weak<MainWindow>>>> = Arc::new(Mutex::new(None));
|
|
|
|
start_notifications(config.clone(), &rt)?;
|
|
|
|
if enable_tray_icon {
|
|
init_tray(supported_properties, config.clone());
|
|
}
|
|
|
|
thread_local! { pub static UI: std::cell::RefCell<Option<MainWindow>> = Default::default()};
|
|
// i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();
|
|
|
|
if !startup_in_background {
|
|
if let Ok(mut app_state) = app_state.lock() {
|
|
*app_state = AppState::MainWindowShouldOpen;
|
|
}
|
|
}
|
|
|
|
if std::env::var("RUST_TRANSLATIONS").is_ok() {
|
|
// don't care about content
|
|
log::debug!("---- Using local-dir translations");
|
|
slint::init_translations!("/usr/share/locale/");
|
|
} else {
|
|
log::debug!("Using system installed translations");
|
|
slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/translations/"));
|
|
}
|
|
|
|
let config_for_ui = config.clone();
|
|
let ui_handle_for_thread = ui_handle.clone();
|
|
thread::spawn(move || {
|
|
let mut state = AppState::StartingUp;
|
|
loop {
|
|
if is_rog_ally {
|
|
let config_copy_2 = config_for_ui.clone();
|
|
let newui = setup_window(config_for_ui.clone());
|
|
newui.window().on_close_requested(move || {
|
|
exit(0);
|
|
});
|
|
|
|
let ui_copy = newui.as_weak();
|
|
newui
|
|
.window()
|
|
.set_rendering_notifier(move |s, _| {
|
|
if let slint::RenderingState::BeforeRendering = s {
|
|
let config = config_copy_2.clone();
|
|
ui_copy
|
|
.upgrade_in_event_loop(move |w| {
|
|
let fullscreen =
|
|
config.lock().is_ok_and(|c| c.start_fullscreen);
|
|
if fullscreen && !w.window().is_fullscreen() {
|
|
w.window().set_fullscreen(fullscreen);
|
|
}
|
|
})
|
|
.ok();
|
|
}
|
|
})
|
|
.ok();
|
|
} else {
|
|
// save as a var, don't hold the lock the entire time or deadlocks happen
|
|
if let Ok(app_state) = app_state.lock() {
|
|
state = *app_state;
|
|
}
|
|
|
|
// This sleep is required to give the event loop time to react
|
|
sleep(Duration::from_millis(300));
|
|
if state == AppState::MainWindowShouldOpen {
|
|
if let Ok(mut app_state) = app_state.lock() {
|
|
*app_state = AppState::MainWindowOpen;
|
|
}
|
|
|
|
let config_copy = config_for_ui.clone();
|
|
let app_state_copy = app_state.clone();
|
|
let ui_handle_for_ui = ui_handle_for_thread.clone();
|
|
slint::invoke_from_event_loop(move || {
|
|
let ui_handle_for_ui = ui_handle_for_ui.clone();
|
|
UI.with(|ui| {
|
|
let app_state_copy = app_state_copy.clone();
|
|
let mut ui = ui.borrow_mut();
|
|
if let Some(ui) = ui.as_mut() {
|
|
// store weak handle so other threads can update UI globals
|
|
if let Ok(mut h) = ui_handle_for_ui.lock() {
|
|
*h = Some(ui.as_weak());
|
|
}
|
|
ui.window().show().unwrap();
|
|
ui.window().on_close_requested(move || {
|
|
if let Ok(mut app_state) = app_state_copy.lock() {
|
|
*app_state = AppState::MainWindowClosed;
|
|
}
|
|
slint::CloseRequestResponse::HideWindow
|
|
});
|
|
} else {
|
|
let config_copy_2 = config_copy.clone();
|
|
let newui = setup_window(config_copy);
|
|
// store weak handle for the newly created UI
|
|
if let Ok(mut h) = ui_handle_for_ui.lock() {
|
|
*h = Some(newui.as_weak());
|
|
}
|
|
newui.window().on_close_requested(move || {
|
|
if let Ok(mut app_state) = app_state_copy.lock() {
|
|
*app_state = AppState::MainWindowClosed;
|
|
}
|
|
slint::CloseRequestResponse::HideWindow
|
|
});
|
|
|
|
let ui_copy = newui.as_weak();
|
|
newui
|
|
.window()
|
|
.set_rendering_notifier(move |s, _| {
|
|
if let slint::RenderingState::RenderingSetup = s {
|
|
let config = config_copy_2.clone();
|
|
ui_copy
|
|
.upgrade_in_event_loop(move |w| {
|
|
let fullscreen = config
|
|
.lock()
|
|
.is_ok_and(|c| c.start_fullscreen);
|
|
if fullscreen && !w.window().is_fullscreen() {
|
|
w.window().set_fullscreen(fullscreen);
|
|
}
|
|
})
|
|
.ok();
|
|
}
|
|
})
|
|
.ok();
|
|
ui.replace(newui);
|
|
}
|
|
});
|
|
})
|
|
.unwrap();
|
|
} else if state == AppState::QuitApp {
|
|
slint::quit_event_loop().unwrap();
|
|
exit(0);
|
|
} else if state != AppState::MainWindowOpen {
|
|
if let Ok(cfg) = config_for_ui.lock() {
|
|
if !cfg.run_in_background {
|
|
slint::quit_event_loop().unwrap();
|
|
exit(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// start config watcher to pick up external edits
|
|
spawn_config_watcher(config.clone(), ui_handle.clone());
|
|
|
|
slint::run_event_loop_until_quit().unwrap();
|
|
rt.shutdown_background();
|
|
Ok(())
|
|
}
|
|
|
|
// Spawn a watcher thread that polls the config file and reloads it when modified.
|
|
// This keeps the running rogcc instance in sync with manual edits to the config file.
|
|
fn spawn_config_watcher(
|
|
config: Arc<Mutex<Config>>,
|
|
ui_handle: Arc<Mutex<Option<slint::Weak<MainWindow>>>>,
|
|
) {
|
|
std::thread::spawn(move || {
|
|
use std::time::SystemTime;
|
|
let cfg_path = Config::config_dir().join(Config::new().file_name());
|
|
let mut last_mtime: Option<SystemTime> = None;
|
|
loop {
|
|
if let Ok(meta) = std::fs::metadata(&cfg_path) {
|
|
if let Ok(m) = meta.modified() {
|
|
if last_mtime.is_none() {
|
|
last_mtime = Some(m);
|
|
} else if last_mtime.is_some_and(|t| t < m) {
|
|
// file changed, reload
|
|
last_mtime = Some(m);
|
|
let new_cfg = Config::new().load();
|
|
if let Ok(mut lock) = config.lock() {
|
|
*lock = new_cfg.clone();
|
|
}
|
|
|
|
// Update UI app settings globals if UI is present
|
|
if let Ok(maybe_weak) = ui_handle.lock() {
|
|
if let Some(weak) = maybe_weak.clone() {
|
|
let config_for_ui = config.clone();
|
|
weak.upgrade_in_event_loop(move |w| {
|
|
if let Ok(lock) = config_for_ui.lock() {
|
|
let cfg = lock.clone();
|
|
w.global::<rog_control_center::AppSettingsPageData>()
|
|
.set_run_in_background(cfg.run_in_background);
|
|
w.global::<rog_control_center::AppSettingsPageData>()
|
|
.set_startup_in_background(cfg.startup_in_background);
|
|
w.global::<rog_control_center::AppSettingsPageData>()
|
|
.set_enable_tray_icon(cfg.enable_tray_icon);
|
|
w.global::<rog_control_center::AppSettingsPageData>()
|
|
.set_enable_dgpu_notifications(
|
|
cfg.notifications.enabled,
|
|
);
|
|
}
|
|
})
|
|
.ok();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
std::thread::sleep(std::time::Duration::from_secs(2));
|
|
}
|
|
});
|
|
}
|
|
|
|
fn do_cli_help(parsed: &CliStart) -> bool {
|
|
if parsed.help {
|
|
println!("{}", CliStart::usage());
|
|
println!();
|
|
if let Some(cmdlist) = CliStart::command_list() {
|
|
let commands: Vec<String> = cmdlist.lines().map(|s| s.to_owned()).collect();
|
|
for command in &commands {
|
|
println!("{}", command);
|
|
}
|
|
}
|
|
}
|
|
|
|
if parsed.version {
|
|
print_versions();
|
|
println!();
|
|
}
|
|
|
|
parsed.help
|
|
}
|
|
|
|
pub fn get_layout_path(path: &Path, layout_name: &str) -> PathBuf {
|
|
let mut data_path = PathBuf::from(path);
|
|
let layout_file = format!("{}_US.ron", layout_name);
|
|
data_path.push("layouts");
|
|
data_path.push(layout_file);
|
|
data_path
|
|
}
|