gpui/platform/
single_instance.rs1use anyhow::Result;
6use std::path::PathBuf;
7
8#[derive(Debug)]
10pub struct AlreadyRunning;
11
12impl std::fmt::Display for AlreadyRunning {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 write!(f, "Another instance is already running")
15 }
16}
17
18impl std::error::Error for AlreadyRunning {}
19
20pub struct SingleInstance {
26 #[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))]
27 _listener: std::os::unix::net::UnixListener,
28 #[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))]
29 _socket_path: PathBuf,
30 #[cfg(target_os = "windows")]
31 _mutex: WindowsMutexHandle,
32}
33
34#[cfg(target_os = "windows")]
35struct WindowsMutexHandle {
36 handle: windows::Win32::Foundation::HANDLE,
37}
38
39#[cfg(target_os = "windows")]
40impl Drop for WindowsMutexHandle {
41 fn drop(&mut self) {
42 unsafe {
43 let _ = windows::Win32::Foundation::CloseHandle(self.handle);
44 }
45 }
46}
47
48#[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))]
49fn socket_path(app_id: &str) -> PathBuf {
50 let dir = std::env::var("XDG_RUNTIME_DIR")
51 .or_else(|_| std::env::var("TMPDIR"))
52 .unwrap_or_else(|_| "/tmp".to_string());
53 PathBuf::from(dir).join(format!("{}.sock", app_id))
54}
55
56impl SingleInstance {
57 pub fn acquire(app_id: &str) -> std::result::Result<Self, AlreadyRunning> {
62 Self::platform_acquire(app_id)
63 }
64
65 pub fn on_activate(&self, callback: Box<dyn Fn() + Send + 'static>) {
71 self.platform_on_activate(callback);
72 }
73}
74
75pub fn send_activate_to_existing(app_id: &str) -> Result<()> {
80 platform_send_activate(app_id)
81}
82
83#[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))]
84impl SingleInstance {
85 fn platform_acquire(app_id: &str) -> std::result::Result<Self, AlreadyRunning> {
86 use std::os::unix::net::UnixListener;
87
88 let path = socket_path(app_id);
89
90 if std::os::unix::net::UnixStream::connect(&path).is_ok() {
91 return Err(AlreadyRunning);
92 }
93
94 let _ = std::fs::remove_file(&path);
95 let listener = UnixListener::bind(&path).map_err(|_| AlreadyRunning)?;
96 listener.set_nonblocking(true).ok();
97
98 Ok(Self {
99 _listener: listener,
100 _socket_path: path,
101 })
102 }
103
104 fn platform_on_activate(&self, callback: Box<dyn Fn() + Send + 'static>) {
105 use std::io::Read;
106 use std::os::unix::net::UnixListener;
107
108 let listener = unsafe {
109 use std::os::unix::io::{AsRawFd, FromRawFd};
110 let fd = self._listener.as_raw_fd();
111 let dup_fd = libc::dup(fd);
112 if dup_fd < 0 {
113 return;
114 }
115 UnixListener::from_raw_fd(dup_fd)
116 };
117 listener.set_nonblocking(false).ok();
118
119 std::thread::spawn(move || {
120 for stream in listener.incoming() {
121 match stream {
122 Ok(mut stream) => {
123 let mut buf = [0u8; 64];
124 if let Ok(n) = stream.read(&mut buf) {
125 if n > 0 && &buf[..n.min(8)] == b"activate" {
126 callback();
127 }
128 }
129 }
130 Err(_) => break,
131 }
132 }
133 });
134 }
135}
136
137#[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))]
138impl Drop for SingleInstance {
139 fn drop(&mut self) {
140 let _ = std::fs::remove_file(&self._socket_path);
141 }
142}
143
144#[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))]
145fn platform_send_activate(app_id: &str) -> Result<()> {
146 use std::io::Write;
147 use std::os::unix::net::UnixStream;
148
149 let path = socket_path(app_id);
150 let mut stream = UnixStream::connect(&path)?;
151 stream.write_all(b"activate")?;
152 Ok(())
153}
154
155#[cfg(target_os = "windows")]
156impl SingleInstance {
157 fn platform_acquire(app_id: &str) -> std::result::Result<Self, AlreadyRunning> {
158 use windows::Win32::Foundation::ERROR_ALREADY_EXISTS;
159 use windows::Win32::Foundation::GetLastError;
160 use windows::Win32::System::Threading::CreateMutexW;
161 use windows::core::HSTRING;
162
163 let name = HSTRING::from(format!("Global\\{}", app_id));
164 unsafe {
165 let handle = CreateMutexW(None, true, &name).map_err(|_| AlreadyRunning)?;
166 if GetLastError() == ERROR_ALREADY_EXISTS {
167 let _ = windows::Win32::Foundation::CloseHandle(handle);
168 return Err(AlreadyRunning);
169 }
170 Ok(Self {
171 _mutex: WindowsMutexHandle { handle },
172 })
173 }
174 }
175
176 fn platform_on_activate(&self, _callback: Box<dyn Fn() + Send + 'static>) {}
177}
178
179#[cfg(target_os = "windows")]
180fn platform_send_activate(_app_id: &str) -> Result<()> {
181 Ok(())
182}