1use crate::state::{SignalPaths, unix_ms};
3use serde::{Deserialize, Serialize};
4
5pub const DISPLAY_PROTOCOL: u32 = 9;
6#[derive(Clone, Debug, Serialize, Deserialize)]
7pub struct DisplayLease {
8 pub protocol: u32,
9 pub build: String,
10 pub pid: u32,
11 pub epoch: String,
12 pub state: String,
13 pub ts_ms: u64,
14}
15impl DisplayLease {
16 pub fn compatible(&self, now: u64) -> bool {
17 self.protocol == DISPLAY_PROTOCOL
18 && self.build == env!("CARGO_PKG_VERSION")
19 && self.ts_ms <= now
20 && now - self.ts_ms < 1500
21 }
22 pub fn ready(&self, now: u64) -> bool {
23 self.compatible(now) && self.state == "ready"
24 }
25}
26#[derive(Clone, Debug, Serialize, Deserialize)]
27pub struct DisplayAck {
28 pub epoch: String,
29 pub call_id: String,
30 pub action_id: u64,
31 pub ts_ms: u64,
32}
33impl SignalPaths {
34 pub fn display_lease(&self) -> Option<DisplayLease> {
35 serde_json::from_slice(&std::fs::read(self.dir.join("display.json")).ok()?).ok()
36 }
37 pub fn write_display<T: Serialize>(&self, name: &str, value: &T) -> std::io::Result<()> {
38 std::fs::create_dir_all(&self.dir)?;
39 let target = self.dir.join(name);
40 let tmp = target.with_extension(format!("{}.tmp", std::process::id()));
41 std::fs::write(&tmp, serde_json::to_vec(value)?)?;
42 std::fs::rename(tmp, target)
43 }
44 pub fn acknowledged(&self, call: &str, action: u64, epoch: &str, since: u64) -> bool {
45 let ack = std::fs::read(self.dir.join(format!("ack-{call}.json")))
46 .ok()
47 .and_then(|v| serde_json::from_slice::<DisplayAck>(&v).ok());
48 ack.is_some_and(|a| {
49 a.call_id == call
50 && a.action_id == action
51 && a.epoch == epoch
52 && a.ts_ms >= since
53 && a.ts_ms <= unix_ms()
54 && unix_ms().saturating_sub(a.ts_ms) < 1500
55 })
56 }
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 #[test]
63 fn lease_requires_current_version_fresh_visible_frame() {
64 let mut l = DisplayLease {
65 protocol: DISPLAY_PROTOCOL,
66 build: env!("CARGO_PKG_VERSION").into(),
67 pid: 1,
68 epoch: "test".into(),
69 state: "ready".into(),
70 ts_ms: 100,
71 };
72 assert!(l.ready(101));
73 assert!(!l.ready(99));
74 assert!(!l.ready(1600));
75 l.state = "capture".into();
76 assert!(!l.ready(101));
77 assert!(l.compatible(101));
78 l.state = "closed".into();
79 assert!(!l.ready(101));
80 l.protocol = 2;
81 assert!(!l.compatible(101));
82 }
83}