diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 6be5e50..45e06ee 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index e81bece..d0318f1 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index a437dd5..8be24a7 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/CS工具箱#FULL.png b/src-tauri/icons/CS工具箱#FULL.png new file mode 100644 index 0000000..6ae356d Binary files /dev/null and b/src-tauri/icons/CS工具箱#FULL.png differ diff --git a/src-tauri/icons/CS工具箱#无圆角.png b/src-tauri/icons/CS工具箱#无圆角.png new file mode 100644 index 0000000..da6af62 Binary files /dev/null and b/src-tauri/icons/CS工具箱#无圆角.png differ diff --git a/src-tauri/icons/CS工具箱.png b/src-tauri/icons/CS工具箱.png new file mode 100644 index 0000000..439a2e3 Binary files /dev/null and b/src-tauri/icons/CS工具箱.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index 0ca4f27..87cf837 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index b81f820..3c7ed8b 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index 624c7bf..29413d2 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index c021d2b..1235fb9 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 6219700..073d2c1 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index f9bc048..ae487f2 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index d5fbfb2..3422cf0 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 63440d7..785f6c9 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index f3f705a..d3a8254 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index 4556388..0ce9545 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/icon-tray.png b/src-tauri/icons/icon-tray.png new file mode 100644 index 0000000..4d2a200 Binary files /dev/null and b/src-tauri/icons/icon-tray.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 87f09b4..035d64c 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index b3636e4..0dae901 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index e1cd261..bc62c60 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs new file mode 100644 index 0000000..7f1a8f3 --- /dev/null +++ b/src-tauri/src/cmds.rs @@ -0,0 +1,57 @@ +use crate::steam; +use crate::tool::*; + +#[tauri::command] +pub fn greet(name: &str) -> String { + format!("Hello, {}! You've been greeted from Rust!", name) +} + +// 工具 + +#[tauri::command] +pub fn launch_game(steam_path: &str, launch_option: &str, server: &str) -> String { + steam::launch_game(steam_path, launch_option, server).expect("启动失败"); + + format!( + "Launching game on server: {}, with launch Option {}", + server, launch_option + ) +} + +#[tauri::command] +pub fn kill_game() -> String { + common::kill("cs2.exe") +} + +#[tauri::command] +pub fn kill_steam() -> String { + common::kill("steam.exe") +} + +// Steam +#[tauri::command] +pub fn get_steam_path() -> String { + steam::path::get_steam_path().expect("获取Steam路径失败") +} + +// TODO get_cs_path +// TODO get_powerplan +// TODO set_powerplan +// TODO watch_steam_users +// TODO watch_video_settings +// TODO watch + cancel +// TODO fs_list_dir +// TODO fs_watch_dir + +#[tauri::command] +pub fn set_auto_login_user(user: &str) -> String { + #[cfg(target_os = "windows")] + steam::reg::set_auto_login_user(user).expect("设置自动登录用户失败"); + + format!("Set auto login user to {}", user) +} + +#[tauri::command] +pub fn check_file_exists(file: &str) -> bool { + std::path::Path::new(&file).exists() +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4eebecc..2e934cc 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,9 +2,31 @@ all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows" )] +use tauri::Manager; +// Window Vibrancy +#[cfg(target_os = "windows")] +use window_vibrancy::apply_mica; +#[cfg(target_os = "macos")] +use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial}; + +// System Tray +use tauri::{ + menu::{MenuBuilder, MenuItemBuilder}, + tray::{ClickType, TrayIconBuilder}, +}; + +// Auto Start +use tauri_plugin_autostart::MacosLauncher; + +// use tauri_plugin_autostart::ManagerExt; use std::time::{SystemTime, UNIX_EPOCH}; +// Local Modules +mod cmds; +mod steam; +mod tool; + #[tauri::command] fn on_button_clicked() -> String { let start = SystemTime::now(); @@ -17,7 +39,109 @@ fn on_button_clicked() -> String { fn main() { tauri::Builder::default() - .invoke_handler(tauri::generate_handler![on_button_clicked]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_os::init()) + .plugin(tauri_plugin_autostart::init( + MacosLauncher::LaunchAgent, + Some(vec![]), + )) + .plugin(tauri_plugin_process::init()) + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_store::Builder::default().build()) + // .plugin(tauri_plugin_updater::Builder::new().build()) + .invoke_handler(tauri::generate_handler![on_button_clicked]) + .setup(|app| { + // Tray Menu + // let quit = CustomMenuItem::new("quit".to_string(), "Quit"); + // let hide = CustomMenuItem::new("hide".to_string(), "Hide"); + // let tray_menu = SystemTrayMenu::new() // insert the menu items here + // .add_item(hide) + // .add_item(quit); + // .add_native_item(SystemTrayMenuItem::Separator) + let toggle = MenuItemBuilder::with_id("toggle", "Toggle").build(app)?; + let menu = MenuBuilder::new(app).items(&[&toggle]).build()?; + + // Setup Tray + // let tray = tauri::tray::TrayIconBuilder::with_id("my-tray").build(app)?; + let _ = TrayIconBuilder::new() + .menu(&menu) + .on_menu_event(move |_, event| { + match event.id().as_ref() { + "toggle" => { + println!("toggle clicked"); + } + _ => (), + } + // match event { + // SystemTrayEvent::LeftClick { position: _, size: _, .. } => { + // let window = app.get_window("main").unwrap(); + // window.show().unwrap(); + // window.set_focus().unwrap(); + + // // thread::sleep(Duration::from_millis(100)); + // // window.set_always_on_top(false).unwrap(); + // println!("system tray received a left click"); + // } + // SystemTrayEvent::RightClick { position: _, size: _, .. } => { + // // let window = app.get_window("main").unwrap(); + // // window.hide().unwrap(); + // println!("system tray received a right click"); + // } + // SystemTrayEvent::DoubleClick { position: _, size: _, .. } => { + // println!("system tray received a double click"); + // } + // SystemTrayEvent::MenuItemClick { id, .. } => + // match id.as_str() { + // "quit" => { + // std::process::exit(0); + // } + // "hide" => { + // let window = app.get_window("main").unwrap(); + // window.hide().unwrap(); + // } + // _ => {} + // } + // _ => {} + // } + }) + .on_tray_icon_event(|tray, event| { + if event.click_type == ClickType::Left { + let app = tray.app_handle(); + if let Some(webview_window) = app.get_webview_window("main") { + let _ = webview_window.show(); + let _ = webview_window.set_focus(); + } + } + }) + .build(app) + .unwrap(); + + // Get Window + let window = app.get_webview_window("main").unwrap(); + + // Vibrant Window + #[cfg(target_os = "macos")] + apply_vibrancy(&window, NSVisualEffectMaterial::HudWindow, None, Some(10.0)) + .expect("Unsupported platform! 'apply_vibrancy' is only supported on macOS"); + + #[cfg(target_os = "windows")] + apply_mica(&window, Some(false)) + .expect("Unsupported platform! 'apply_mica' is only supported on Windows"); + + // apply_blur(&window, Some((18, 18, 18, 0))) + // .expect("Unsupported platform! 'apply_blur' is only supported on Windows"); + + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + cmds::greet, + cmds::launch_game, + cmds::kill_game, + cmds::kill_steam, + cmds::get_steam_path, + cmds::set_auto_login_user, + cmds::check_file_exists + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } diff --git a/src-tauri/src/steam/id.rs b/src-tauri/src/steam/id.rs new file mode 100644 index 0000000..5f73839 --- /dev/null +++ b/src-tauri/src/steam/id.rs @@ -0,0 +1,41 @@ +// Steam users, including: +// - ID Conversion +#![allow(unused)] + +static ID_BASE: u64 = 76561197960265728; + +pub fn id64_to_32(id64: u64) -> u32 { + (id64 - ID_BASE) as u32 +} + +pub fn id32_to_64(id32: u32) -> u64 { + (id32 as u64) + ID_BASE +} + +#[cfg(test)] +mod tests { + use super::*; + + const ID32: [&str; 2] = ["354813078", "146859713"]; + const ID64: [&str; 2] = ["76561198315078806", "76561198107125441"]; + + #[test] + fn test_id32_to_64() { + for (i32, i64) in ID32.iter().zip(ID64.iter()) { + assert_eq!( + id32_to_64(i32.parse::().unwrap()), + i64.parse::().unwrap() + ); + } + } + + #[test] + fn test_id64_to_32() { + for (i32, i64) in ID32.iter().zip(ID64.iter()) { + assert_eq!( + id64_to_32(i64.parse::().unwrap()), + i32.parse::().unwrap() + ); + } + } +} diff --git a/src-tauri/src/steam/mod.rs b/src-tauri/src/steam/mod.rs new file mode 100644 index 0000000..6a10cc2 --- /dev/null +++ b/src-tauri/src/steam/mod.rs @@ -0,0 +1,38 @@ +// Manage sub modules +pub mod id; +pub mod path; +pub mod reg; + +// common steam utils +use std::error::Error; +use std::process::{Command, Output}; + +pub fn launch_game( + steam_path: &str, + launch_option: &str, + server: &str, +) -> Result<(), Box> { + let mut opt = launch_option.replace("\n", " "); + + if server == "perfectworld" { + opt = opt.replace("-worldwide", "") + " -perfectworld"; + } else if server == "worldwide" { + opt = opt.replace("-perfectworld", "") + " -worldwide"; + } + + let opts = format!("-applaunch 730 {}", opt); + let opts_split = opts.split_whitespace().collect::>(); + + let output: Output = Command::new(steam_path).args(opts_split).output()?; + + if !output.status.success() { + let error_message = String::from_utf8_lossy(&output.stderr); + println!("Error: {}", error_message); + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to launch game", + ))); + } + + Ok(()) +} diff --git a/src-tauri/src/steam/path.rs b/src-tauri/src/steam/path.rs new file mode 100644 index 0000000..9e6a6dd --- /dev/null +++ b/src-tauri/src/steam/path.rs @@ -0,0 +1,48 @@ +// Steam related path utils, including: +// - Steam Path +// - CS2(CS:GO) Path +#![allow(unused)] + +use crate::tool::common::get_exe_path; + +use super::reg; + +pub fn get_steam_path<'a>() -> Result { + // Windows Registry + #[cfg(target_os = "windows")] + if let Ok(reg) = reg::SteamReg::get_all() { + return Ok(reg.steam_path); + } + + // Running steam.exe + #[cfg(target_os = "windows")] + if let Ok(steam_path) = get_exe_path("steam.exe") { + return Ok(steam_path); + } + + Err("no steam path found") +} + +pub fn get_cs_path<'a>(name: &str) -> Result { + if name != "csgo" || name != "cs2" { + return Err("invalid cs name"); + } + + #[cfg(target_os = "windows")] + if let Ok(cs_path) = get_exe_path(&(name.to_owned() + ".exe")) { + return Ok(cs_path); + } + + Err("no cs path found") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_steam_path() { + let path = get_steam_path().unwrap(); + println!("{}", path); + } +} diff --git a/src-tauri/src/steam/reg.rs b/src-tauri/src/steam/reg.rs new file mode 100644 index 0000000..ac36165 --- /dev/null +++ b/src-tauri/src/steam/reg.rs @@ -0,0 +1,101 @@ +// Steam metadata parsing from reg, including: +// - Steam Path +// - Users (ID32) +#![allow(unused)] +use crate::tool::common::kill; +use crate::tool::common::run_steam; +use std::process::Command; + +#[cfg(target_os = "windows")] +use winreg::enums::*; +#[cfg(target_os = "windows")] +use winreg::RegKey; +#[cfg(target_os = "windows")] +static STEAM_REG: &str = r"Software\Valve\Steam"; + +#[derive(Debug)] +pub struct SteamReg { + pub steam_path: String, + pub auto_login_user: String, + pub suppress_auto_run: u32, + pub remember_password: u32, + pub language: String, + pub users: Vec, +} + +impl SteamReg { + #[cfg(target_os = "windows")] + pub fn get_all<'a>() -> Result { + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let cur_ver = hkcu.open_subkey(STEAM_REG).expect("steam reg"); + + let steam_path: String = cur_ver.get_value("SteamExe").expect("steam path"); + let auto_login_user: String = cur_ver.get_value("AutoLoginUser").expect("auto login user"); + let suppress_auto_run: u32 = cur_ver + .get_value("SuppressAutoRun") + .expect("suppress auto run"); + let remember_password: u32 = cur_ver + .get_value("RememberPassword") + .expect("remember password"); + let language: String = cur_ver.get_value("Language").expect("language"); + + let users = cur_ver + .open_subkey("Users") + .expect("users") + .enum_keys() + .map(|x| x.unwrap().to_string()) + .collect::>(); + + Ok(Self { + steam_path, + auto_login_user, + suppress_auto_run, + remember_password, + language, + users: users, + }) + } +} + +#[cfg(target_os = "windows")] +pub fn set_auto_login_user(user: &str) -> Result<(), &str> { + // ensure steam.exe is not running + kill("steam.exe"); + + // set reg key value + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + let cur_ver = hkcu + .open_subkey_with_flags(STEAM_REG, KEY_WRITE) + .expect("steam reg"); + + cur_ver + .set_value("AutoLoginUser", &user) + .expect("auto login user"); + cur_ver + .set_value("RememberPassword", &1u32) + .expect("remember passwd"); + + // open steam.exe again + let output = run_steam().expect("failed to execute process"); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(target_os = "windows")] + fn test_get_steam_path() { + let path = SteamReg::get_all().unwrap(); + println!("{:?}", path); + } + + #[test] + #[cfg(target_os = "windows")] + fn test_set_auto_login() { + // set_auto_login_user("_im_ai_"); + set_auto_login_user("_jerry_dota2"); + } +} diff --git a/src-tauri/src/tool/common.rs b/src-tauri/src/tool/common.rs new file mode 100644 index 0000000..edd418e --- /dev/null +++ b/src-tauri/src/tool/common.rs @@ -0,0 +1,35 @@ +use std::process::Command; + +pub fn kill(name: &str) -> String { + Command::new("taskkill") + .args(&["/IM", name, "/F"]) + .output() + .unwrap(); + + format!("Killing {}", name) +} + +pub fn run_steam() -> std::io::Result { + Command::new("cmd") + .args(&["/C", "start", "steam://run"]) + .output() +} + +pub fn get_exe_path(name: &str) -> Result { + let command = format!("/C wmic process where name='{}' get ExecutablePath", name); + let args = command.split_whitespace().collect::>(); + let output = Command::new("cmd.exe").args(&args).output()?; + + let out = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + if out.contains("ExecutablePath") { + let spt: Vec<&str> = out.split("\r\n").collect(); + if spt.len() >= 2 { + return Ok(spt[1].to_string()); + } + } + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to get executable path", + )) +} diff --git a/src-tauri/src/tool/macros.rs b/src-tauri/src/tool/macros.rs new file mode 100644 index 0000000..b6b7e28 --- /dev/null +++ b/src-tauri/src/tool/macros.rs @@ -0,0 +1,44 @@ +#[macro_export] +macro_rules! error { + ($result: expr) => { + log::error!(target: "app", "{}", $result); + }; +} + +#[macro_export] +macro_rules! log_err { + ($result: expr) => { + if let Err(err) = $result { + log::error!(target: "app", "{err}"); + } + }; + + ($result: expr, $err_str: expr) => { + if let Err(_) = $result { + log::error!(target: "app", "{}", $err_str); + } + }; +} + +#[macro_export] +macro_rules! trace_err { + ($result: expr, $err_str: expr) => { + if let Err(err) = $result { + log::trace!(target: "app", "{}, err {}", $err_str, err); + } + } +} + +/// transform the error to String +#[macro_export] +macro_rules! wrap_err { + ($stat: expr) => { + match $stat { + Ok(a) => Ok(a), + Err(err) => { + log::error!(target: "app", "{}", err.to_string()); + Err(format!("{}", err.to_string())) + } + } + }; +} diff --git a/src-tauri/src/tool/mod.rs b/src-tauri/src/tool/mod.rs new file mode 100644 index 0000000..edeeeb5 --- /dev/null +++ b/src-tauri/src/tool/mod.rs @@ -0,0 +1,2 @@ +pub mod common; +pub mod macros; diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 93b344f..87644de 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -57,9 +57,16 @@ { "fullscreen": false, "resizable": true, - "title": "Tauri NextJS App", - "width": 1280, - "height": 800 + "title": "CS Toolbox", + "width": 976, + "height": 720, + "minWidth": 600, + "minHeight": 480, + "decorations": false, + "transparent": true, + "theme": "Light", + "hiddenTitle": true, + "titleBarStyle": "Overlay" } ] }