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 }