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}