1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use std::env;
use std::path::PathBuf;
use anyhow::{Context, Result};
/// 开机自启的注册配置;三个操作系统共用同一份配置。
#[derive(Debug, Clone)]
pub struct StartupConfig {
/// 自启名称,同时作为 Windows 计划任务名、systemd 单元名与 launchd 标签。
pub name: String,
/// 自启描述。
pub description: String,
/// 要启动的可执行文件绝对路径;为空表示当前运行中的可执行文件。
pub program: PathBuf,
/// 启动参数。
pub args: Vec<String>,
/// 工作目录;为空表示由系统决定。
pub working_directory: Option<PathBuf>,
/// 自启未注册时 `start`、`stop`、`is_run`、`restart` 与 `uninstall` 的行为:
/// 为真时直接返回成功或假,为假时返回错误。默认为真,便于把这些操作当作清理手段重复调用。
pub ignore_not_installed: bool,
/// 是否以最高可用权限启动,仅 Windows 生效。
///
/// 为真时用户级计划任务追加 `/rl highest`:登录时以当前用户身份、用提权后的令牌启动,
/// 不触发 UAC 弹窗;账号不在管理员组时注册或启动会失败。
/// 系统级任务固定以 `SYSTEM` 身份运行,该字段对它无意义。
#[cfg(target_os = "windows")]
pub highest: bool,
}
impl StartupConfig {
/// 创建配置;启动目标默认为当前运行中的可执行文件,未注册时忽略。
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
program: PathBuf::new(),
args: Vec::new(),
working_directory: None,
ignore_not_installed: true,
#[cfg(target_os = "windows")]
highest: false,
}
}
/// 补全启动目标:未指定可执行文件时取当前运行中的可执行文件。
pub(crate) fn resolve(mut self) -> Result<Self> {
if self.program.as_os_str().is_empty() {
self.program = env::current_exe().context("获取当前可执行文件路径失败")?;
}
Ok(self)
}
/// 完整的启动命令行:可执行文件绝对路径加启动参数,含空格的路径加引号;
/// 供 Windows 计划任务与 systemd 单元使用,launchd 需要拆成独立的参数元素。
#[cfg(any(target_os = "windows", target_os = "linux"))]
pub(crate) fn command_line(&self) -> String {
let mut line = quote(&self.program.to_string_lossy());
for arg in &self.args {
line.push(' ');
line.push_str("e(arg));
}
line
}
}
/// 命令行参数加引号;参数本身含双引号时原样返回,避免破坏用户输入。
#[cfg(any(target_os = "windows", target_os = "linux"))]
fn quote(value: &str) -> String {
if value.contains('"') || !value.contains(' ') {
return value.to_string();
}
format!("\"{value}\"")
}