Skip to main content

nex_core/
startup.rs

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}