From c19adcc3f83563e5af7968e7e157f17174cfb808 Mon Sep 17 00:00:00 2001 From: purp1e Date: Wed, 5 Nov 2025 11:32:43 +0800 Subject: [PATCH] [feat] listen to steam user changes --- src-tauri/Cargo.lock | 162 ++++++++++++++++++++++++++++++++++- src-tauri/Cargo.toml | 1 + src-tauri/src/cmds.rs | 5 ++ src-tauri/src/main.rs | 1 + src-tauri/src/steam/mod.rs | 1 + src-tauri/src/steam/watch.rs | 76 ++++++++++++++++ src/app/layout.tsx | 31 ++++++- 7 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 src-tauri/src/steam/watch.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 539aa78..2cb0128 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -10,6 +10,7 @@ dependencies = [ "base64 0.22.1", "futures-util", "log", + "notify", "regex", "reqwest", "serde", @@ -1288,6 +1289,18 @@ 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" @@ -1367,6 +1380,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futf" version = "0.1.5" @@ -2204,6 +2226,26 @@ dependencies = [ "cfb", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -2348,6 +2390,26 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kuchikiki" version = "0.8.8-speedreader" @@ -2430,6 +2492,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", + "redox_syscall", ] [[package]] @@ -2572,6 +2635,18 @@ 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" @@ -2708,6 +2783,25 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.10.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "notify-rust" version = "4.11.7" @@ -5450,7 +5544,7 @@ checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "bytes", "libc", - "mio", + "mio 1.1.0", "pin-project-lite", "signal-hook-registry", "socket2", @@ -6433,6 +6527,15 @@ 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" @@ -6484,6 +6587,21 @@ 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" @@ -6541,6 +6659,12 @@ 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" @@ -6559,6 +6683,12 @@ 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" @@ -6577,6 +6707,12 @@ 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" @@ -6607,6 +6743,12 @@ 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" @@ -6625,6 +6767,12 @@ 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" @@ -6643,6 +6791,12 @@ 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" @@ -6661,6 +6815,12 @@ 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 c553e1d..d1ecd7c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -52,6 +52,7 @@ 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" [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 72ddf1d..c051c1d 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -106,6 +106,11 @@ pub fn set_auto_login_user(user: &str) -> Result { Ok(format!("Set auto login user to {}", user)) } +#[tauri::command] +pub fn start_watch_loginusers(app: tauri::AppHandle, steam_dir: String) -> Result<(), String> { + wrap_err!(steam::watch::start_watch_loginusers(app, steam_dir)) +} + #[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 8ad1e17..0c4e02e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -129,6 +129,7 @@ fn main() { cmds::set_powerplan, cmds::get_steam_users, cmds::set_auto_login_user, + cmds::start_watch_loginusers, cmds::get_cs2_video_config, cmds::set_cs2_video_config, cmds::check_path, diff --git a/src-tauri/src/steam/mod.rs b/src-tauri/src/steam/mod.rs index 2f78e66..51a7bb4 100644 --- a/src-tauri/src/steam/mod.rs +++ b/src-tauri/src/steam/mod.rs @@ -3,6 +3,7 @@ pub mod id; pub mod path; pub mod reg; pub mod user; +pub mod watch; // common steam utils use anyhow::Result; diff --git a/src-tauri/src/steam/watch.rs b/src-tauri/src/steam/watch.rs new file mode 100644 index 0000000..de4823d --- /dev/null +++ b/src-tauri/src/steam/watch.rs @@ -0,0 +1,76 @@ +use anyhow::Result; +use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; +use std::path::Path; +use std::sync::{Mutex, OnceLock}; +use tauri::{AppHandle, Emitter}; + +// 全局 watcher 存储,用于管理文件监听器的生命周期 +static WATCHER: OnceLock>> = OnceLock::new(); + +/// 启动监听 loginusers.vdf 文件的变化 +pub fn start_watch_loginusers(app: AppHandle, steam_dir: String) -> Result<()> { + // 停止之前的监听 + stop_watch_loginusers(); + + let loginusers_path = Path::new(&steam_dir).join("config/loginusers.vdf"); + let config_dir = Path::new(&steam_dir).join("config"); + + // 如果 config 目录不存在,不进行监听 + if !config_dir.exists() { + log::warn!("config 目录不存在,跳过监听: {:?}", config_dir); + return Ok(()); + } + + let app_clone = app.clone(); + + let mut watcher = RecommendedWatcher::new( + move |result: Result| { + match result { + Ok(event) => { + // 检查是否是 loginusers.vdf 文件的变化 + if let EventKind::Modify(_) | EventKind::Create(_) = event.kind { + for path in &event.paths { + if path.ends_with("loginusers.vdf") { + log::info!("检测到 loginusers.vdf 文件变化: {:?}", path); + // 延迟一小段时间,确保文件写入完成 + std::thread::sleep(std::time::Duration::from_millis(100)); + // 发送事件到前端 + if let Err(e) = app_clone.emit("steam://loginusers_changed", ()) { + log::error!("发送 loginusers 变化事件失败: {}", e); + } + break; + } + } + } + } + Err(e) => { + log::error!("文件监听错误: {}", e); + } + } + }, + Config::default(), + )?; + + // 监听 config 目录(而不是单个文件),因为某些文件系统可能不会直接监听单个文件 + watcher.watch(&config_dir, RecursiveMode::NonRecursive)?; + log::info!("开始监听 loginusers.vdf 文件: {:?}", loginusers_path); + + // 保存 watcher 到全局变量 + let watcher_store = WATCHER.get_or_init(|| Mutex::new(None)); + *watcher_store.lock().unwrap() = Some(watcher); + + Ok(()) +} + +/// 停止监听 loginusers.vdf 文件 +pub fn stop_watch_loginusers() { + if let Some(watcher_store) = WATCHER.get() { + if let Ok(mut watcher_guard) = watcher_store.lock() { + if let Some(watcher) = watcher_guard.take() { + drop(watcher); + log::info!("已停止监听 loginusers.vdf 文件"); + } + } + } +} + diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 315ca32..01c6357 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,7 +5,7 @@ import { toolStore, useToolStore } from "@/store/tool" import { addToast } from "@heroui/react" import { invoke } from "@tauri-apps/api/core" import { listen } from "@tauri-apps/api/event" -import { useDebounce } from "ahooks" +import { useDebounce, useThrottleFn } from "ahooks" import { useEffect } from "react" import "./globals.css" import Providers from "./providers" @@ -75,8 +75,35 @@ export default function RootLayout({ children }: { children: React.ReactNode }) useEffect(() => { if (debounceSteamDirValid) { void steam.getUsers() + // 启动文件监听 + void invoke("start_watch_loginusers", { steamDir: steam.state.steamDir }) } - }, [debounceSteamDirValid]) + }, [debounceSteamDirValid, steam.state.steamDir]) + + // 节流获取用户列表函数,3秒间隔,trailing模式 + const { run: throttledGetUsers } = useThrottleFn( + async () => { + if (steam.state.steamDirValid) { + await steam.getUsers() + } + }, + { + wait: 3000, + leading: false, + trailing: true, + } + ) + + // 监听 loginusers.vdf 文件变化事件 + useEffect(() => { + const unlisten = listen("steam://loginusers_changed", async () => { + // 文件变化时使用节流函数重新获取用户列表 + throttledGetUsers() + }) + return () => { + void unlisten.then((fn) => fn()) + } + }, [steam.state.steamDirValid, throttledGetUsers]) return (