|
| 1 | +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] |
| 2 | +use std::io::{Read, Write}; |
| 3 | +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] |
| 4 | +use std::net::{Ipv4Addr, TcpListener, TcpStream}; |
| 5 | + |
| 6 | +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] |
| 7 | +const RELAUNCH_SIGNAL: &[u8] = b"show"; |
| 8 | +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] |
| 9 | +const RELAUNCH_PORT_FILE: &str = "relaunch.port"; |
| 10 | +#[cfg(target_os = "macos")] |
| 11 | +const SINGLE_INSTANCE_FILE: &str = "single-instance.lock"; |
| 12 | + |
| 13 | +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] |
| 14 | +fn relaunch_port_path() -> std::path::PathBuf { |
| 15 | + crate::defaults::get_data_path().join(RELAUNCH_PORT_FILE) |
| 16 | +} |
| 17 | + |
| 18 | +#[cfg(any(target_os = "linux", target_os = "windows"))] |
| 19 | +pub(crate) fn single_instance_key() -> String { |
| 20 | + crate::APP_NAME.to_string() |
| 21 | +} |
| 22 | + |
| 23 | +#[cfg(target_os = "macos")] |
| 24 | +pub(crate) fn single_instance_key() -> String { |
| 25 | + crate::defaults::get_data_path() |
| 26 | + .join(SINGLE_INSTANCE_FILE) |
| 27 | + .to_string_lossy() |
| 28 | + .into_owned() |
| 29 | +} |
| 30 | + |
| 31 | +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] |
| 32 | +pub(crate) fn notify_running_instance() -> Result<(), String> { |
| 33 | + let port_contents = |
| 34 | + std::fs::read_to_string(relaunch_port_path()).map_err(|e| format!("read port: {e}"))?; |
| 35 | + let port: u16 = port_contents |
| 36 | + .trim() |
| 37 | + .parse() |
| 38 | + .map_err(|e| format!("parse port: {e}"))?; |
| 39 | + |
| 40 | + let mut stream = |
| 41 | + TcpStream::connect((Ipv4Addr::LOCALHOST, port)).map_err(|e| format!("connect: {e}"))?; |
| 42 | + stream |
| 43 | + .write_all(RELAUNCH_SIGNAL) |
| 44 | + .map_err(|e| format!("send signal: {e}")) |
| 45 | +} |
| 46 | + |
| 47 | +#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] |
| 48 | +pub(crate) fn start_listener<F>(on_relaunch: F) -> Result<(), String> |
| 49 | +where |
| 50 | + F: Fn() + Send + Sync + 'static, |
| 51 | +{ |
| 52 | + let listener = |
| 53 | + TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).map_err(|e| format!("bind listener: {e}"))?; |
| 54 | + let port = listener |
| 55 | + .local_addr() |
| 56 | + .map_err(|e| format!("resolve listener addr: {e}"))? |
| 57 | + .port(); |
| 58 | + |
| 59 | + std::fs::write(relaunch_port_path(), port.to_string()) |
| 60 | + .map_err(|e| format!("write port: {e}"))?; |
| 61 | + |
| 62 | + let on_relaunch = std::sync::Arc::new(on_relaunch); |
| 63 | + std::thread::spawn(move || { |
| 64 | + for incoming in listener.incoming() { |
| 65 | + match incoming { |
| 66 | + Ok(mut stream) => { |
| 67 | + let mut buffer = [0_u8; 16]; |
| 68 | + match stream.read(&mut buffer) { |
| 69 | + Ok(bytes_read) if bytes_read > 0 => { |
| 70 | + if buffer[..bytes_read].starts_with(RELAUNCH_SIGNAL) { |
| 71 | + on_relaunch(); |
| 72 | + } |
| 73 | + } |
| 74 | + Ok(_) => {} |
| 75 | + Err(err) => log::warn!("Failed to read relaunch signal: {err}"), |
| 76 | + } |
| 77 | + } |
| 78 | + Err(err) => { |
| 79 | + log::warn!("Relaunch listener stopped: {err}"); |
| 80 | + break; |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + }); |
| 85 | + |
| 86 | + Ok(()) |
| 87 | +} |
0 commit comments