[feat] optimization for getting cs2 path + add Toast for auto path
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
"@heroui/react": "^2.7.5",
|
||||
"@icon-park/react": "^1.4.2",
|
||||
"@reactuses/core": "6.0.1",
|
||||
"@supabase/ssr": "^0.5.2",
|
||||
"@supabase/ssr": "0.6.1",
|
||||
"@tauri-apps/api": "2.1.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "2.0.0",
|
||||
"@tauri-apps/plugin-deep-link": "~2.2.0",
|
||||
@@ -43,7 +43,6 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"swr": "^2.3.3",
|
||||
"tauri-plugin-system-info-api": "^2.0.10",
|
||||
"tauri-plugin-valtio": "1.1.2",
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"zustand": "5.0.1"
|
||||
},
|
||||
|
||||
@@ -39,8 +39,8 @@ pub fn get_steam_path() -> Result<String, String> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_cs_path(name: &str) -> Result<String, String> {
|
||||
wrap_err!(steam::path::get_cs_path(name))
|
||||
pub fn get_cs_path(name: &str, steam_dir: &str) -> Result<String, String> {
|
||||
wrap_err!(steam::path::get_cs_path(name, steam_dir))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -3,35 +3,102 @@
|
||||
// - CS2(CS:GO) Path
|
||||
#![allow(unused)]
|
||||
|
||||
use anyhow::Result;
|
||||
use std::fs::{self, exists};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::tool::common::get_exe_path;
|
||||
use anyhow::Result;
|
||||
|
||||
use super::reg;
|
||||
|
||||
pub fn get_steam_path() -> Result<String, String> {
|
||||
// Running steam.exe
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Ok(steam_path) = get_exe_path("steam") {
|
||||
// 去除结尾的 steam.exe 不区分大小写
|
||||
if let Some(parent_path) = Path::new(&steam_path).parent() {
|
||||
return Ok(parent_path.to_string_lossy().to_string());
|
||||
} else {
|
||||
return Err("Failed to get parent directory".into());
|
||||
}
|
||||
}
|
||||
|
||||
// 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".into())
|
||||
}
|
||||
|
||||
pub fn get_cs_path(name: &str) -> Result<String, String> {
|
||||
pub fn get_cs_path(name: &str, steam_dir: &str) -> Result<String, String> {
|
||||
if name != "csgo" && name != "cs2" {
|
||||
return Err("invalid cs name".into());
|
||||
}
|
||||
|
||||
// 1. 优先检查 steam_dir 参数
|
||||
let steam_path = if steam_dir.is_empty() || !Path::new(steam_dir).exists() {
|
||||
// 如果 steam_dir 不满足条件,调用 get_steam_path 获取路径
|
||||
let p = get_steam_path().map_err(|e| e.to_string())?;
|
||||
PathBuf::from(p)
|
||||
} else {
|
||||
PathBuf::from(steam_dir) // 同样转换为PathBuf
|
||||
};
|
||||
|
||||
let cs_path = steam_path
|
||||
.join("steamapps\\common\\Counter-Strike Global Offensive\\game\\bin\\win64\\cs2.exe");
|
||||
if cs_path.exists() {
|
||||
if let Some(parent) = cs_path.parent() {
|
||||
return Ok(parent.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 通过注册表读取所有磁盘盘符
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Ok(cs_path) = get_exe_path(&(name.to_owned() + ".exe")) {
|
||||
return Ok(cs_path);
|
||||
{
|
||||
use winreg::enums::HKEY_LOCAL_MACHINE;
|
||||
use winreg::RegKey;
|
||||
|
||||
let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
|
||||
let mounted_devices = hklm
|
||||
.open_subkey("SYSTEM\\MountedDevices")
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// 获取所有盘符
|
||||
let drives: Vec<String> = mounted_devices
|
||||
.enum_values()
|
||||
.filter_map(|v| {
|
||||
let (name, _) = v.ok()?;
|
||||
let name = name.to_string();
|
||||
if name.starts_with("\\DosDevices\\") {
|
||||
Some(name.replace("\\DosDevices\\", ""))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 遍历盘符,尝试查找 cs2.exe
|
||||
let cs_path_suffix = "SteamLibrary\\steamapps\\common\\Counter-Strike Global Offensive\\game\\bin\\win64\\cs2.exe";
|
||||
for drive in drives {
|
||||
let cs_path = Path::new(&drive).join(cs_path_suffix);
|
||||
if cs_path.exists() {
|
||||
if let Some(parent) = cs_path.parent() {
|
||||
return Ok(parent.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 查找正在运行的cs2进程
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Ok(cs2_path) = get_exe_path("cs2") {
|
||||
// 去除结尾的 steam.exe 不区分大小写
|
||||
if let Some(parent_path) = Path::new(&cs2_path).parent() {
|
||||
return Ok(parent_path.to_string_lossy().to_string());
|
||||
} else {
|
||||
return Err("Failed to get parent directory".into());
|
||||
}
|
||||
}
|
||||
|
||||
Err("no cs path found".into())
|
||||
@@ -46,4 +113,17 @@ mod tests {
|
||||
let path = get_steam_path().unwrap();
|
||||
println!("{}", path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_cs_path() {
|
||||
let result = get_cs_path("cs2", "");
|
||||
assert!(result.is_ok() || result.is_err());
|
||||
println!("CS2 Path: {:?}", result);
|
||||
|
||||
// 使用get_steam_path给到的路径
|
||||
let steam_dir = get_steam_path().unwrap();
|
||||
let result = get_cs_path("cs2", &steam_dir);
|
||||
assert!(result.is_ok() || result.is_err());
|
||||
println!("CS2 Path: {:?}", result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::process::Command;
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::process::Command;
|
||||
|
||||
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||
// const DETACHED_PROCESS: u32 = 0x00000008;
|
||||
@@ -21,18 +21,29 @@ pub fn run_steam() -> std::io::Result<std::process::Output> {
|
||||
.output()
|
||||
}
|
||||
|
||||
// FIXME wmic is deprecated
|
||||
|
||||
pub fn get_exe_path(name: &str) -> Result<String, std::io::Error> {
|
||||
let command = format!("/C wmic process where name='{}' get ExecutablePath", name);
|
||||
// [原理]
|
||||
// Powershell 运行 Get-Process name | Select-Object path
|
||||
// 有name.exe运行时返回
|
||||
// Path
|
||||
// ----
|
||||
// 进程路径
|
||||
let command = format!("Get-Process {} | Select-Object path", name);
|
||||
let args = command.split_whitespace().collect::<Vec<&str>>();
|
||||
let output = Command::new("cmd.exe").args(&args).output()?;
|
||||
let output = Command::new("powershell.exe")
|
||||
.args(&args)
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()?;
|
||||
|
||||
let out = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let out = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
if out.contains("ExecutablePath") {
|
||||
if out.contains("Path") {
|
||||
let out = out.trim();
|
||||
let spt: Vec<&str> = out.split("\r\n").collect();
|
||||
if spt.len() >= 2 {
|
||||
return Ok(spt[1].to_string());
|
||||
|
||||
if spt.len() > 2 {
|
||||
return Ok(spt[2].to_string());
|
||||
}
|
||||
}
|
||||
Err(std::io::Error::new(
|
||||
@@ -68,4 +79,12 @@ mod tests {
|
||||
println!("test open path: {}", path);
|
||||
open_path(path).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_exe_path() {
|
||||
let path = get_exe_path("steam").expect("failed");
|
||||
println!("test get steam path: {}", path);
|
||||
|
||||
get_exe_path("not_running").expect("failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,13 @@
|
||||
"use client"
|
||||
import SteamUsers from "@/components/cstb/SteamUsers"
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
CardIcon,
|
||||
CardTool,
|
||||
} from "@/components/window/Card"
|
||||
import { ToolButton } from "@/components/window/ToolButton"
|
||||
import { cn } from "@heroui/react"
|
||||
import {
|
||||
AssemblyLine, HardDisk, SettingConfig,
|
||||
UploadOne
|
||||
} from "@icon-park/react"
|
||||
import { usePathname, useRouter } from "next/navigation"
|
||||
// import { usePathname, useRouter } from "next/navigation"
|
||||
// import { platform } from "@tauri-apps/plugin-os"
|
||||
|
||||
export default function PreferenceLayout({
|
||||
children,
|
||||
}: { children: React.ReactNode }) {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
// const router = useRouter()
|
||||
// const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<div className="flex w-full gap-3">
|
||||
|
||||
@@ -13,13 +13,10 @@ export default function Providers({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<HeroUIProvider
|
||||
className={cn(
|
||||
"h-full bg-zinc-100/90 dark:bg-zinc-900",
|
||||
os === "macos" && "rounded-lg",
|
||||
)}
|
||||
className={cn("h-full bg-zinc-200/90 dark:bg-zinc-900", os === "macos" && "rounded-lg")}
|
||||
>
|
||||
<NextThemesProvider attribute="class" defaultTheme="light">
|
||||
<ToastProvider toastOffset={10} placement="top-center" />
|
||||
<ToastProvider toastOffset={10} placement="top-center" toastProps={{ timeout: 3000 }} />
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
</HeroUIProvider>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { addToast } from "@heroui/react"
|
||||
import { FolderFocusOne } from "@icon-park/react"
|
||||
import { Card, CardBody, CardHeader, CardIcon } from "../window/Card"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { configDir } from "@tauri-apps/api/path"
|
||||
import { useSteamStore } from "@/store/steam"
|
||||
import path from "path"
|
||||
|
||||
@@ -17,7 +16,7 @@ const RoundedButton = ({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center px-3 py-1 transition rounded-full select-none min-w-fit active:scale-95 hover:bg-black/10 text-zinc-700 dark:text-zinc-100 bg-black/5"
|
||||
className="flex items-center justify-center px-3 py-1 transition rounded-full select-none min-w-fit active:scale-95 hover:bg-black/10 text-zinc-700 dark:text-zinc-100 bg-black/5 dark:bg-white/5"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addToast, Button, Chip, Input, Spinner } from "@heroui/react"
|
||||
import { addToast, Button, Input, Spinner } from "@heroui/react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useSteamStore } from "@/store/steam"
|
||||
@@ -6,6 +6,7 @@ import { open } from "@tauri-apps/plugin-dialog"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { onOpenUrl } from "@tauri-apps/plugin-deep-link"
|
||||
import { useDebounce } from "ahooks"
|
||||
import { useAppStore } from "@/store/app"
|
||||
|
||||
/**
|
||||
* 检查指定路径的有效性
|
||||
@@ -28,6 +29,7 @@ function trim_end_string(str: string, suffix: string): string {
|
||||
|
||||
export function Prepare() {
|
||||
const steam = useSteamStore()
|
||||
const app = useAppStore()
|
||||
const router = useRouter()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [, setSteamDir] = useState(steam.state.steamDir)
|
||||
@@ -35,7 +37,12 @@ export function Prepare() {
|
||||
|
||||
// Init
|
||||
useEffect(() => {
|
||||
void steam.store.start()
|
||||
const init = async () => {
|
||||
await steam.store.start()
|
||||
await app.store.start()
|
||||
if (!app.state.inited) await autoGetPaths()
|
||||
}
|
||||
void init()
|
||||
}, [])
|
||||
|
||||
// valid变动后调整State
|
||||
@@ -45,22 +52,12 @@ export function Prepare() {
|
||||
trailing: true,
|
||||
maxWait: 2500,
|
||||
})
|
||||
const [checkCount, setCheckCount] = useState(0)
|
||||
useEffect(() => {
|
||||
setCheckCount((prev) => (prev >= 10 ? 10 : prev + 1))
|
||||
console.log(checkCount, "触发", debounceValid, steam.state.steamDir, steam.state.cs2Dir)
|
||||
|
||||
if (checkCount < 2) {
|
||||
if (debounceValid) {
|
||||
console.log("跳转")
|
||||
setTimeout(() => {
|
||||
router.push("/home")
|
||||
}, 500)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
}, 1200)
|
||||
}
|
||||
// console.log(checkCount, "触发", debounceValid, steam.state.steamDir, steam.state.cs2Dir)
|
||||
if (debounceValid && app.state.inited) {
|
||||
setTimeout(() => {
|
||||
router.push("/home")
|
||||
}, 500)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
@@ -94,10 +91,17 @@ export function Prepare() {
|
||||
}
|
||||
|
||||
try {
|
||||
const cs2_path = await invoke<string>("get_cs_path", { name: "cs2" })
|
||||
if (cs2_path) steam.setCsDir(cs2_path.replace(/\\[^\\]+$/, ""))
|
||||
const cs2_path = await invoke<string>("get_cs_path", {
|
||||
name: "cs2",
|
||||
steamDir: steam.state.steamDir,
|
||||
})
|
||||
if (cs2_path) {
|
||||
steam.setCsDir(cs2_path)
|
||||
addToast({ title: "自动获取路径成功", color: "success" })
|
||||
}
|
||||
} catch (e) {
|
||||
addToast({ title: "自动获取CS2路径失败", color: "danger" })
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +133,7 @@ export function Prepare() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full max-w-3xl gap-2 p-5">
|
||||
<p className="text-center">当前路径等设置有待确认</p>
|
||||
<p className="text-center">请确认下列设置以确保工具箱正常运行</p>
|
||||
<br />
|
||||
<h3 className="font-semibold">Steam所在文件夹</h3>
|
||||
<div className="flex gap-2">
|
||||
@@ -145,7 +149,13 @@ export function Prepare() {
|
||||
errorMessage={"路径无效"}
|
||||
isInvalid={!steam.state.steamDirValid}
|
||||
/>
|
||||
<Button onPress={handleSelectSteamDir} variant="solid" color="primary" size="sm" isLoading={steam.state.steamDirChecking}>
|
||||
<Button
|
||||
onPress={handleSelectSteamDir}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
isLoading={steam.state.steamDirChecking}
|
||||
>
|
||||
选择
|
||||
</Button>
|
||||
</div>
|
||||
@@ -160,10 +170,16 @@ export function Prepare() {
|
||||
steam.setCsDir(value)
|
||||
}}
|
||||
description="cs2.exe所在文件夹"
|
||||
errorMessage={"路径无效"}
|
||||
errorMessage={"路径无效,建议启动游戏后点击自动获取,可以检测运行中的cs2"}
|
||||
isInvalid={!steam.state.cs2DirValid}
|
||||
/>
|
||||
<Button onPress={handleSelectCs2Dir} variant="solid" color="primary" size="sm" isLoading={steam.state.cs2DirChecking}>
|
||||
<Button
|
||||
onPress={handleSelectCs2Dir}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
isLoading={steam.state.cs2DirChecking}
|
||||
>
|
||||
选择
|
||||
</Button>
|
||||
</div>
|
||||
@@ -175,16 +191,19 @@ export function Prepare() {
|
||||
onPress={() => void autoGetPaths()}
|
||||
variant="ghost"
|
||||
color="default"
|
||||
size="sm"
|
||||
size="md"
|
||||
className="w-24"
|
||||
>
|
||||
自动获取
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => router.push("/home")}
|
||||
onPress={() => {
|
||||
app.setInited(true)
|
||||
router.push("/home")
|
||||
}}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
size="md"
|
||||
className="w-24"
|
||||
isLoading={steam.state.steamDirChecking || steam.state.cs2DirChecking}
|
||||
isDisabled={!steam.state.steamDirValid || !steam.state.cs2DirValid}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { User } from "@icon-park/react"
|
||||
import { Card, CardBody, CardHeader, CardIcon } from "../window/Card"
|
||||
import { Button, Chip, cn, Code } from "@heroui/react"
|
||||
import { Button, Chip } from "@heroui/react"
|
||||
import { useSteamStore } from "@/store/steam"
|
||||
|
||||
const SteamUsers = ({ className }: { className?: string }) => {
|
||||
|
||||
@@ -42,15 +42,18 @@ const Nav = () => {
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
const app = useAppStore()
|
||||
|
||||
return (
|
||||
<nav className="absolute top-0 right-0 flex flex-row h-16 gap-0.5 p-4" data-tauri-drag-region>
|
||||
<ResetModal />
|
||||
|
||||
{pathname !== "/" && (
|
||||
<button
|
||||
type="button"
|
||||
className="px-2 py-0 transition duration-150 rounded hover:bg-zinc-200/80 dark:hover:bg-zinc-100/10 active:scale-95"
|
||||
onClick={() => (pathname !== "/" ? router.push("/") : router.back())}
|
||||
onClick={() => {
|
||||
app.setInited(false)
|
||||
if(pathname !== "/") router.push("/")
|
||||
}}
|
||||
>
|
||||
<RocketOne size={16} />
|
||||
</button>
|
||||
@@ -64,6 +67,8 @@ const Nav = () => {
|
||||
{theme === "light" ? <SunOne size={16} /> : <Moon size={16} />}
|
||||
</button>
|
||||
|
||||
<ResetModal />
|
||||
|
||||
{/* { platform() === "windows" && ( */}
|
||||
<>
|
||||
<button
|
||||
@@ -127,9 +132,7 @@ function ResetModal() {
|
||||
<>
|
||||
<ModalHeader className="flex flex-col gap-1">重置设置</ModalHeader>
|
||||
<ModalBody>
|
||||
<p>
|
||||
确认后会恢复CS工具箱的偏好设置为默认设置
|
||||
</p>
|
||||
<p>确认后会恢复CS工具箱的偏好设置为默认设置</p>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button color="danger" variant="light" onPress={onClose}>
|
||||
|
||||
@@ -29,8 +29,8 @@ const SideButton = ({
|
||||
onClick={() => router.push(route || "/")}
|
||||
className={cn(
|
||||
className,
|
||||
"p-2.5 hover:bg-black/5 rounded-lg transition relative",
|
||||
path.startsWith(route) && "bg-black/5"
|
||||
"p-2.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg transition relative active:scale-90",
|
||||
path.startsWith(route) && "bg-black/5 dark:bg-white/5"
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user