diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4d9636b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.showUnlinkedFileNotification": false +} \ No newline at end of file diff --git a/TODO.md b/TODO.md index bc2788c..f573b16 100644 --- a/TODO.md +++ b/TODO.md @@ -1,2 +1,4 @@ - Close riot client if dolos closes -- Read which games are installed and only show the ones installed \ No newline at end of file +- Read which games are installed and only show the ones installed +- Toggleable statuses +- Fake user \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index b8563a6..bb382b6 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dolos" -version = "0.1.2" +version = "0.1.3" description = "Dolos Desktop Application" default-run = "dolos" license = "GPLv3" @@ -16,7 +16,7 @@ tauri-build = { version = "1.5.0", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.5.2", features = [ "window-close", "updater", "system-tray"] } +tauri = { version = "1.5.2", features = [ "updater", "system-tray", "notification"] } tokio = { version = "1.34.0", features = ["full"] } hyper = { version = "1", features = ["full"] } http-body-util = "0.1" @@ -26,8 +26,7 @@ native-tls = "0.2" tokio-native-tls = "0.3.1" jsonwebtoken = "9.1.0" quick-xml = { version = "0.31.0", features = ["async-tokio"] } -sysinfo = "0.29.11" -winapi = "0.3.9" +winapi = { version = "0.3.9", features = ["tlhelp32"] } [features] custom-protocol = [ "tauri/custom-protocol" ] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9330e7a..cf264dd 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,11 +3,14 @@ mod http_proxy; mod tcp_proxy; mod utils; +mod sys_tray; +mod state; use std::{error::Error, env, fs}; use tauri::Manager; use tokio::{sync::OnceCell, process::Command}; +use tauri::api::notification::Notification; pub static RIOT_CLIENT_PATH: OnceCell = OnceCell::const_new(); @@ -25,39 +28,67 @@ fn main() { let open_pids_c = open_pids.clone(); tauri::Builder::default() - .invoke_handler(tauri::generate_handler![launch_game]) + .manage(state::DolosState::default()) + .invoke_handler(tauri::generate_handler![launch_game, mark_shutdown]) .on_page_load(move |window, _| { - if !open_pids_c.is_empty() { + if open_pids_c.is_some() { window.eval("showRiotClientPopup()").unwrap(); window.eval(&format!("gamesToClose = {}", open_pids_c.iter().count())).unwrap(); } }) + .system_tray(sys_tray::create_tray()) + .on_system_tray_event(sys_tray::handle_event) .setup(|app| { let handle = app.handle(); handle.once_global("closeClients", move |_| { - for (pid, name) in open_pids { - utils::kill_process(pid, name) + if let Some(pids) = open_pids { + for (pid, name) in pids { + utils::kill_process(pid, name) + } } }); - - tauri::async_runtime::spawn(async { - tokio::spawn(tcp_proxy::proxy_tcp_chat()); - tokio::spawn(http_proxy::listen_http()); - }); + tauri::async_runtime::spawn(tcp_proxy::proxy_tcp_chat()); + tauri::async_runtime::spawn(http_proxy::listen_http()); Ok(()) }) - .run(tauri::generate_context!()) - .expect("[Dolos] [Main] error while running tauri application"); + .build(tauri::generate_context!()) + .expect("[Dolos] [Main] error while running tauri application") + .run(|app_handle, event| match event { + tauri::RunEvent::ExitRequested { api, .. } => { + if app_handle.state::().shutdown.load(std::sync::atomic::Ordering::Relaxed) { + app_handle.exit(0); + } else { + api.prevent_exit(); + let _ = Notification::new(&app_handle.config().tauri.bundle.identifier) + .title("Dolos is running") + .body("Dolos is running in the background! View the tray icon for more options.") + .show(); + } + } + _ => {} + }); } #[tauri::command] -fn launch_game(game: &str) { +async fn launch_game(app: tauri::AppHandle, game: String) -> () { Command::new(RIOT_CLIENT_PATH.get().unwrap()) .arg(format!("--client-config-url=http://127.0.0.1:{}", http_proxy::HTTP_PORT.get().unwrap())) .arg(format!("--launch-product={}", game)) .arg("--launch-patchline=live") .spawn() .expect("[Dolos] [Main] Could not launch riot client!"); + + tokio::time::sleep(tokio::time::Duration::from_millis(2500)).await; + app.get_window("main").unwrap().close().unwrap(); + let _ = Notification::new(&app.config().tauri.bundle.identifier) + .title("Dolos is running") + .body("Dolos is running in the background setting your status to offline! View the tray icon for more options.") + .show(); +} + +#[tauri::command] +fn mark_shutdown(state: tauri::State) { + state.shutdown.store(true, std::sync::atomic::Ordering::Relaxed); } \ No newline at end of file diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs new file mode 100644 index 0000000..86f671b --- /dev/null +++ b/src-tauri/src/state.rs @@ -0,0 +1,11 @@ +use std::sync::atomic::AtomicBool; + +pub struct DolosState { + pub shutdown: AtomicBool +} + +impl Default for DolosState { + fn default() -> Self { + Self { shutdown: AtomicBool::new(false) } + } +} \ No newline at end of file diff --git a/src-tauri/src/sys_tray.rs b/src-tauri/src/sys_tray.rs new file mode 100644 index 0000000..9656217 --- /dev/null +++ b/src-tauri/src/sys_tray.rs @@ -0,0 +1,34 @@ +use tauri::{SystemTray, CustomMenuItem, SystemTrayMenu, + // SystemTrayMenuItem, + AppHandle, SystemTrayEvent, + // SystemTraySubmenu +}; + +pub fn create_tray() -> SystemTray { + // let status = SystemTraySubmenu::new("Status", SystemTrayMenu::new().add_item(CustomMenuItem::new("Online".to_string(), "Online").selected())); + let quit = CustomMenuItem::new("quit".to_string(), "Quit"); + let open = CustomMenuItem::new("open".to_string(), "Open"); + let menu = SystemTrayMenu::new() + // .add_submenu(status) + // .add_native_item(SystemTrayMenuItem::Separator) + .add_item(open) + .add_item(quit); + SystemTray::new().with_menu(menu) +} + +pub fn handle_event(app: &AppHandle, event: SystemTrayEvent) { + match event { + SystemTrayEvent::MenuItemClick { id, .. } => { + match id.as_ref() { + "open" => { + tauri::WindowBuilder::from_config(app, app.config().tauri.windows.get(0).unwrap().clone()).build().unwrap(); + }, + "quit" => { + app.exit(0); + }, + _ => {} + } + } + _ => {} + } +} \ No newline at end of file diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index 844d631..a5f6fc1 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -1,6 +1,7 @@ +use std::ffi::CStr; use serde_json::Value; -use sysinfo::{System, SystemExt, ProcessExt, PidExt}; -use winapi::um::{processthreadsapi::{OpenProcess, TerminateProcess}, winnt::PROCESS_ALL_ACCESS, handleapi::CloseHandle, errhandlingapi::GetLastError}; +use winapi::um::{processthreadsapi::{OpenProcess, TerminateProcess}, winnt::PROCESS_ALL_ACCESS, handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, errhandlingapi::GetLastError, tlhelp32::{TH32CS_SNAPPROCESS, PROCESSENTRY32, Process32First, Process32Next}}; +use winapi::um::tlhelp32::CreateToolhelp32Snapshot; const PROCESS_NAMES: [&str; 3] = ["RiotClientServices.exe", "LeagueClient.exe", "VALORANT-Win64-Shipping.exe"]; @@ -16,15 +17,38 @@ pub fn choose_channel(data: Value) -> Option { None } -pub fn get_pids() -> Vec<(u32, String)> { - let sys = System::new_all(); - sys.processes().iter().filter_map(|(pid, process)| { - if PROCESS_NAMES.contains(&process.name()) { - Some((pid.as_u32(), process.name().to_string())) - } else { +pub fn get_pids() -> Option> { + unsafe { + let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if snapshot == INVALID_HANDLE_VALUE { + eprintln!("[DOLOS] Error creating snapshot of processes"); + return None; + } + + let mut pids: Vec<(u32, String)> = vec![]; + let mut pe: PROCESSENTRY32 = std::mem::zeroed(); + pe.dwSize = std::mem::size_of::() as u32; + + if Process32First(snapshot, &mut pe) == 0 { + eprintln!("[DOLOS] Error getting process snapshot"); + CloseHandle(snapshot); + return None; + } + + while Process32Next(snapshot, &mut pe) != 0 { + let process_name = CStr::from_ptr(pe.szExeFile.as_ptr()).to_string_lossy(); + + if PROCESS_NAMES.contains(&process_name.as_ref()) { + pids.push((pe.th32ProcessID, process_name.to_string())); + } + } + CloseHandle(snapshot); + if pids.is_empty() { None + } else { + Some(pids) } - }).collect::>() + } } pub fn kill_process(pid: u32, name: String) { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index eeb34b2..98104a7 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,13 +8,11 @@ }, "package": { "productName": "Dolos", - "version": "0.1.2" + "version": "0.1.3" }, "tauri": { "allowlist": { - "window": { - "close": true - } + "all": false }, "systemTray": { "iconPath": "icons/icon.png", diff --git a/ui/index.html b/ui/index.html index 971be8e..9ceff93 100644 --- a/ui/index.html +++ b/ui/index.html @@ -79,7 +79,14 @@

} document.getElementById('btnNo').addEventListener('click', function() { - hideRiotClientPopup(); + const { invoke } = window.__TAURI__.tauri + invoke('mark_shutdown', {}) + let pElem = document.getElementById("popup-text") + let yElem = document.getElementById("btnYes") + let nElem = document.getElementById("btnNo") + yElem.style.display = 'none'; + nElem.style.display = 'none'; + pElem.textContent = "Dolos must launch the Riot Games launcher with a custom configuration to work. Please close Dolos and stop any Riot Games processes and retry." }); document.getElementById('btnYes').addEventListener('click', function() { @@ -106,7 +113,6 @@

hideRiotClientPopup(); }, 2500) }); - \ No newline at end of file