|
1 | 1 | use crate::Result; |
2 | 2 | use serde::{Deserialize, Serialize}; |
3 | 3 | use std::{ |
4 | | - io::{BufRead, BufReader, Read, Write}, |
| 4 | + io::{BufRead, BufReader, ErrorKind, Read, Write}, |
5 | 5 | os::unix::net::UnixStream, |
6 | 6 | path::PathBuf, |
7 | 7 | time::{Duration, Instant}, |
@@ -161,18 +161,32 @@ pub fn activate(pid: u32, title: &str) -> Result<()> { |
161 | 161 | } |
162 | 162 | return Ok(()); |
163 | 163 | } |
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)?; |
173 | 165 | } |
174 | 166 | } |
175 | 167 |
|
| 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 | + |
176 | 190 | #[cfg(test)] |
177 | 191 | mod tests { |
178 | 192 | use super::*; |
@@ -212,6 +226,25 @@ mod tests { |
212 | 226 | ); |
213 | 227 | } |
214 | 228 | #[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] |
215 | 248 | fn rectangles_accept_negative_origins_not_invalid_sizes() { |
216 | 249 | assert!(window(1, "t").bounds().valid()); |
217 | 250 | assert!( |
|
0 commit comments