diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 322246ca..120aff5c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -80,6 +80,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -659,6 +709,52 @@ dependencies = [ "inout", ] +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "combine" version = "4.6.7" @@ -2217,6 +2313,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.17" @@ -2627,6 +2729,7 @@ dependencies = [ "base64 0.22.1", "bytes", "chrono", + "clap", "flate2", "futures-util", "libc", @@ -3037,6 +3140,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "open" version = "5.3.3" @@ -5745,6 +5854,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.19.0" @@ -6507,7 +6622,7 @@ dependencies = [ [[package]] name = "winsafe" version = "0.0.27" -source = "git+https://github.com/rodrigocfd/winsafe.git?rev=908010cd6ecabad500fe06a32a8d3fa5fc0fec40#908010cd6ecabad500fe06a32a8d3fa5fc0fec40" +source = "git+https://github.com/rodrigocfd/winsafe.git?rev=9a13dd541cf2a3fdc7b03b8a13c6fc0bf0de5871#9a13dd541cf2a3fdc7b03b8a13c6fc0bf0de5871" [[package]] name = "wit-bindgen" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 61df4ad2..34e0167a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -50,6 +50,7 @@ notify-rust = "4" shell-words = "1.1.1" maa-framework = { version = "1", features = ["dynamic"] } rust-embed = "8" +clap = { version = "4", features = ["derive"] } [profile.release] # 保留调试符号以生成 PDB 文件,便于崩溃分析 @@ -57,7 +58,7 @@ debug = true [target.'cfg(windows)'.dependencies] # FIXME: winsafe new release -winsafe = { git = "https://github.com/rodrigocfd/winsafe.git", rev = "908010cd6ecabad500fe06a32a8d3fa5fc0fec40", features = [ +winsafe = { git = "https://github.com/rodrigocfd/winsafe.git", rev = "9a13dd541cf2a3fdc7b03b8a13c6fc0bf0de5871", features = [ "advapi", "kernel", "shell", diff --git a/src-tauri/src/commands/system.rs b/src-tauri/src/commands/system.rs index fee45c4c..4377c2c3 100644 --- a/src-tauri/src/commands/system.rs +++ b/src-tauri/src/commands/system.rs @@ -6,11 +6,13 @@ use super::types::MaaState; use super::types::SystemInfo; use super::types::WebView2DirInfo; use super::utils::get_maafw_dir; +use clap::Parser; use log::info; #[cfg(windows)] use log::warn; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::sync::OnceLock; use std::time::Duration; use tauri::State; use tokio::time::sleep; @@ -26,6 +28,87 @@ pub fn set_vcredist_missing(missing: bool) { VCREDIST_MISSING.store(missing, Ordering::SeqCst); } +static CLI: OnceLock = OnceLock::new(); + +#[derive(Parser)] +#[command( + disable_help_flag = true, + disable_version_flag = true, + disable_help_subcommand = true, + ignore_errors = true +)] +pub struct Cli { + #[arg(long)] + pub autostart: bool, + #[arg(short = 'h', long)] + pub help: bool, + #[arg(short = 'i', long = "instance")] + pub instance: Option, + #[arg(short = 'q', long = "quit-after-run")] + pub quit_after_run: bool, +} + +pub fn init_cli() -> &'static Cli { + CLI.get_or_init(|| { + let cli = Cli::parse(); + if cli.help { + #[cfg(windows)] + let attach = winsafe::AttachConsole(winsafe::PidParent::Parent); + + print!("{}", get_cli_help_text()); + + use std::io::Write; + let _ = std::io::stdout().flush(); + + #[cfg(windows)] + if attach.is_ok() { let _ = winsafe::FreeConsole(); }; + + std::process::exit(0); + } + cli + }) +} + +fn get_cli_help_text() -> String { + let exe_name = std::env::current_exe() + .ok() + .and_then(|path| { + path.file_name() + .map(|name| name.to_string_lossy().into_owned()) + }) + .filter(|name| !name.is_empty()) + .unwrap_or_else(|| "mxu".to_string()); + + format!( + "\ +MXU 命令行参数 + +用法: + {exe_name} [参数] + +参数: + -h, --help + 显示本帮助并退出 + + --autostart + 以开机自启动模式运行,并触发自动执行逻辑 + 通常由 MXU 创建的系统自启动任务自动传入 + + -i, --instance <实例名> + 指定自动执行时使用的实例名 + 仅在 --autostart 模式下生效 + 也支持 -i=<实例名> 与 --instance=<实例名> 写法 + + -q, --quit-after-run + 当本次启动实际触发自动执行后,在任务完成时自动退出 + +示例: + {exe_name} --autostart --instance \"日常任务\" + {exe_name} --autostart -i \"日常任务\" --quit-after-run +" + ) +} + /// 检查当前进程是否以管理员权限运行 #[tauri::command] pub fn is_elevated() -> bool { @@ -531,134 +614,19 @@ pub fn check_vcredist_missing() -> bool { /// 检查本次启动是否来自开机自启动(通过 --autostart 参数判断) #[tauri::command] pub fn is_autostart() -> bool { - std::env::args().any(|arg| arg == "--autostart") -} - -/// 打印命令行帮助文本。 -pub fn print_cli_help_text() { - #[cfg(windows)] - let attached_console = attach_parent_console_for_cli(); - - print!("{}", get_cli_help_text()); - - use std::io::Write; - let _ = std::io::stdout().flush(); - - #[cfg(windows)] - if attached_console { - detach_parent_console_for_cli(); - } -} - -#[cfg(windows)] -fn attach_parent_console_for_cli() -> bool { - extern "system" { - fn AttachConsole(dw_process_id: u32) -> i32; - } - - const ATTACH_PARENT_PROCESS: u32 = 0xFFFF_FFFF; - - // GUI subsystem builds do not auto-attach to the invoking terminal. - // Ignore failure: redirected stdout or double-click launches should still fall through. - unsafe { AttachConsole(ATTACH_PARENT_PROCESS) != 0 } -} - -#[cfg(windows)] -fn detach_parent_console_for_cli() { - extern "system" { - fn FreeConsole() -> i32; - } - - unsafe { - FreeConsole(); - } -} - -/// 检查命令行是否包含 -h/--help 参数 -pub fn has_help_flag() -> bool { - std::env::args() - .skip(1) - .any(|arg| arg == "-h" || arg == "--help") -} - -/// 生成命令行帮助文本 -pub fn get_cli_help_text() -> String { - let exe_name = std::env::current_exe() - .ok() - .and_then(|path| { - path.file_name() - .map(|name| name.to_string_lossy().into_owned()) - }) - .filter(|name| !name.is_empty()) - .unwrap_or_else(|| "mxu".to_string()); - - format!( - "\ -MXU 命令行参数 - -用法: - {exe_name} [参数] - -参数: - -h, --help - 显示本帮助并退出 - - --autostart - 以开机自启动模式运行,并触发自动执行逻辑 - 通常由 MXU 创建的系统自启动任务自动传入 - - -i, --instance <实例名> - 指定自动执行时使用的实例名 - 仅在 --autostart 模式下生效 - 也支持 -i=<实例名> 与 --instance=<实例名> 写法 - - -q, --quit-after-run - 当本次启动实际触发自动执行后,在任务完成时自动退出 - -示例: - {exe_name} --autostart --instance \"日常任务\" - {exe_name} --autostart -i \"日常任务\" --quit-after-run -" - ) -} - -/// 从命令行参数中获取指定选项的值 -/// 支持 `-x value`、`--name value`、`-x=value`、`--name=value` 格式 -/// 返回第一个匹配的值;若值缺失或以 `-` 开头则视为无效并跳过 -fn get_cli_arg_value(short: &str, long: &str) -> Option { - let short_eq = format!("{}=", short); - let long_eq = format!("{}=", long); - let args: Vec = std::env::args().collect(); - let mut iter = args.iter(); - while let Some(arg) = iter.next() { - if arg == short || arg == long { - if let Some(value) = iter.next() { - if !value.starts_with('-') { - return Some(value.clone()); - } - } - return None; - } - if let Some(value) = arg.strip_prefix(&short_eq) { - return Some(value.to_string()); - } - if let Some(value) = arg.strip_prefix(&long_eq) { - return Some(value.to_string()); - } - } - None + init_cli().autostart } /// 获取命令行 -i/--instance 参数指定的启动实例名称 #[tauri::command] pub fn get_start_instance() -> Option { - get_cli_arg_value("-i", "--instance") + init_cli().instance.clone() } /// 检查命令行是否包含 -q/--quit-after-run 参数(任务完成后关闭自身) #[tauri::command] pub fn has_quit_after_run_flag() -> bool { - std::env::args().any(|arg| arg == "-q" || arg == "--quit-after-run") + init_cli().quit_after_run } /// 自动迁移旧版注册表自启动到任务计划程序 diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index df379472..bfd3cad8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,10 +5,7 @@ mod webview2; fn main() { - if mxu_lib::commands::system::has_help_flag() { - mxu_lib::commands::system::print_cli_help_text(); - std::process::exit(0); - } + let _cli = mxu_lib::commands::system::init_cli(); #[cfg(target_os = "windows")] { @@ -76,3 +73,4 @@ fn main() { mxu_lib::run() } + diff --git a/src-tauri/src/mxu_actions.rs b/src-tauri/src/mxu_actions.rs index 3b8d6cf6..4e2429a2 100644 --- a/src-tauri/src/mxu_actions.rs +++ b/src-tauri/src/mxu_actions.rs @@ -728,13 +728,13 @@ fn execute_power_restart() -> bool { fn execute_power_screenoff() -> bool { #[cfg(windows)] { + use winsafe::msg::WmSysCommand; use winsafe::co::SC; - use winsafe::msg::wm; use winsafe::{HWND, POINT}; unsafe { // NOTE: POINT::from(2) is equal to LPARAM(2) - HWND::BROADCAST.SendMessage(wm::SysCommand { + HWND::BROADCAST.SendMessage(WmSysCommand { request: SC::MONITORPOWER, position: POINT::from(2), });