Files
qiming/qimingclaw/specs/agent-client/vendorsdoc/auto-launch.md

11 KiB
Raw Permalink Blame History

auto-launch

项目概述

跨平台应用开机自启动库,支持 Windows、macOS 和 Linux。

GitHub: https://github.com/zzzgydi/auto-launch.git 本地路径: vendors/auto-launch

目录结构

auto-launch/
├── src/
│   ├── lib.rs              # 公共 API
│   ├── windows.rs          # Windows 实现
│   ├── macos.rs            # macOS 实现
│   ├── linux.rs            # Linux 实现
│   └── error.rs            # 错误处理
├── Cargo.toml
└── README.md

核心依赖

[dependencies]
thiserror = "1.0"
os_info = "7.0"

# Windows
windows-registry = "0.2"
windows-result = "0.2"

# macOS
smappservice-rs = "0.3"

# Linux
dirs = "5.0"

核心 API

AutoLaunchBuilder

// lib.rs

pub struct AutoLaunchBuilder {
    app_name: Option<String>,
    app_path: Option<String>,
    macos_launch_mode: MacOSLaunchMode,
    bundle_identifiers: Option<Vec<String>>,
    agent_extra_config: Option<String>,
    windows_enable_mode: WindowsEnableMode,
    linux_launch_mode: LinuxLaunchMode,
    args: Option<Vec<String>>,
}

impl AutoLaunchBuilder {
    pub fn new() -> Self;

    /// 设置应用名称
    pub fn set_app_name(&mut self, name: &str) -> &mut Self;

    /// 设置应用路径
    pub fn set_app_path(&mut self, path: &str) -> &mut Self;

    /// 设置 macOS 启动模式
    pub fn set_macos_launch_mode(&mut self, mode: MacOSLaunchMode) -> &mut Self;

    /// 设置 Windows 启用模式
    pub fn set_windows_enable_mode(&mut self, mode: WindowsEnableMode) -> &mut Self;

    /// 设置 Linux 启动模式
    pub fn set_linux_launch_mode(&mut self, mode: LinuxLaunchMode) -> &mut Self;

    /// 设置启动参数
    pub fn set_args(&mut self, args: &[impl AsRef<str>]) -> &mut Self;

    /// 构建 AutoLaunch
    pub fn build(&self) -> Result<AutoLaunch>;
}

AutoLaunch

pub struct AutoLaunch {
    app_name: String,
    app_path: String,
    args: Vec<String>,
    // 平台特定配置...
}

impl AutoLaunch {
    /// 启用自启动
    pub async fn enable(&self) -> Result<()>;

    /// 禁用自启动
    pub async fn disable(&self) -> Result<()>;

    /// 检查是否已启用
    pub async fn is_enabled(&self) -> Result<bool>;
}

平台特定枚举

pub enum MacOSLaunchMode {
    /// 使用 SMAppService (macOS 13+)
    SMAppService,
    /// 使用 LaunchAgent
    LaunchAgent,
    /// 使用 AppleScript
    AppleScript,
}

pub enum WindowsEnableMode {
    /// 动态尝试(系统级 -> 用户级)
    Dynamic,
    /// 仅当前用户
    CurrentUser,
    /// 所有用户(需管理员权限)
    System,
}

pub enum LinuxLaunchMode {
    /// 使用 XDG Autostart
    XdgAutostart,
    /// 使用 systemd
    Systemd,
}

使用示例

use auto_launch::AutoLaunch;

#[tokio::main]
async fn main() -> Result<()> {
    let auto = AutoLaunchBuilder::new()
        .set_app_name("qiming-agent")
        .set_app_path("/Applications/qiming-agent.app")
        .set_args(&["--hidden", "--minimize"])
        .set_macos_launch_mode(MacOSLaunchMode::SMAppService)
        .build()?;

    // 检查是否已启用
    let is_enabled = auto.is_enabled().await?;
    println!("Auto launch enabled: {}", is_enabled);

    // 启用自启动
    if !is_enabled {
        auto.enable().await?;
    }

    Ok(())
}

平台实现

平台 实现方式 配置位置
Windows 注册表 HKCU\Software\Microsoft\Windows\CurrentVersion\Run
macOS SMAppService ~/Library/LaunchAgents/
Linux XDG ~/.config/autostart/

与 agent-client 集成场景

场景1设置界面的自启动开关

// 在设置界面中集成自启动管理

use auto_launch::{AutoLaunch, AutoLaunchBuilder, MacOSLaunchMode};

pub struct AutoLaunchManager {
    auto_launch: AutoLaunch,
    // 当前状态(缓存,避免频繁读取)
    cached_enabled: bool,
}

impl AutoLaunchManager {
    pub fn new(app_name: &str, app_path: &str) -> Result<Self> {
        let auto = AutoLaunchBuilder::new()
            .set_app_name(app_name)
            .set_app_path(app_path)
            .set_macos_launch_mode(MacOSLaunchMode::SMAppService)  // macOS 13+
            .build()?;

        Ok(Self {
            auto_launch: auto,
            cached_enabled: false,
        })
    }

    /// 获取当前自启动状态
    pub async fn is_enabled(&self) -> Result<bool> {
        self.auto_launch.is_enabled().await
    }

    /// 启用自启动
    pub async fn enable(&self) -> Result<()> {
        self.auto_launch.enable().await
    }

    /// 禁用自启动
    pub async fn disable(&self) -> Result<()> {
        self.auto_launch.disable().await
    }

    /// 切换自启动状态
    pub async fn toggle(&self) -> Result<bool> {
        let enabled = self.is_enabled().await?;
        if enabled {
            self.disable().await?;
        } else {
            self.enable().await?;
        }
        Ok(!enabled)
    }
}

/// 设置界面中的自启动开关组件
pub struct AutoLaunchSwitch {
    manager: AutoLaunchManager,
    enabled: bool,
    loading: bool,
}

impl AutoLaunchSwitch {
    pub fn new(manager: AutoLaunchManager) -> Self {
        Self {
            manager,
            enabled: false,
            loading: true,
        }
    }

    /// 初始化时检查状态
    pub fn refresh_status(&mut self) {
        let manager = self.manager.clone();
        cx.spawn(|this, mut cx| async move {
            let enabled = manager.is_enabled().await.unwrap_or(false);
            this.update(&mut cx, |this, _| {
                this.enabled = enabled;
                this.loading = false;
            });
        });
    }
}

impl Render for AutoLaunchSwitch {
    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
        div()
            .flex()
            .items_center()
            .justify_between()
            .p_4()
            .rounded_md()
            .bg(cx.theme().colors.surface)
            .border_1()
            .border_color(cx.theme().colors.border)
            .child(
                div()
                    .flex()
                    .flex_col()
                    .gap_1()
                    .child(
                        div()
                            .font_medium()
                            .child("Auto Launch")
                    )
                    .child(
                        div()
                            .text_sm()
                            .text_color(cx.theme().colors.muted)
                            .child("Start application when system boots")
                    )
            )
            .child(
                match self.loading {
                    true => Loading::new().into_any_element(),
                    false => Switch::new(self.enabled, |enabled, _| {
                        // 切换自启动
                        let manager = self.manager.clone();
                        cx.spawn(|this, mut cx| async move {
                            let result = if enabled {
                                manager.disable().await
                            } else {
                                manager.enable().await
                            };

                            match result {
                                Ok(_) => {
                                    this.update(&mut cx, |this, _| {
                                        this.enabled = !enabled;
                                    });
                                }
                                Err(e) => {
                                    // 显示错误提示
                                    tracing::error!("Failed to toggle auto-launch: {}", e);
                                }
                            }
                        });
                    }).into_any_element(),
                }
            )
    }
}

场景2应用启动时检查自启动状态

// 应用启动时的初始化逻辑

pub struct AppInitializer {
    auto_launch_manager: Option<AutoLaunchManager>,
}

impl AppInitializer {
    pub async fn initialize(&mut self, app_path: &str) -> Result<()> {
        // 1. 初始化自启动管理器
        self.auto_launch_manager = Some(
            AutoLaunchManager::new("qiming-agent", app_path).ok()
        );

        // 2. 检查自启动是否已启用
        if let Some(manager) = &self.auto_launch_manager {
            let is_enabled = manager.is_enabled().await.unwrap_or(false);
            tracing::info!("Auto-launch enabled: {}", is_enabled);
        }

        // 3. 根据启动参数决定行为
        let args: Vec<String> = std::env::args().collect();

        // 如果是通过自启动触发的,可能需要最小化到托盘
        if args.iter().any(|arg| arg == "--minimize") {
            // 最小化到托盘,不显示主窗口
        }

        Ok(())
    }
}

场景3跨平台注意事项

// 不同平台的自启动行为差异处理

#[cfg(target_os = "windows")]
mod windows_impl {
    use auto_launch::WindowsEnableMode;

    pub fn get_app_path() -> String {
        // Windows: 使用完整路径
        std::env::current_exe()
            .unwrap()
            .to_string_lossy()
            .into_owned()
    }

    pub fn build_auto_launch(name: &str, path: &str) -> auto_launch::AutoLaunchBuilder {
        AutoLaunchBuilder::new()
            .set_app_name(name)
            .set_app_path(path)
            .set_windows_enable_mode(WindowsEnableMode::CurrentUser)  // 当前用户
    }
}

#[cfg(target_os = "macos")]
mod macos_impl {
    use auto_launch::MacOSLaunchMode;

    pub fn get_app_path() -> String {
        // macOS: 使用 .app 路径
        std::env::current_exe()
            .unwrap()
            .to_string_lossy()
            .into_owned()
    }

    pub fn build_auto_launch(name: &str, path: &str) -> auto_launch::AutoLaunchBuilder {
        AutoLaunchBuilder::new()
            .set_app_name(name)
            .set_app_path(path)
            .set_macos_launch_mode(MacOSLaunchMode::SMAppService)  // macOS 13+
    }
}

#[cfg(target_os = "linux")]
mod linux_impl {
    use auto_launch::LinuxLaunchMode;

    pub fn get_app_path() -> String {
        std::env::current_exe()
            .unwrap()
            .to_string_lossy()
            .into_owned()
    }

    pub fn build_auto_launch(name: &str, path: &str) -> auto_launch::AutoLaunchBuilder {
        AutoLaunchBuilder::new()
            .set_app_name(name)
            .set_app_path(path)
            .set_linux_launch_mode(LinuxLaunchMode::XdgAutostart)
    }
}

在本项目中的使用

用于实现 agent-client 的开机自启动功能:

agent-client
    │
    └── auto-launch
            │
            ├── 检测自启动状态 (设置界面开关)
            ├── 启用自启动 (用户勾选开关)
            ├── 禁用自启动 (用户取消勾选)
            └── 应用启动时最小化到托盘