git.lucas.co / cce-remote
remote trackpad and keyboard server
git clone https://git.lucas.co/cce-remote.git

src/winstream.rs (2.9K)

 1 //! Consumer for the compositor's window-stream socket — the first-choice
 2 //! frame source. The compositor pushes damage-driven RGBA frames of the
 3 //! focused window (`window focused` subscription follows focus server-side,
 4 //! works off-viewport/occluded, and a truly idle window sends nothing but a
 5 //! ≤15s keepalive). Frames land in the latest-wins slot (`stream::Slot`);
 6 //! encoding happens at send time, not here.
 7 
 8 use std::io::{BufRead, BufReader, Read, Write};
 9 use std::os::unix::net::UnixStream;
10 use std::time::Duration;
11 
12 const MAX_FRAME_BYTES: u32 = 64 * 1024 * 1024;
13 
14 fn stream_socket_path() -> String {
15     let display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
16     format!("/tmp/cce-stream-{display}.sock")
17 }
18 
19 pub struct Reader {
20     sock: BufReader<UnixStream>,
21 }
22 
23 impl Reader {
24     pub fn connect() -> Result<Self, String> {
25         let path = stream_socket_path();
26         let sock = UnixStream::connect(&path).map_err(|e| format!("{path}: {e}"))?;
27         // The compositor keepalives every ≤15s; 20s of silence means it's
28         // gone. This timeout is also what bounds how long a stopped
29         // producer thread lingers.
30         sock.set_read_timeout(Some(Duration::from_secs(20))).ok();
31         let mut sock = BufReader::new(sock);
32         sock.get_mut()
33             .write_all(b"window focused\n")
34             .map_err(|e| e.to_string())?;
35         Ok(Self { sock })
36     }
37 
38     /// The next frame. Err = the source is unavailable/broke (caller falls
39     /// back or gives up) — including a read timeout, which given the
40     /// keepalive cadence means a dead compositor, not an idle window.
41     pub fn next(&mut self) -> Result<crate::stream::Payload, String> {
42         let mut header = String::new();
43         loop {
44             header.clear();
45             if self.sock.read_line(&mut header).map_err(|e| e.to_string())? == 0 {
46                 return Err("stream socket closed".into());
47             }
48             let mut it = header.split_ascii_whitespace();
49             if it.next() != Some("frame") {
50                 continue;
51             }
52             let (Some(w), Some(h), Some(len)) = (
53                 it.next().and_then(|v| v.parse::<u32>().ok()),
54                 it.next().and_then(|v| v.parse::<u32>().ok()),
55                 it.next().and_then(|v| v.parse::<u32>().ok()),
56             ) else {
57                 return Err(format!("bad frame header: {header:?}"));
58             };
59             if len != w.saturating_mul(h).saturating_mul(4) || len > MAX_FRAME_BYTES {
60                 return Err(format!("implausible frame: {header:?}"));
61             }
62             let mut rgba = vec![0u8; len as usize];
63             self.sock.read_exact(&mut rgba).map_err(|e| e.to_string())?;
64             return Ok(crate::stream::Payload::Raw {
65                 data: rgba,
66                 w,
67                 h,
68                 stride: w * 4,
69                 rgb: (0, 1, 2),
70             });
71         }
72     }
73 }