Skip to content

Commit 87b024b

Browse files
only21milclaude
andcommitted
fix(desktop): explain Hyprland SnapShot activation timeouts
When T3 Code never appeared before the 3 second activation deadline, the event socket's read timeout surfaced as "Resource temporarily unavailable (os error 11)" instead of the helper's own explanation. Map the expired read (WouldBlock or TimedOut) and a zero remaining wait to "T3 Code did not become visible." Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent bd6940b commit 87b024b

1 file changed

Lines changed: 43 additions & 10 deletions

File tree

  • native/hyprland-snap-shot/src

‎native/hyprland-snap-shot/src/ipc.rs‎

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::Result;
22
use serde::{Deserialize, Serialize};
33
use std::{
4-
io::{BufRead, BufReader, Read, Write},
4+
io::{BufRead, BufReader, ErrorKind, Read, Write},
55
os::unix::net::UnixStream,
66
path::PathBuf,
77
time::{Duration, Instant},
@@ -161,18 +161,32 @@ pub fn activate(pid: u32, title: &str) -> Result<()> {
161161
}
162162
return Ok(());
163163
}
164-
events.get_ref().set_read_timeout(Some(
165-
deadline
166-
.checked_duration_since(Instant::now())
167-
.ok_or("T3 Code did not become visible.")?,
168-
))?;
169-
let mut event = Vec::new();
170-
if events.by_ref().take(8193).read_until(b'\n', &mut event)? == 0 || event.len() > 8192 {
171-
return Err("Hyprland's event stream closed or returned an invalid event.".into());
172-
}
164+
next_event(&mut events, deadline)?;
173165
}
174166
}
175167

168+
/// Waits for one Hyprland event, or explains that T3 Code never appeared before `deadline`.
169+
fn next_event(events: &mut BufReader<UnixStream>, deadline: Instant) -> Result<()> {
170+
let not_visible = "T3 Code did not become visible.";
171+
let left = deadline.checked_duration_since(Instant::now());
172+
events.get_ref().set_read_timeout(Some(
173+
left.filter(|left| !left.is_zero()).ok_or(not_visible)?,
174+
))?;
175+
let mut event = Vec::new();
176+
let read = events.by_ref().take(8193).read_until(b'\n', &mut event);
177+
// The read timeout ends at the deadline, and Linux reports its expiry as WouldBlock.
178+
if read
179+
.as_ref()
180+
.is_err_and(|error| matches!(error.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut))
181+
{
182+
return Err(not_visible.into());
183+
}
184+
if read? == 0 || event.len() > 8192 {
185+
return Err("Hyprland's event stream closed or returned an invalid event.".into());
186+
}
187+
Ok(())
188+
}
189+
176190
#[cfg(test)]
177191
mod tests {
178192
use super::*;
@@ -212,6 +226,25 @@ mod tests {
212226
);
213227
}
214228
#[test]
229+
fn event_wait_explains_an_activation_timeout() {
230+
let (socket, mut hyprland) = UnixStream::pair().unwrap();
231+
// A quiet socket reports the same WouldBlock as an expired read timeout, without waiting.
232+
socket.set_nonblocking(true).unwrap();
233+
let mut events = BufReader::new(socket);
234+
let deadline = Instant::now() + Duration::from_secs(3);
235+
assert_eq!(
236+
next_event(&mut events, deadline).unwrap_err().to_string(),
237+
"T3 Code did not become visible."
238+
);
239+
hyprland.write_all(b"openwindow>>1,1,t3,T3 Code\n").unwrap();
240+
assert!(next_event(&mut events, deadline).is_ok());
241+
drop(hyprland);
242+
assert_eq!(
243+
next_event(&mut events, deadline).unwrap_err().to_string(),
244+
"Hyprland's event stream closed or returned an invalid event."
245+
);
246+
}
247+
#[test]
215248
fn rectangles_accept_negative_origins_not_invalid_sizes() {
216249
assert!(window(1, "t").bounds().valid());
217250
assert!(

0 commit comments

Comments
 (0)