auto_launcher/
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_launcher::{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_launcher::{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_launcher::{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_launcher::*;
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("Unsupported target os")]
139    UnsupportedOS,
140    #[error(transparent)]
141    Io(#[from] std::io::Error),
142}
143
144pub type Result<T> = std::result::Result<T, Error>;
145
146#[cfg(target_os = "linux")]
147mod linux;
148#[cfg(target_os = "macos")]
149mod macos;
150#[cfg(target_os = "windows")]
151mod windows;
152
153/// The parameters of `AutoLaunch::new` are different on each platform.
154///
155/// ### Linux
156///
157/// ```rust
158/// # #[cfg(target_os = "linux")]
159/// # {
160/// # use auto_launcher::{AutoLaunch, LinuxLaunchMode};
161/// # let app_name = "the-app";
162/// # let app_path = "/path/to/the-app";
163/// # let launch_mode = LinuxLaunchMode::XdgAutostart;
164/// # let args = &["--minimized"];
165/// AutoLaunch::new(app_name, app_path, launch_mode, args);
166/// # }
167/// ```
168///
169/// ### Macos
170///
171/// ```rust
172/// # #[cfg(target_os = "macos")]
173/// # {
174/// # use auto_launcher::{AutoLaunch, MacOSLaunchMode};
175/// # let app_name = "the-app";
176/// # let app_path = "/path/to/the-app";
177/// # let launch_mode = MacOSLaunchMode::LaunchAgent;
178/// # let args = &["--minimized"];
179/// # let bundle_identifiers = &["com.github.auto-launch-test"];
180/// AutoLaunch::new(app_name, app_path, launch_mode, args, bundle_identifiers, "");
181/// # }
182/// ```
183///
184/// ### Windows
185///
186/// ```rust
187/// # #[cfg(target_os = "windows")]
188/// # {
189/// # use auto_launcher::{AutoLaunch, WindowsEnableMode};
190/// # let app_name = "the-app";
191/// # let app_path = "/path/to/the-app";
192/// # let args = &["--minimized"];
193/// # let enable_mode = WindowsEnableMode::CurrentUser;
194/// AutoLaunch::new(app_name, app_path, enable_mode, args);
195/// # }
196/// ```
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct AutoLaunch {
199    /// The application name
200    pub(crate) app_name: String,
201
202    /// The application executable path (absolute path will be better)
203    pub(crate) app_path: String,
204
205    /// Args passed to the binary on startup
206    pub(crate) args: Vec<String>,
207
208    #[cfg(target_os = "linux")]
209    /// Launch mode for Linux (XDG Autostart or systemd)
210    pub(crate) launch_mode: LinuxLaunchMode,
211
212    #[cfg(target_os = "macos")]
213    /// Launch mode for macOS (Launch Agent or AppleScript)
214    pub(crate) launch_mode: MacOSLaunchMode,
215
216    #[cfg(target_os = "macos")]
217    /// Bundle identifiers
218    pub(crate) bundle_identifiers: Vec<String>,
219
220    #[cfg(target_os = "macos")]
221    /// Extra config in plist file for Launch Agent
222    pub(crate) agent_extra_config: String,
223
224    #[cfg(windows)]
225    pub(crate) enable_mode: WindowsEnableMode,
226}
227
228impl AutoLaunch {
229    /// check whether it is support the platform
230    ///
231    /// ## Usage
232    ///
233    /// ```rust
234    /// use auto_launcher::AutoLaunch;
235    ///
236    /// dbg!(AutoLaunch::is_support());
237    /// ```
238    pub fn is_support() -> bool {
239        cfg!(any(
240            target_os = "linux",
241            target_os = "macos",
242            target_os = "windows",
243        ))
244    }
245
246    /// get the application name
247    pub fn get_app_name(&self) -> &str {
248        &self.app_name
249    }
250
251    /// get the application path
252    pub fn get_app_path(&self) -> &str {
253        &self.app_path
254    }
255
256    /// get the args
257    pub fn get_args(&self) -> &[String] {
258        &self.args
259    }
260}
261
262#[derive(Debug, Default, Clone)]
263/// AutoLaunch Builder helps to eliminate the constructor difference
264/// on various platforms.
265///
266/// ## Notes
267///
268/// The builder will not check whether the app_path matches the platform-specify file path.
269///
270/// ## Usage
271///
272/// ```rust
273/// use auto_launcher::*;
274///
275/// # fn example() -> std::result::Result<(), Box<dyn std::error::Error>> {
276/// let auto = AutoLaunchBuilder::new()
277///     .set_app_name("the-app")
278///     .set_app_path("/path/to/the-app")
279///     .set_macos_launch_mode(MacOSLaunchMode::LaunchAgent)
280///     .set_args(&["--minimized"])
281///     .build()?;
282///
283/// auto.enable()?;
284/// auto.is_enabled()?;
285///
286/// auto.disable()?;
287/// auto.is_enabled()?;
288/// # Ok(())
289/// # }
290/// ```
291pub struct AutoLaunchBuilder {
292    pub app_name: Option<String>,
293
294    pub app_path: Option<String>,
295
296    pub macos_launch_mode: MacOSLaunchMode,
297
298    pub bundle_identifiers: Option<Vec<String>>,
299
300    pub agent_extra_config: Option<String>,
301
302    pub windows_enable_mode: WindowsEnableMode,
303
304    pub linux_launch_mode: LinuxLaunchMode,
305
306    pub args: Option<Vec<String>>,
307}
308
309/// Determines how the auto launch is enabled on Linux.
310#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub enum LinuxLaunchMode {
312    /// Use XDG Autostart (.desktop file in ~/.config/autostart/)
313    XdgAutostart,
314    /// Use systemd user service (~/.config/systemd/user/)
315    Systemd,
316}
317
318impl Default for LinuxLaunchMode {
319    fn default() -> Self {
320        Self::XdgAutostart
321    }
322}
323
324/// Determines how the auto launch is enabled on macOS.
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub enum MacOSLaunchMode {
327    /// Use Launch Agent (plist file in ~/Library/LaunchAgents/)
328    LaunchAgent,
329    /// Use AppleScript to add login item
330    AppleScript,
331}
332
333impl Default for MacOSLaunchMode {
334    fn default() -> Self {
335        Self::LaunchAgent
336    }
337}
338
339/// Determines how the auto launch is enabled on Windows.
340#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub enum WindowsEnableMode {
342    /// Dynamically tries to enable the auto launch for the system (admin privileges required),
343    /// fallbacks to the current user if there is no permission to modify the system registry.
344    Dynamic,
345    /// Enables the auto launch for the current user only. Does not require admin permissions.
346    CurrentUser,
347    /// Enables the auto launch for all users. Requires admin permissions.
348    System,
349}
350
351impl Default for WindowsEnableMode {
352    fn default() -> Self {
353        Self::Dynamic
354    }
355}
356
357impl AutoLaunchBuilder {
358    pub fn new() -> AutoLaunchBuilder {
359        AutoLaunchBuilder::default()
360    }
361
362    /// Set the `app_name`
363    pub fn set_app_name(&mut self, name: &str) -> &mut Self {
364        self.app_name = Some(name.into());
365        self
366    }
367
368    /// Set the `app_path`
369    pub fn set_app_path(&mut self, path: &str) -> &mut Self {
370        self.app_path = Some(path.into());
371        self
372    }
373
374    /// Set the [`MacOSLaunchMode`].
375    /// This setting only works on macOS
376    pub fn set_macos_launch_mode(&mut self, mode: MacOSLaunchMode) -> &mut Self {
377        self.macos_launch_mode = mode;
378        self
379    }
380
381    /// Set the `use_launch_agent` (deprecated: use `set_macos_launch_mode` instead)
382    /// This setting only works on macOS
383    #[deprecated(since = "0.6.0", note = "Use `set_macos_launch_mode` instead")]
384    pub fn set_use_launch_agent(&mut self, use_launch_agent: bool) -> &mut Self {
385        self.macos_launch_mode = if use_launch_agent {
386            MacOSLaunchMode::LaunchAgent
387        } else {
388            MacOSLaunchMode::AppleScript
389        };
390        self
391    }
392
393    /// Set the `bundle_identifiers`
394    /// This setting only works on macOS
395    pub fn set_bundle_identifiers(&mut self, bundle_identifiers: &[impl AsRef<str>]) -> &mut Self {
396        self.bundle_identifiers = Some(
397            bundle_identifiers
398                .iter()
399                .map(|s| s.as_ref().to_string())
400                .collect(),
401        );
402        self
403    }
404
405    /// Set the `agent_extra_config`
406    /// This setting only works on macOS
407    pub fn set_agent_extra_config(&mut self, config: &str) -> &mut Self {
408        self.agent_extra_config = Some(config.into());
409        self
410    }
411
412    /// Set the [`WindowsEnableMode`].
413    /// This setting only works on Windows
414    pub fn set_windows_enable_mode(&mut self, mode: WindowsEnableMode) -> &mut Self {
415        self.windows_enable_mode = mode;
416        self
417    }
418
419    /// Set the [`LinuxLaunchMode`].
420    /// This setting only works on Linux
421    pub fn set_linux_launch_mode(&mut self, mode: LinuxLaunchMode) -> &mut Self {
422        self.linux_launch_mode = mode;
423        self
424    }
425
426    /// Set the args
427    pub fn set_args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {
428        self.args = Some(args.iter().map(|s| s.as_ref().to_string()).collect());
429        self
430    }
431
432    /// Construct a AutoLaunch instance
433    ///
434    /// ## Errors
435    ///
436    /// - `app_name` is none
437    /// - `app_path` is none
438    /// - Unsupported target OS
439    pub fn build(&self) -> Result<AutoLaunch> {
440        let app_name = self.app_name.as_ref().ok_or(Error::AppNameNotSpecified)?;
441        let app_path = self.app_path.as_ref().ok_or(Error::AppPathNotSpecified)?;
442        let args = self.args.clone().unwrap_or_default();
443        let bundle_identifiers = self.bundle_identifiers.clone().unwrap_or_default();
444        let agent_extra_config = self.agent_extra_config.as_ref().map_or("", |v| v);
445
446        #[cfg(target_os = "linux")]
447        return Ok(AutoLaunch::new(
448            app_name,
449            app_path,
450            self.linux_launch_mode,
451            &args,
452        ));
453        #[cfg(target_os = "macos")]
454        return Ok(AutoLaunch::new(
455            app_name,
456            app_path,
457            self.macos_launch_mode,
458            &args,
459            &bundle_identifiers,
460            agent_extra_config,
461        ));
462        #[cfg(target_os = "windows")]
463        return Ok(AutoLaunch::new(
464            app_name,
465            app_path,
466            self.windows_enable_mode,
467            &args,
468        ));
469
470        #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
471        return Err(Error::UnsupportedOS);
472    }
473}