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<()> { // Ensure tracing spans are quiet by default unless user overrides if std::env::var_os("RUST_LOG").is_none() { std::env::set_var("RUST_LOG", "warn,tracing=error,zbus=error"); } let mut logger = env_logger::Builder::new(); logger .parse_default_env() .filter_level(LevelFilter::Info) .parse_default_env() .target(env_logger::Target::Stderr) .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 args: Vec = 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; } let is_rog_ally = { #[cfg(feature = "rog_ally")] { board_name == "RC71L" || board_name == "RC72L" || prod_family == "ROG Ally" } #[cfg(not(feature = "rog_ally"))] { false } }; #[cfg(feature = "rog_ally")] 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; } 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)); start_notifications(config.clone(), &rt)?; if enable_tray_icon { init_tray(supported_properties, config.clone()); } thread_local! { pub static UI: std::cell::RefCell> = 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/")); } thread::spawn(move || { let mut state = AppState::StartingUp; loop { if is_rog_ally { let config_copy_2 = config.clone(); let newui = setup_window(config.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(); continue; } // 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.clone(); let app_state_copy = app_state.clone(); slint::invoke_from_event_loop(move || { UI.with(|ui| { let app_state_copy = app_state_copy.clone(); let mut ui = ui.borrow_mut(); if let Some(ui) = ui.as_mut() { 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); 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(config) = config.lock() { if !config.run_in_background { slint::quit_event_loop().unwrap(); exit(0); } } } } }); slint::run_event_loop_until_quit().unwrap(); rt.shutdown_background(); Ok(()) } fn do_cli_help(parsed: &CliStart) -> bool { if parsed.help { println!("{}", CliStart::usage()); println!(); if let Some(cmdlist) = CliStart::command_list() { let commands: Vec = 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 }