1use std::fmt::{Display, Formatter};
2use std::path::Path;
3
4#[derive(Debug)]
5pub enum StartupError {
6 Io(std::io::Error),
7 Command(String),
8 UnsupportedPlatform,
9}
10
11impl Display for StartupError {
12 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
13 match self {
14 Self::Io(error) => write!(f, "io error: {error}"),
15 Self::Command(error) => write!(f, "command error: {error}"),
16 Self::UnsupportedPlatform => write!(f, "unsupported platform"),
17 }
18 }
19}
20
21impl std::error::Error for StartupError {}
22
23impl From<std::io::Error> for StartupError {
24 fn from(value: std::io::Error) -> Self {
25 Self::Io(value)
26 }
27}
28
29#[cfg(target_os = "windows")]
30const RUN_SUBKEY: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
31#[cfg(target_os = "windows")]
32const VALUE_NAME: &str = "Nex";
33#[cfg(target_os = "windows")]
34const LEGACY_VALUE_NAME: &str = "SwiftFind";
35const STARTUP_ARG: &str = "--background";
36
37pub fn startup_command_for_executable(executable_path: &Path) -> Result<String, StartupError> {
38 if executable_path.as_os_str().is_empty() {
39 return Err(StartupError::Command(
40 "executable path is empty".to_string(),
41 ));
42 }
43 if !executable_path.exists() {
44 return Err(StartupError::Command(format!(
45 "executable path does not exist: {}",
46 executable_path.display()
47 )));
48 }
49 if !executable_path.is_file() {
50 return Err(StartupError::Command(format!(
51 "executable path is not a file: {}",
52 executable_path.display()
53 )));
54 }
55
56 Ok(format!(
57 "\"{}\" {}",
58 executable_path.to_string_lossy(),
59 STARTUP_ARG
60 ))
61}
62
63#[cfg(target_os = "windows")]
64pub fn is_enabled() -> Result<bool, StartupError> {
65 use windows_sys::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_SUCCESS};
66 use windows_sys::Win32::System::Registry::{
67 RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER, KEY_QUERY_VALUE,
68 };
69
70 let subkey = to_wide(RUN_SUBKEY);
71 let value_name = to_wide(VALUE_NAME);
72 let legacy_value_name = to_wide(LEGACY_VALUE_NAME);
73 let mut key = std::ptr::null_mut();
74 let status = unsafe {
75 RegOpenKeyExW(
76 HKEY_CURRENT_USER,
77 subkey.as_ptr(),
78 0,
79 KEY_QUERY_VALUE,
80 &mut key,
81 )
82 };
83
84 if status == ERROR_FILE_NOT_FOUND {
85 return Ok(false);
86 }
87 if status != ERROR_SUCCESS {
88 return Err(registry_error("query run key", status));
89 }
90
91 let query_value_exists = |value_name: &[u16]| {
92 let mut value_type = 0_u32;
93 let mut size = 0_u32;
94 unsafe {
95 RegQueryValueExW(
96 key,
97 value_name.as_ptr(),
98 std::ptr::null(),
99 &mut value_type,
100 std::ptr::null_mut(),
101 &mut size,
102 )
103 }
104 };
105 let status = query_value_exists(&value_name);
106 let legacy_status = if status == ERROR_FILE_NOT_FOUND {
107 query_value_exists(&legacy_value_name)
108 } else {
109 ERROR_FILE_NOT_FOUND
110 };
111 unsafe {
112 RegCloseKey(key);
113 }
114
115 if status == ERROR_FILE_NOT_FOUND && legacy_status == ERROR_FILE_NOT_FOUND {
116 return Ok(false);
117 }
118 if status != ERROR_SUCCESS {
119 if status != ERROR_FILE_NOT_FOUND {
120 return Err(registry_error("query run value", status));
121 }
122 }
123 if legacy_status != ERROR_SUCCESS && legacy_status != ERROR_FILE_NOT_FOUND {
124 return Err(registry_error("query legacy run value", legacy_status));
125 }
126
127 Ok(true)
128}
129
130#[cfg(target_os = "windows")]
131pub fn set_enabled(enabled: bool, executable_path: &Path) -> Result<(), StartupError> {
132 use windows_sys::Win32::Foundation::{ERROR_FILE_NOT_FOUND, ERROR_SUCCESS};
133 use windows_sys::Win32::System::Registry::{
134 RegCloseKey, RegCreateKeyExW, RegDeleteValueW, RegOpenKeyExW, RegSetValueExW,
135 HKEY_CURRENT_USER, KEY_SET_VALUE, REG_SZ,
136 };
137
138 let subkey = to_wide(RUN_SUBKEY);
139 let value_name = to_wide(VALUE_NAME);
140 let legacy_value_name = to_wide(LEGACY_VALUE_NAME);
141
142 if enabled {
143 let value = startup_command_for_executable(executable_path)?;
144 let mut key = std::ptr::null_mut();
145 let status = unsafe {
146 RegCreateKeyExW(
147 HKEY_CURRENT_USER,
148 subkey.as_ptr(),
149 0,
150 std::ptr::null(),
151 0,
152 KEY_SET_VALUE,
153 std::ptr::null(),
154 &mut key,
155 std::ptr::null_mut(),
156 )
157 };
158 if status != ERROR_SUCCESS {
159 return Err(registry_error("create/open run key", status));
160 }
161
162 let value_wide = to_wide(&value);
163 let status = unsafe {
164 RegSetValueExW(
165 key,
166 value_name.as_ptr(),
167 0,
168 REG_SZ,
169 value_wide.as_ptr() as *const u8,
170 (value_wide.len() * std::mem::size_of::<u16>()) as u32,
171 )
172 };
173 unsafe {
174 let _ = RegDeleteValueW(key, legacy_value_name.as_ptr());
175 RegCloseKey(key);
176 }
177
178 if status != ERROR_SUCCESS {
179 return Err(registry_error("set run value", status));
180 }
181 return Ok(());
182 }
183
184 let mut key = std::ptr::null_mut();
185 let status = unsafe {
186 RegOpenKeyExW(
187 HKEY_CURRENT_USER,
188 subkey.as_ptr(),
189 0,
190 KEY_SET_VALUE,
191 &mut key,
192 )
193 };
194 if status == ERROR_FILE_NOT_FOUND {
195 return Ok(());
196 }
197 if status != ERROR_SUCCESS {
198 return Err(registry_error("open run key for delete", status));
199 }
200
201 let status = unsafe { RegDeleteValueW(key, value_name.as_ptr()) };
202 let legacy_status = unsafe { RegDeleteValueW(key, legacy_value_name.as_ptr()) };
203 unsafe {
204 RegCloseKey(key);
205 }
206 if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND)
207 && (legacy_status == ERROR_SUCCESS || legacy_status == ERROR_FILE_NOT_FOUND)
208 {
209 return Ok(());
210 }
211 if status != ERROR_SUCCESS && status != ERROR_FILE_NOT_FOUND {
212 return Err(registry_error("delete run value", status));
213 }
214
215 Err(registry_error("delete legacy run value", legacy_status))
216}
217
218#[cfg(not(target_os = "windows"))]
219pub fn is_enabled() -> Result<bool, StartupError> {
220 Err(StartupError::UnsupportedPlatform)
221}
222
223#[cfg(not(target_os = "windows"))]
224pub fn set_enabled(_enabled: bool, _executable_path: &Path) -> Result<(), StartupError> {
225 Err(StartupError::UnsupportedPlatform)
226}
227
228#[cfg(target_os = "windows")]
229fn to_wide(value: &str) -> Vec<u16> {
230 value.encode_utf16().chain(std::iter::once(0)).collect()
231}
232
233#[cfg(target_os = "windows")]
234fn registry_error(action: &str, status: u32) -> StartupError {
235 StartupError::Command(format!("{action} failed with code {status}"))
236}