main.rs (8198B)
1 use std::{ 2 error::Error, 3 io, 4 sync::{Mutex, OnceLock}, 5 }; 6 7 use tauri::WindowEvent; 8 use tauri_plugin_shell::{ShellExt, process::CommandChild}; 9 10 struct IunaSidecar(Mutex<Option<CommandChild>>); 11 struct IunaSleepInhibitor(Mutex<Option<SleepInhibitor>>); 12 13 static SIDECAR: OnceLock<IunaSidecar> = OnceLock::new(); 14 static SLEEP_INHIBITOR: OnceLock<IunaSleepInhibitor> = OnceLock::new(); 15 16 fn main() { 17 tauri::Builder::default() 18 .plugin(tauri_plugin_shell::init()) 19 .setup(|app| { 20 let (mut events, child) = app 21 .shell() 22 .sidecar("iuna-sidecar") 23 .map_err(setup_error)? 24 .spawn() 25 .map_err(setup_error)?; 26 27 keep_system_awake_while_node_runs(); 28 29 tauri::async_runtime::spawn(async move { while events.recv().await.is_some() {} }); 30 31 let sidecar = SIDECAR.get_or_init(|| IunaSidecar(Mutex::new(None))); 32 *sidecar.0.lock().expect("sidecar mutex poisoned") = Some(child); 33 Ok(()) 34 }) 35 .on_window_event(|_window, event| { 36 if matches!(event, WindowEvent::CloseRequested { .. }) { 37 stop_sidecar(); 38 } 39 }) 40 .build(tauri::generate_context!()) 41 .expect("error while building iuna desktop") 42 .run(|_app, event| { 43 if matches!( 44 event, 45 tauri::RunEvent::Exit | tauri::RunEvent::ExitRequested { .. } 46 ) { 47 stop_sidecar(); 48 } 49 }); 50 } 51 52 fn stop_sidecar() { 53 if let Some(sidecar) = SIDECAR.get() { 54 if let Some(child) = sidecar.0.lock().expect("sidecar mutex poisoned").take() { 55 let _ = child.kill(); 56 } 57 } 58 release_system_awake_inhibitor(); 59 } 60 61 fn setup_error(error: impl Error + Send + Sync + 'static) -> Box<dyn Error> { 62 Box::new(io::Error::other(error)) 63 } 64 65 fn keep_system_awake_while_node_runs() { 66 match SleepInhibitor::acquire() { 67 Ok(inhibitor) => { 68 let store = SLEEP_INHIBITOR.get_or_init(|| IunaSleepInhibitor(Mutex::new(None))); 69 *store.0.lock().expect("sleep inhibitor mutex poisoned") = Some(inhibitor); 70 } 71 Err(error) => { 72 eprintln!("iuna desktop could not prevent system sleep: {error}"); 73 } 74 } 75 } 76 77 fn release_system_awake_inhibitor() { 78 let Some(store) = SLEEP_INHIBITOR.get() else { 79 return; 80 }; 81 let _ = store 82 .0 83 .lock() 84 .expect("sleep inhibitor mutex poisoned") 85 .take(); 86 } 87 88 #[cfg(target_os = "macos")] 89 struct SleepInhibitor { 90 assertion_id: u32, 91 } 92 93 #[cfg(target_os = "macos")] 94 impl SleepInhibitor { 95 fn acquire() -> io::Result<Self> { 96 macos_sleep::prevent_idle_system_sleep("iuna node is running") 97 .map(|assertion_id| Self { assertion_id }) 98 } 99 } 100 101 #[cfg(target_os = "macos")] 102 impl Drop for SleepInhibitor { 103 fn drop(&mut self) { 104 let _ = macos_sleep::release_assertion(self.assertion_id); 105 } 106 } 107 108 #[cfg(target_os = "macos")] 109 mod macos_sleep { 110 use std::{ 111 ffi::{CString, c_char, c_void}, 112 io, ptr, 113 }; 114 115 type CFStringRef = *const c_void; 116 type IOReturn = i32; 117 118 const K_CF_STRING_ENCODING_UTF8: u32 = 0x0800_0100; 119 const K_IOPM_ASSERTION_LEVEL_ON: u32 = 255; 120 const K_IO_RETURN_SUCCESS: IOReturn = 0; 121 122 #[link(name = "CoreFoundation", kind = "framework")] 123 unsafe extern "C" { 124 fn CFStringCreateWithCString( 125 alloc: *const c_void, 126 c_str: *const c_char, 127 encoding: u32, 128 ) -> CFStringRef; 129 fn CFRelease(cf: *const c_void); 130 } 131 132 #[link(name = "IOKit", kind = "framework")] 133 unsafe extern "C" { 134 fn IOPMAssertionCreateWithName( 135 assertion_type: CFStringRef, 136 assertion_level: u32, 137 assertion_name: CFStringRef, 138 assertion_id: *mut u32, 139 ) -> IOReturn; 140 fn IOPMAssertionRelease(assertion_id: u32) -> IOReturn; 141 } 142 143 pub fn prevent_idle_system_sleep(reason: &str) -> io::Result<u32> { 144 let assertion_type = cf_string("PreventUserIdleSystemSleep")?; 145 let assertion_name = cf_string(reason)?; 146 let mut assertion_id = 0_u32; 147 let result = unsafe { 148 IOPMAssertionCreateWithName( 149 assertion_type, 150 K_IOPM_ASSERTION_LEVEL_ON, 151 assertion_name, 152 &mut assertion_id, 153 ) 154 }; 155 unsafe { 156 CFRelease(assertion_type); 157 CFRelease(assertion_name); 158 } 159 if result == K_IO_RETURN_SUCCESS { 160 Ok(assertion_id) 161 } else { 162 Err(io::Error::other(format!( 163 "IOPMAssertionCreateWithName failed with status {result}" 164 ))) 165 } 166 } 167 168 pub fn release_assertion(assertion_id: u32) -> io::Result<()> { 169 let result = unsafe { IOPMAssertionRelease(assertion_id) }; 170 if result == K_IO_RETURN_SUCCESS { 171 Ok(()) 172 } else { 173 Err(io::Error::other(format!( 174 "IOPMAssertionRelease failed with status {result}" 175 ))) 176 } 177 } 178 179 fn cf_string(value: &str) -> io::Result<CFStringRef> { 180 let value = CString::new(value) 181 .map_err(|error| io::Error::new(io::ErrorKind::InvalidInput, error))?; 182 let cf_string = unsafe { 183 CFStringCreateWithCString(ptr::null(), value.as_ptr(), K_CF_STRING_ENCODING_UTF8) 184 }; 185 if cf_string.is_null() { 186 Err(io::Error::other("CFStringCreateWithCString returned null")) 187 } else { 188 Ok(cf_string) 189 } 190 } 191 } 192 193 #[cfg(target_os = "windows")] 194 struct SleepInhibitor; 195 196 #[cfg(target_os = "windows")] 197 impl SleepInhibitor { 198 fn acquire() -> io::Result<Self> { 199 windows_sleep::prevent_idle_system_sleep()?; 200 Ok(Self) 201 } 202 } 203 204 #[cfg(target_os = "windows")] 205 impl Drop for SleepInhibitor { 206 fn drop(&mut self) { 207 let _ = windows_sleep::clear_sleep_requirement(); 208 } 209 } 210 211 #[cfg(target_os = "windows")] 212 mod windows_sleep { 213 use std::io; 214 215 const ES_CONTINUOUS: u32 = 0x8000_0000; 216 const ES_SYSTEM_REQUIRED: u32 = 0x0000_0001; 217 218 #[link(name = "kernel32")] 219 unsafe extern "system" { 220 fn SetThreadExecutionState(es_flags: u32) -> u32; 221 } 222 223 pub fn prevent_idle_system_sleep() -> io::Result<()> { 224 set_execution_state(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) 225 } 226 227 pub fn clear_sleep_requirement() -> io::Result<()> { 228 set_execution_state(ES_CONTINUOUS) 229 } 230 231 fn set_execution_state(flags: u32) -> io::Result<()> { 232 let previous = unsafe { SetThreadExecutionState(flags) }; 233 if previous == 0 { 234 Err(io::Error::last_os_error()) 235 } else { 236 Ok(()) 237 } 238 } 239 } 240 241 #[cfg(target_os = "linux")] 242 struct SleepInhibitor { 243 child: Option<std::process::Child>, 244 } 245 246 #[cfg(target_os = "linux")] 247 impl SleepInhibitor { 248 fn acquire() -> io::Result<Self> { 249 let parent_pid = std::process::id().to_string(); 250 let child = std::process::Command::new("systemd-inhibit") 251 .args([ 252 "--what=sleep", 253 "--mode=block", 254 "--why=iuna node is running", 255 "sh", 256 "-c", 257 "trap 'exit 0' TERM; while kill -0 \"$1\" 2>/dev/null; do sleep 30 & wait $!; done", 258 "iuna-sleep-inhibitor", 259 &parent_pid, 260 ]) 261 .stdin(std::process::Stdio::null()) 262 .stdout(std::process::Stdio::null()) 263 .stderr(std::process::Stdio::null()) 264 .spawn()?; 265 Ok(Self { child: Some(child) }) 266 } 267 } 268 269 #[cfg(target_os = "linux")] 270 impl Drop for SleepInhibitor { 271 fn drop(&mut self) { 272 if let Some(mut child) = self.child.take() { 273 let _ = child.kill(); 274 let _ = child.wait(); 275 } 276 } 277 } 278 279 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] 280 struct SleepInhibitor; 281 282 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))] 283 impl SleepInhibitor { 284 fn acquire() -> io::Result<Self> { 285 Ok(Self) 286 } 287 }