diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2cb0128..e2e13ae 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -8,6 +8,7 @@ version = "0.0.6" dependencies = [ "anyhow", "base64 0.22.1", + "dirs 6.0.0", "futures-util", "log", "notify", @@ -1289,18 +1290,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "filetime" -version = "0.2.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.60.2", -] - [[package]] name = "find-msvc-tools" version = "0.1.4" @@ -2228,11 +2217,11 @@ dependencies = [ [[package]] name = "inotify" -version = "0.9.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.10.0", "inotify-sys", "libc", ] @@ -2492,7 +2481,6 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall", ] [[package]] @@ -2635,18 +2623,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - [[package]] name = "mio" version = "1.1.0" @@ -2654,6 +2630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", + "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -2785,21 +2762,20 @@ dependencies = [ [[package]] name = "notify" -version = "6.1.1" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ "bitflags 2.10.0", - "crossbeam-channel", - "filetime", "fsevent-sys", "inotify", "kqueue", "libc", "log", - "mio 0.8.11", + "mio", + "notify-types", "walkdir", - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] @@ -2816,6 +2792,12 @@ dependencies = [ "zbus", ] +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + [[package]] name = "ntapi" version = "0.4.1" @@ -5544,7 +5526,7 @@ checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "bytes", "libc", - "mio 1.1.0", + "mio", "pin-project-lite", "signal-hook-registry", "socket2", @@ -6527,15 +6509,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -6587,21 +6560,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -6659,12 +6617,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6683,12 +6635,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -6707,12 +6653,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6743,12 +6683,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -6767,12 +6701,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -6791,12 +6719,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6815,12 +6737,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d1ecd7c..e238379 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -52,7 +52,8 @@ tauri-plugin-autostart = "2.5.1" tauri-plugin-single-instance = { version = "2.3.6", features = ["deep-link"] } tauri-plugin-deep-link = "2.4.5" anyhow = "1.0.100" -notify = "6.1.1" +notify = "8.2.0" +dirs = "6.0.0" [target.'cfg(windows)'.dependencies] # Windows Only winreg = "0.55.0" diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index c051c1d..075fb06 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -111,6 +111,21 @@ pub fn start_watch_loginusers(app: tauri::AppHandle, steam_dir: String) -> Resul wrap_err!(steam::watch::start_watch_loginusers(app, steam_dir)) } +#[tauri::command] +pub fn start_watch_cs2_video( + app: tauri::AppHandle, + steam_dir: String, + steam_id32: u32, +) -> Result<(), String> { + wrap_err!(steam::watch::start_watch_cs2_video(app, steam_dir, steam_id32)) +} + +#[tauri::command] +pub fn stop_watch_cs2_video() -> Result<(), String> { + steam::watch::stop_watch_cs2_video(); + Ok(()) +} + #[tauri::command] pub fn get_cs2_video_config(steam_dir: &str, steam_id32: u32) -> Result { let p = format!( diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0c4e02e..a13e5a4 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,6 +3,8 @@ windows_subsystem = "windows" )] +use std::thread; +use std::time::Duration; use tauri::Manager; use tauri_plugin_autostart::MacosLauncher; use tauri_plugin_cli::CliExt; @@ -38,17 +40,22 @@ fn on_button_clicked() -> String { } fn main() { + // 获取应用上下文 + let ctx = tauri::generate_context!(); + + // 手动构建 AppConfig 目录路径 + let config_dir = dirs::config_dir().expect("无法获取配置目录"); + let app_name = ctx.config().identifier.as_str(); + let store_dir = config_dir.join(app_name).join("cstb"); + tauri::Builder::default() .plugin(tauri_plugin_single_instance::init(|app, _, _| { - let window = app - .get_webview_window("main") - .expect("no main window"); + let window = app.get_webview_window("main").expect("no main window"); - window.show().expect("no main window, can't show"); + window.show().expect("no main window, can't show"); window.set_focus().expect("no main window, can't set focus") })) .plugin(tauri_plugin_deep_link::init()) - .plugin(tauri_plugin_valtio::init()) .plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_autostart::init( @@ -65,24 +72,41 @@ fn main() { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_system_info::init()) .plugin(tauri_plugin_cli::init()) - // .plugin(tauri_plugin_store::Builder::default().build()) + // .plugin(tauri_plugin_valtio::init()) + .plugin(tauri_plugin_valtio::Builder::new().path(&store_dir).build()) // .plugin(tauri_plugin_updater::Builder::new().build()) - .setup(|app| { + .setup(move |app| { // Get Window let window = app.get_webview_window("main").unwrap(); - let store = app.store("cstb.json")?; - // 获取boolean类型的hidden值,Err时设置为False - let hidden: bool = store.get("hidden").and_then(|v| v.as_bool()).unwrap_or(false); + let store = app.store("cstb.json")?; + // 获取boolean类型的hidden值,Err时设置为False + let hidden: bool = store + .get("hidden") + .and_then(|v| v.as_bool()) + .unwrap_or(false); - // Vibrant Window + // 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"); + { + if let Err(e) = + apply_vibrancy(&window, NSVisualEffectMaterial::HudWindow, None, Some(10.0)) + { + eprintln!("Failed to apply vibrancy effect: {:?}", e); + } + } #[cfg(target_os = "windows")] - apply_acrylic(&window, None) - .expect("Unsupported platform! 'apply_acrylic' is only supported on Windows"); + { + // 延迟应用 acrylic 效果,确保窗口完全初始化 + let window_handle = window.clone(); + thread::spawn(move || { + thread::sleep(Duration::from_millis(100)); + if let Err(e) = apply_acrylic(&window_handle, None) { + eprintln!("Failed to apply acrylic effect: {:?}", e); + } + }); + } // apply_blur(&window, Some((18, 18, 18, 0))) // .expect("Unsupported platform! 'apply_blur' is only supported on Windows"); @@ -105,7 +129,10 @@ fn main() { // `subcommand` is `Option>` where `SubcommandMatches` is a struct with { name, matches }. Ok(matches) => { println!("{:?}", matches); - if matches.args.contains_key("hidden") && matches.args["hidden"].value == true && hidden { + if matches.args.contains_key("hidden") + && matches.args["hidden"].value == true + && hidden + { window.hide().unwrap(); } else { window.show().unwrap(); @@ -130,10 +157,12 @@ fn main() { cmds::get_steam_users, cmds::set_auto_login_user, cmds::start_watch_loginusers, - cmds::get_cs2_video_config, - cmds::set_cs2_video_config, + cmds::start_watch_cs2_video, + cmds::stop_watch_cs2_video, + cmds::get_cs2_video_config, + cmds::set_cs2_video_config, cmds::check_path, - cmds::analyze_replay, + cmds::analyze_replay, cmds::get_console_log_path, cmds::read_vprof_report, cmds::check_app_update, @@ -141,6 +170,6 @@ fn main() { cmds::install_app_update, on_button_clicked ]) - .run(tauri::generate_context!()) + .run(ctx) .expect("error while running tauri application"); } diff --git a/src-tauri/src/steam/watch.rs b/src-tauri/src/steam/watch.rs index de4823d..32adcf0 100644 --- a/src-tauri/src/steam/watch.rs +++ b/src-tauri/src/steam/watch.rs @@ -6,6 +6,7 @@ use tauri::{AppHandle, Emitter}; // 全局 watcher 存储,用于管理文件监听器的生命周期 static WATCHER: OnceLock>> = OnceLock::new(); +static CS2_VIDEO_WATCHER: OnceLock>> = OnceLock::new(); /// 启动监听 loginusers.vdf 文件的变化 pub fn start_watch_loginusers(app: AppHandle, steam_dir: String) -> Result<()> { @@ -74,3 +75,74 @@ pub fn stop_watch_loginusers() { } } +/// 启动监听 cs2_video.txt 文件的变化 +pub fn start_watch_cs2_video(app: AppHandle, steam_dir: String, steam_id32: u32) -> Result<()> { + // 停止之前的监听 + stop_watch_cs2_video(); + + let cfg_dir = Path::new(&steam_dir) + .join("userdata") + .join(steam_id32.to_string()) + .join("730") + .join("local") + .join("cfg"); + + // 如果 cfg 目录不存在,不进行监听 + if !cfg_dir.exists() { + log::warn!("cs2_video.txt 配置目录不存在,跳过监听: {:?}", cfg_dir); + return Ok(()); + } + + let app_clone = app.clone(); + + let mut watcher = RecommendedWatcher::new( + move |result: Result| { + match result { + Ok(event) => { + // 检查是否是 cs2_video.txt 文件的变化 + if let EventKind::Modify(_) | EventKind::Create(_) = event.kind { + for path in &event.paths { + if path.ends_with("cs2_video.txt") { + log::info!("检测到 cs2_video.txt 文件变化: {:?}", path); + // 延迟一小段时间,确保文件写入完成 + std::thread::sleep(std::time::Duration::from_millis(200)); + // 发送事件到前端 + if let Err(e) = app_clone.emit("steam://cs2_video_changed", ()) { + log::error!("发送 cs2_video 变化事件失败: {}", e); + } + break; + } + } + } + } + Err(e) => { + log::error!("文件监听错误: {}", e); + } + } + }, + Config::default(), + )?; + + // 监听 cfg 目录(而不是单个文件),因为某些文件系统可能不会直接监听单个文件 + watcher.watch(&cfg_dir, RecursiveMode::NonRecursive)?; + log::info!("开始监听 cs2_video.txt 文件: {:?}", cfg_dir.join("cs2_video.txt")); + + // 保存 watcher 到全局变量 + let watcher_store = CS2_VIDEO_WATCHER.get_or_init(|| Mutex::new(None)); + *watcher_store.lock().unwrap() = Some(watcher); + + Ok(()) +} + +/// 停止监听 cs2_video.txt 文件 +pub fn stop_watch_cs2_video() { + if let Some(watcher_store) = CS2_VIDEO_WATCHER.get() { + if let Ok(mut watcher_guard) = watcher_store.lock() { + if let Some(watcher) = watcher_guard.take() { + drop(watcher); + log::info!("已停止监听 cs2_video.txt 文件"); + } + } + } +} + diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index 84bc850..cd2ba8c 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -1,11 +1,18 @@ use tauri::{ - menu::{CheckMenuItem, Menu, MenuItem, PredefinedMenuItem}, + menu::{CheckMenuItem, Menu, MenuItem, PredefinedMenuItem, Submenu}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, Emitter, Listener, Manager, Runtime, }; +use serde::{Deserialize, Serialize}; use crate::tool::powerplan::PowerPlanMode; +#[derive(Debug, Clone, Serialize, Deserialize)] +struct LaunchOption { + option: String, + name: String, +} + pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { // 托盘菜单项目 let separator = &PredefinedMenuItem::separator(app).unwrap(); @@ -47,13 +54,8 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { )?; - let current_launch_option = MenuItem::with_id( - app, - "current_launch_option", - "启动项档位", - true, - None::<&str>, - )?; + // 创建启动项子菜单(初始为空,后续会动态更新) + let launch_option_submenu = Submenu::with_id(app, "launch_option_submenu", "启动项: 游戏", true)?; // 创建托盘菜单 let menu = Menu::with_items( @@ -64,7 +66,7 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { &power_plan_balanced, &power_plan_powersave, separator, - ¤t_launch_option, + &launch_option_submenu, launch_ww_i, launch_pw_i, separator, @@ -114,10 +116,58 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { } }); - let _ = app.listen("tray://get_current_launch_option", move |event| { - let payload = event.payload(); - if payload != "" { - let _ = current_launch_option.set_text("启动项档位 ".to_string() + payload); + // 监听启动项列表更新事件 + let launch_option_submenu_clone = launch_option_submenu.clone(); + let _ = app.listen("tray://update_launch_options", move |event| { + if let Ok(data) = serde_json::from_str::(event.payload()) { + if let (Some(options), Some(current_index)) = ( + data.get("options").and_then(|v| v.as_array()), + data.get("currentIndex").and_then(|v| v.as_u64()), + ) { + let current_index = current_index as usize; + // 获取当前启动项名称 + let current_name = options + .get(current_index) + .and_then(|opt| opt.get("name")) + .and_then(|n| n.as_str()) + .map(|s| s.to_string()) + .unwrap_or_else(|| format!("{}", current_index + 1)); + + // 更新子菜单标题 + let _ = launch_option_submenu_clone.set_text(format!("启动项: {}", current_name)); + + // 清空现有子菜单项 - 先收集所有项目,然后移除 + if let Ok(items) = launch_option_submenu_clone.items() { + let items_to_remove: Vec<_> = items.iter().collect(); + for item in items_to_remove { + let _ = launch_option_submenu_clone.remove(item); + } + } + + // 创建新的子菜单项 + for (index, option) in options.iter().enumerate() { + if let Some(name) = option.get("name").and_then(|n| n.as_str()) { + let display_name = if name.is_empty() { + format!("{}", index + 1) + } else { + name.to_string() + }; + + let item_id = format!("launch_option_{}", index); + let app_handle = launch_option_submenu_clone.app_handle(); + if let Ok(item) = CheckMenuItem::with_id( + app_handle, + &item_id, + &display_name, + true, + index == current_index, + None::<&str>, + ) { + let _ = launch_option_submenu_clone.append(&item); + } + } + } + } } }); @@ -166,6 +216,12 @@ pub fn create_tray(app: &tauri::AppHandle) -> tauri::Result<()> { let _ = app.emit("tray://set_powerplan", PowerPlanMode::PowerSaving as i32); // let _ = power_plan_powersave.set_checked(true); } + id if id.starts_with("launch_option_") => { + // 提取索引 + if let Ok(index) = id.replace("launch_option_", "").parse::() { + let _ = app.emit("tray://set_launch_index", index); + } + } _ => {} }) .on_tray_icon_event(|tray, event| { diff --git a/src/store/index.ts b/src/store/index.ts index b9b6078..801e0a1 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,11 +1,8 @@ -import { appConfigDir } from "@tauri-apps/api/path" -import { commands } from "@tauri-store/shared" import { appStore } from "./app" import { authStore } from "./auth" import { steamStore } from "./steam" import { toolStore } from "./tool" -import { fpsTestStore } from "./fpsTest" -import path from "path" +import { fpsTestStore } from "./fps_test" export async function init() { await appStore.start() @@ -13,7 +10,4 @@ export async function init() { await toolStore.start() await steamStore.start() await fpsTestStore.start() - const appConfigDirPath = await appConfigDir() - const setPath = commands.setStoreCollectionPath("valtio") - await setPath(path.resolve(appConfigDirPath, "cstb")) }