auto_launch/
lib.rs

1//! Auto launch any application or executable at startup. Supports Windows, Mac (via AppleScript or Launch Agent), and Linux.
2//!
3//! ## Usage
4//!
5//! The parameters of `AutoLaunch::new` are different on each platform.
6//! See the function definition or the demo below for details.
7//!
8//! Or you can construct the AutoLaunch by using `AutoLaunchBuilder`.
9//!
10//! ```rust
11//! # #[cfg(target_os = "linux")]
12//! # mod linux {
13//! use auto_launch::{AutoLaunch, LinuxLaunchMode};
14//!
15//! fn main() {
16//!     let app_name = "the-app";
17//!     let app_path = "/path/to/the-app";
18//!     let args = &["--minimized"];
19//!     // Use XDG Autostart by default, or use LinuxLaunchMode::Systemd for systemd
20//!     let auto = AutoLaunch::new(app_name, app_path, LinuxLaunchMode::XdgAutostart, args);
21//!
22//!     // enable the auto launch
23//!     auto.enable().is_ok();
24//!     auto.is_enabled().unwrap();
25//!
26//!     // disable the auto launch
27//!     auto.disable().is_ok();
28//!     auto.is_enabled().unwrap();
29//! }
30//! # }
31//! ```
32//!
33//! ### macOS
34//!
35//! macOS supports two ways to achieve auto launch:
36//! - **Launch Agent**: Uses plist files in `~/Library/LaunchAgents/` (default)
37//! - **AppleScript**: Uses AppleScript to add login items
38//!
39//! **Note**:
40//! - The `app_path` should be a absolute path and exists. Otherwise, it will cause an error when `enable`.
41//! - In case using AppleScript, the `app_name` should be same as the basename of `app_path`, or it will be corrected automatically.
42//! - In case using AppleScript, only `--hidden` and `--minimized` in `args` are valid, which means that hide the app on launch.
43//!
44//! ```rust
45//! # #[cfg(target_os = "macos")]
46//! # mod macos {
47//! use auto_launch::{AutoLaunch, MacOSLaunchMode};
48//!
49//! fn main() {
50//!     let app_name = "the-app";
51//!     let app_path = "/path/to/the-app.app";
52//!     let args = &["--minimized"];
53//!     let bundle_identifiers = &["com.github.auto-launch-test"];
54//!     // Use Launch Agent by default, or use MacOSLaunchMode::AppleScript
55//!     let auto = AutoLaunch::new(app_name, app_path, MacOSLaunchMode::LaunchAgent, args, bundle_identifiers, "");
56//!
57//!     // enable the auto launch
58//!     auto.enable().is_ok();
59//!     auto.is_enabled().unwrap();
60//!
61//!     // disable the auto launch
62//!     auto.disable().is_ok();
63//!     auto.is_enabled().unwrap();
64//! }
65//! # }
66//! ```
67//!
68//! ### Windows
69//!
70//! On Windows, it will add a registry entry under either `\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` (system-wide) or
71//! `\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` (current user only).
72//!
73//! By default we try to apply the auto launch to the system registry, which requires admin privileges and applies the auto launch to any user in the system.
74//! If there's no permission to do so, we fallback to enabling it to the current user only.
75//! To change this behavior, specify the [`WindowsEnableMode`] when creating the [`AutoLaunch`] instance.
76//!
77//! ```rust
78//! # #[cfg(target_os = "windows")]
79//! # mod win {
80//! use auto_launch::{AutoLaunch, WindowsEnableMode};
81//!
82//! fn main() {
83//!     let app_name = "the-app";
84//!     let app_path = "C:\\path\\to\\the-app.exe";
85//!     let args = &["--minimized"];
86//!     let enable_mode = WindowsEnableMode::CurrentUser;
87//!     let auto = AutoLaunch::new(app_name, app_path, enable_mode, args);
88//!
89//!     // enable the auto launch
90//!     auto.enable().is_ok();
91//!     auto.is_enabled().unwrap();
92//!
93//!     // disable the auto launch
94//!     auto.disable().is_ok();
95//!     auto.is_enabled().unwrap();
96//! }
97//! # }
98//! ```
99//!
100//! ### Builder
101//!
102//! AutoLaunch Builder helps to eliminate the constructor difference
103//! on various platforms.
104//!
105//! ```rust
106//! use auto_launch::*;
107//!
108//! # fn example() -> std::result::Result<(), Box<dyn std::error::Error>> {
109//! let auto = AutoLaunchBuilder::new()
110//!     .set_app_name("the-app")
111//!     .set_app_path("/path/to/the-app")
112//!     .set_macos_launch_mode(MacOSLaunchMode::LaunchAgent)
113//!     .set_args(&["--minimized"])
114//!     .build()?;
115//!
116//! auto.enable()?;
117//! auto.is_enabled()?;
118//!
119//! auto.disable()?;
120//! auto.is_enabled()?;
121//! # Ok(())
122//! # }
123//! ```
124//!
125
126#[derive(thiserror::Error, Debug)]
127pub enum Error {
128    #[error("app_name shouldn't be None")]
129    AppNameNotSpecified,
130    #[error("app_path shouldn't be None")]
131    AppPathNotSpecified,
132    #[error("app path doesn't exist: {0}")]
133    AppPathDoesntExist(std::path::PathBuf),
134    #[error("app path is not absolute: {0}")]
135    AppPathIsNotAbsolute(std::path::PathBuf),
136    #[error("Failed to execute apple script with status: {0}")]
137    AppleScriptFailed(i32),
138    #[error("Failed to register app with SMAppService with status: {0}")]
139    SMAppServiceRegistrationFailed(u32),
140    #[error("Failed to unregister app with SMAppService with status: {0}")]
141    SMAppServiceUnregistrationFailed(u32),
142    #[error("Unsupported target os")]
143    UnsupportedOS,
144    #[error(transparent)]
145    Io(#[from] std::io::Error),
146}
147
148pub type Result<T> = std::result::Result<T, Error>;
149
150#[cfg(target_os = "linux")]
151mod linux;
152#[cfg(target_os = "macos")]
153mod macos;
154#[cfg(target_os = "windows")]
155mod windows;
156
157/// The parameters of `AutoLaunch::new` are different on each platform.
158///
159/// ### Linux
160///
161/// ```rust
162/// # #[cfg(target_os = "linux")]
163/// # {
164/// # use auto_launch::{AutoLaunch, LinuxLaunchMode};
165/// # let app_name = "the-app";
166/// # let app_path = "/path/to/the-app";
167/// # let launch_mode = LinuxLaunchMode::XdgAutostart;
168/// # let args = &["--minimized"];
169/// AutoLaunch::new(app_name, app_path, launch_mode, args);
170/// # }
171/// ```
172///
173/// ### Macos
174///
175/// ```rust
176/// # #[cfg(target_os = "macos")]
177/// # {
178/// # use auto_launch::{AutoLaunch, MacOSLaunchMode};
179/// # let app_name = "the-app";
180/// # let app_path = "/path/to/the-app";
181/// # let launch_mode = MacOSLaunchMode::LaunchAgent;
182/// # let args = &["--minimized"];
183/// # let bundle_identifiers = &["com.github.auto-launch-test"];
184/// AutoLaunch::new(app_name, app_path, launch_mode, args, bundle_identifiers, "");
185/// # }
186/// ```
187///
188/// ### Windows
189///
190/// ```rust
191/// # #[cfg(target_os = "windows")]
192/// # {
193/// # use auto_launch::{AutoLaunch, WindowsEnableMode};
194/// # let app_name = "the-app";
195/// # let app_path = "/path/to/the-app";
196/// # let args = &["--minimized"];
197/// # let enable_mode = WindowsEnableMode::CurrentUser;
198/// AutoLaunch::new(app_name, app_path, enable_mode, args);
199/// # }
200/// ```
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct AutoLaunch {
203    /// The application name
204    pub(crate) app_name: String,
205
206    /// The application executable path (absolute path will be better)
207    pub(crate) app_path: String,
208
209    /// Args passed to the binary on startup
210    pub(crate) args: Vec<String>,
211
212    #[cfg(target_os = "linux")]
213    /// Launch mode for Linux (XDG Autostart or systemd)
214    pub(crate) launch_mode: LinuxLaunchMode,
215
216    #[cfg(target_os = "macos")]
217    /// Launch mode for macOS (Launch Agent or AppleScript)
218    pub(crate) launch_mode: MacOSLaunchMode,
219
220    #[cfg(target_os = "macos")]
221    /// Bundle identifiers
222    pub(crate) bundle_identifiers: Vec<String>,
223
224    #[cfg(target_os = "macos")]
225    /// Extra config in plist file for Launch Agent
226    pub(crate) agent_extra_config: String,
227
228    #[cfg(windows)]
229    pub(crate) enable_mode: WindowsEnableMode,
230}
231
232impl AutoLaunch {
233    /// check whether it is support the platform
234    ///
235    /// ## Usage
236    ///
237    /// ```rust
238    /// use auto_launch::AutoLaunch;
239    ///
240    /// dbg!(AutoLaunch::is_support());
241    /// ```
242    pub fn is_support() -> bool {
243        cfg!(any(
244            target_os = "linux",
245            target_os = "macos",
246            target_os = "windows",
247        ))
248    }
249
250    /// get the application name
251    pub fn get_app_name(&self) -> &str {
252        &self.app_name
253    }
254
255    /// get the application path
256    pub fn get_app_path(&self) -> &str {
257        &self.app_path
258    }
259
260    /// get the args
261    pub fn get_args(&self) -> &[String] {
262        &self.args
263    }
264}
265
266#[derive(Debug, Default, Clone)]
267/// AutoLaunch Builder helps to eliminate the constructor difference
268/// on various platforms.
269///
270/// ## Notes
271///
272/// The builder will not check whether the app_path matches the platform-specify file path.
273///
274/// ## Usage
275///
276/// ```rust
277/// use auto_launch::*;
278///
279/// # fn example() -> std::result::Result<(), Box<dyn std::error::Error>> {
280/// let auto = AutoLaunchBuilder::new()
281///     .set_app_name("the-app")
282///     .set_app_path("/path/to/the-app")
283///     .set_macos_launch_mode(MacOSLaunchMode::LaunchAgent)
284///     .set_args(&["--minimized"])
285///     .build()?;
286///
287/// auto.enable()?;
288/// auto.is_enabled()?;
289///
290/// auto.disable()?;
291/// auto.is_enabled()?;
292/// # Ok(())
293/// # }
294/// ```
295pub struct AutoLaunchBuilder {
296    pub app_name: Option<String>,
297
298    pub app_path: Option<String>,
299
300    pub macos_launch_mode: MacOSLaunchMode,
301
302    pub bundle_identifiers: Option<Vec<String>>,
303
304    pub agent_extra_config: Option<String>,
305
306    pub windows_enable_mode: WindowsEnableMode,
307
308    pub linux_launch_mode: LinuxLaunchMode,
309
310    pub args: Option<Vec<String>>,
311}
312
313/// Determines how the auto launch is enabled on Linux.
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub enum LinuxLaunchMode {
316    /// Use XDG Autostart (.desktop file in ~/.config/autostart/)
317    XdgAutostart,
318    /// Use systemd user service (~/.config/systemd/user/)
319    Systemd,
320}
321
322impl Default for LinuxLaunchMode {
323    fn default() -> Self {
324        Self::XdgAutostart
325    }
326}
327
328/// Determines how the auto launch is enabled on macOS.
329#[derive(Debug, Clone, Copy, PartialEq, Eq)]
330pub enum MacOSLaunchMode {
331    /// Use Launch Agent (plist file in ~/Library/LaunchAgents/)
332    LaunchAgent,
333    /// Use AppleScript to add login item
334    AppleScript,
335    /// User SMAppService API to enable the auto launch (macOS 13+)
336    SMAppService,
337}
338
339impl Default for MacOSLaunchMode {
340    fn default() -> Self {
341        Self::LaunchAgent
342    }
343}
344
345/// Determines how the auto launch is enabled on Windows.
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347pub enum WindowsEnableMode {
348    /// Dynamically tries to enable the auto launch for the system (admin privileges required),
349    /// fallbacks to the current user if there is no permission to modify the system registry.
350    Dynamic,
351    /// Enables the auto launch for the current user only. Does not require admin permissions.
352    CurrentUser,
353    /// Enables the auto launch for all users. Requires admin permissions.
354    System,
355}
356
357impl Default for WindowsEnableMode {
358    fn default() -> Self {
359        Self::Dynamic
360    }
361}
362
363impl AutoLaunchBuilder {
364    pub fn new() -> AutoLaunchBuilder {
365        AutoLaunchBuilder::default()
366    }
367
368    /// Set the `app_name`
369    pub fn set_app_name(&mut self, name: &str) -> &mut Self {
370        self.app_name = Some(name.into());
371        self
372    }
373
374    /// Set the `app_path`
375    pub fn set_app_path(&mut self, path: &str) -> &mut Self {
376        self.app_path = Some(path.into());
377        self
378    }
379
380    /// Set the [`MacOSLaunchMode`].
381    /// This setting only works on macOS
382    pub fn set_macos_launch_mode(&mut self, mode: MacOSLaunchMode) -> &mut Self {
383        self.macos_launch_mode = mode;
384        self
385    }
386
387    /// Set the `use_launch_agent` (deprecated: use `set_macos_launch_mode` instead)
388    /// This setting only works on macOS
389    #[deprecated(since = "0.6.0", note = "Use `set_macos_launch_mode` instead")]
390    pub fn set_use_launch_agent(&mut self, use_launch_agent: bool) -> &mut Self {
391        self.macos_launch_mode = if use_launch_agent {
392            MacOSLaunchMode::LaunchAgent
393        } else {
394            MacOSLaunchMode::AppleScript
395        };
396        self
397    }
398
399    /// Set the `bundle_identifiers`
400    /// This setting only works on macOS
401    pub fn set_bundle_identifiers(&mut self, bundle_identifiers: &[impl AsRef<str>]) -> &mut Self {
402        self.bundle_identifiers = Some(
403            bundle_identifiers
404                .iter()
405                .map(|s| s.as_ref().to_string())
406                .collect(),
407        );
408        self
409    }
410
411    /// Set the `agent_extra_config`
412    /// This setting only works on macOS
413    pub fn set_agent_extra_config(&mut self, config: &str) -> &mut Self {
414        self.agent_extra_config = Some(config.into());
415        self
416    }
417
418    /// Set the [`WindowsEnableMode`].
419    /// This setting only works on Windows
420    pub fn set_windows_enable_mode(&mut self, mode: WindowsEnableMode) -> &mut Self {
421        self.windows_enable_mode = mode;
422        self
423    }
424
425    /// Set the [`LinuxLaunchMode`].
426    /// This setting only works on Linux
427    pub fn set_linux_launch_mode(&mut self, mode: LinuxLaunchMode) -> &mut Self {
428        self.linux_launch_mode = mode;
429        self
430    }
431
432    /// Set the args
433    pub fn set_args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {
434        self.args = Some(args.iter().map(|s| s.as_ref().to_string()).collect());
435        self
436    }
437
438    /// Construct a AutoLaunch instance
439    ///
440    /// ## Errors
441    ///
442    /// - `app_name` is none
443    /// - `app_path` is none
444    /// - Unsupported target OS
445    pub fn build(&self) -> Result<AutoLaunch> {
446        let default_str = String::new();
447        /*
448         * When SMAppService is used, app_name and app_path are ignored. This
449         * is because the SMAppService API is used to register the running app.
450         *
451         * We also need to check whether the os version is compatible with SMAppService.
452         */
453        let (app_name, app_path) = if self.macos_launch_mode == MacOSLaunchMode::SMAppService {
454            let info = os_info::get();
455            match info.version() {
456                os_info::Version::Semantic(major, _, _) => {
457                    if *major < 13 {
458                        return Err(Error::UnsupportedOS);
459                    }
460                }
461                _ => return Err(Error::UnsupportedOS),
462            };
463
464            (
465                self.app_name.as_ref().unwrap_or(&default_str),
466                self.app_path.as_ref().unwrap_or(&default_str),
467            )
468        } else {
469            (
470                self.app_name.as_ref().ok_or(Error::AppNameNotSpecified)?,
471                self.app_path.as_ref().ok_or(Error::AppPathNotSpecified)?,
472            )
473        };
474        let args = self.args.clone().unwrap_or_default();
475        let bundle_identifiers = self.bundle_identifiers.clone().unwrap_or_default();
476        let agent_extra_config = self.agent_extra_config.as_ref().map_or("", |v| v);
477
478        #[cfg(target_os = "linux")]
479        return Ok(AutoLaunch::new(
480            app_name,
481            app_path,
482            self.linux_launch_mode,
483            &args,
484        ));
485        #[cfg(target_os = "macos")]
486        return Ok(AutoLaunch::new(
487            app_name,
488            app_path,
489            self.macos_launch_mode,
490            &args,
491            &bundle_identifiers,
492            agent_extra_config,
493        ));
494        #[cfg(target_os = "windows")]
495        return Ok(AutoLaunch::new(
496            app_name,
497            app_path,
498            self.windows_enable_mode,
499            &args,
500        ));
501
502        #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
503        return Err(Error::UnsupportedOS);
504    }
505}