iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

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 }