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