fix store dir + feat launch option switch in tray

This commit is contained in:
2025-11-06 03:07:38 +08:00
parent ce410f7a26
commit 8550887bfb
7 changed files with 225 additions and 142 deletions

118
src-tauri/Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -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<VideoConfig, String> {
let p = format!(

View File

@@ -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<Box<SubcommandMatches>>` 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");
}

View File

@@ -6,6 +6,7 @@ use tauri::{AppHandle, Emitter};
// 全局 watcher 存储,用于管理文件监听器的生命周期
static WATCHER: OnceLock<Mutex<Option<RecommendedWatcher>>> = OnceLock::new();
static CS2_VIDEO_WATCHER: OnceLock<Mutex<Option<RecommendedWatcher>>> = 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<Event, notify::Error>| {
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 文件");
}
}
}
}

View File

@@ -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<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
// 托盘菜单项目
let separator = &PredefinedMenuItem::separator(app).unwrap();
@@ -47,13 +54,8 @@ pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> 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<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
&power_plan_balanced,
&power_plan_powersave,
separator,
&current_launch_option,
&launch_option_submenu,
launch_ww_i,
launch_pw_i,
separator,
@@ -114,10 +116,58 @@ pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> 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::<serde_json::Value>(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<R: Runtime>(app: &tauri::AppHandle<R>) -> 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::<usize>() {
let _ = app.emit("tray://set_launch_index", index);
}
}
_ => {}
})
.on_tray_icon_event(|tray, event| {

View File

@@ -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"))
}