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}