1use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum ClipboardEffect {
7 #[default]
8 Untouched,
9 Read,
10 Write,
11}
12
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
14pub struct InputEffects {
15 pub keyboard: bool,
16 pub pointer: bool,
17 pub clipboard: ClipboardEffect,
18 pub focus: bool,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum ActionPhase {
24 Preparing,
25 Executing,
26 Delivered,
27 Failed,
28 Cancelled,
29 Partial,
30}
31
32#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub struct ActionTarget {
35 pub window: Option<String>,
36 pub point: Option<[i32; 2]>,
37 pub bounds: Option<[i32; 4]>,
38 pub destination: Option<[i32; 2]>,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct ActionState {
43 pub id: u64,
44 pub seq: u64,
45 pub kind: String,
46 pub phase: ActionPhase,
47 pub effects: InputEffects,
48 pub target: ActionTarget,
49 pub ts_ms: u64,
50 pub expires_ms: u64,
51}
52
53impl ActionState {
54 pub fn active(&self) -> bool {
55 matches!(self.phase, ActionPhase::Preparing | ActionPhase::Executing)
56 }
57}
58
59#[derive(Default)]
61pub struct ActionCursor {
62 since_ms: u64,
63 seen: std::collections::HashMap<String, u64>,
64}
65
66impl ActionCursor {
67 pub fn new(since_ms: u64) -> Self {
68 Self {
69 since_ms,
70 seen: Default::default(),
71 }
72 }
73
74 pub fn take(&mut self, states: &[crate::state::SessionState], now: u64) -> Vec<ActionState> {
75 let mut out = Vec::new();
76 self.seen
77 .retain(|id, _| states.iter().any(|s| &s.call_id == id));
78 for state in states {
79 let seq = self.seen.entry(state.call_id.clone()).or_default();
80 for action in &state.recent_actions {
81 if action.seq > *seq
82 && action.ts_ms >= self.since_ms
83 && action.expires_ms > now
84 && action.phase == ActionPhase::Delivered
85 {
86 out.push(action.clone());
87 }
88 *seq = (*seq).max(action.seq);
89 }
90 }
91 out.sort_by_key(|a| a.ts_ms);
92 out
93 }
94}