1use crate::{AutoLaunch, LinuxLaunchMode, Result};
2use std::{fs, io::Write, path::PathBuf};
3
4impl AutoLaunch {
6 pub fn new(
16 app_name: &str,
17 app_path: &str,
18 launch_mode: LinuxLaunchMode,
19 args: &[impl AsRef<str>],
20 ) -> AutoLaunch {
21 AutoLaunch {
22 app_name: app_name.into(),
23 app_path: app_path.into(),
24 launch_mode,
25 args: args.iter().map(|s| s.as_ref().to_string()).collect(),
26 }
27 }
28
29 pub fn enable(&self) -> Result<()> {
38 match self.launch_mode {
39 LinuxLaunchMode::XdgAutostart => self.enable_xdg_autostart(),
40 LinuxLaunchMode::Systemd => self.enable_systemd(),
41 }
42 }
43
44 fn enable_xdg_autostart(&self) -> Result<()> {
46 let data = format!(
47 "[Desktop Entry]\n\
48 Type=Application\n\
49 Version=1.0\n\
50 Name={}\n\
51 Comment={} startup script\n\
52 Exec={} {}\n\
53 StartupNotify=false\n\
54 Terminal=false",
55 self.app_name,
56 self.app_name,
57 self.app_path,
58 self.args.join(" ")
59 );
60
61 let dir = get_xdg_autostart_dir();
62 if !dir.exists() {
63 fs::create_dir_all(&dir).or_else(|e| {
64 if e.kind() == std::io::ErrorKind::AlreadyExists {
65 Ok(())
66 } else {
67 Err(e)
68 }
69 })?;
70 }
71 let file_path = self.get_xdg_desktop_file();
72 let mut file = fs::OpenOptions::new()
73 .write(true)
74 .create(true)
75 .truncate(true)
76 .open(file_path)?;
77 file.write_all(data.as_bytes())?;
78 Ok(())
79 }
80
81 fn enable_systemd(&self) -> Result<()> {
83 let args_str = if self.args.is_empty() {
85 String::new()
86 } else {
87 format!(" {}", self.args.join(" "))
88 };
89
90 let data = format!(
91 "[Unit]\n\
92 Description={}\n\
93 After=default.target\n\
94 \n\
95 [Service]\n\
96 Type=simple\n\
97 ExecStart={}{}\n\
98 Restart=on-failure\n\
99 RestartSec=10\n\
100 \n\
101 [Install]\n\
102 WantedBy=default.target",
103 self.app_name, self.app_path, args_str
104 );
105
106 let dir = get_systemd_user_dir();
108 if !dir.exists() {
109 fs::create_dir_all(&dir).or_else(|e| {
110 if e.kind() == std::io::ErrorKind::AlreadyExists {
111 Ok(())
112 } else {
113 Err(e)
114 }
115 })?;
116 }
117
118 let service_file = self.get_systemd_service_file();
120 let mut file = fs::OpenOptions::new()
121 .write(true)
122 .create(true)
123 .truncate(true)
124 .open(&service_file)?;
125 file.write_all(data.as_bytes())?;
126
127 self.systemctl_enable()?;
129
130 Ok(())
131 }
132
133 fn systemctl_enable(&self) -> Result<()> {
135 let service_name = format!("{}.service", self.app_name);
136 let output = std::process::Command::new("systemctl")
137 .args(&["--user", "enable", &service_name])
138 .output()?;
139
140 if !output.status.success() {
141 return Err(std::io::Error::new(
142 std::io::ErrorKind::Other,
143 format!(
144 "Failed to enable systemd service: {}",
145 String::from_utf8_lossy(&output.stderr)
146 ),
147 )
148 .into());
149 }
150
151 Ok(())
152 }
153
154 pub fn disable(&self) -> Result<()> {
161 match self.launch_mode {
162 LinuxLaunchMode::XdgAutostart => self.disable_xdg_autostart(),
163 LinuxLaunchMode::Systemd => self.disable_systemd(),
164 }
165 }
166
167 fn disable_xdg_autostart(&self) -> Result<()> {
169 let file = self.get_xdg_desktop_file();
170 if file.exists() {
171 fs::remove_file(file)?;
172 }
173 Ok(())
174 }
175
176 fn disable_systemd(&self) -> Result<()> {
178 self.systemctl_disable()?;
180
181 let service_file = self.get_systemd_service_file();
183 if service_file.exists() {
184 fs::remove_file(service_file)?;
185 }
186
187 let _ = std::process::Command::new("systemctl")
189 .args(&["--user", "daemon-reload"])
190 .output();
191
192 Ok(())
193 }
194
195 fn systemctl_disable(&self) -> Result<()> {
197 let service_name = format!("{}.service", self.app_name);
198 let output = std::process::Command::new("systemctl")
199 .args(&["--user", "disable", &service_name])
200 .output()?;
201
202 if !output.status.success() {
204 let stderr = String::from_utf8_lossy(&output.stderr);
205 if !stderr.contains("No such file or directory") && !stderr.contains("not loaded") {
206 return Err(std::io::Error::new(
207 std::io::ErrorKind::Other,
208 format!("Failed to disable systemd service: {}", stderr),
209 )
210 .into());
211 }
212 }
213
214 Ok(())
215 }
216
217 pub fn is_enabled(&self) -> Result<bool> {
219 match self.launch_mode {
220 LinuxLaunchMode::XdgAutostart => Ok(self.get_xdg_desktop_file().exists()),
221 LinuxLaunchMode::Systemd => self.is_systemd_enabled(),
222 }
223 }
224
225 fn is_systemd_enabled(&self) -> Result<bool> {
227 let service_name = format!("{}.service", self.app_name);
228 let output = std::process::Command::new("systemctl")
229 .args(&["--user", "is-enabled", &service_name])
230 .output()?;
231
232 Ok(output.status.success())
237 }
238
239 fn get_xdg_desktop_file(&self) -> PathBuf {
241 get_xdg_autostart_dir().join(format!("{}.desktop", self.app_name))
242 }
243
244 fn get_systemd_service_file(&self) -> PathBuf {
246 get_systemd_user_dir().join(format!("{}.service", self.app_name))
247 }
248}
249
250fn get_xdg_autostart_dir() -> PathBuf {
252 dirs::home_dir().unwrap().join(".config").join("autostart")
253}
254
255fn get_systemd_user_dir() -> PathBuf {
257 dirs::home_dir()
258 .unwrap()
259 .join(".config")
260 .join("systemd")
261 .join("user")
262}