context_live_view/
context_live_view.rs1use std::collections::VecDeque;
2use std::io::{self, Write};
3use std::sync::{Arc, Mutex};
4use std::thread;
5use std::time::{Duration, UNIX_EPOCH};
6
7use lcsa_core::{ContextApi, SignalEnvelope, SignalSupport, SignalType, StructuralSignal};
8
9const MAX_EVENTS: usize = 18;
10
11fn main() -> Result<(), Box<dyn std::error::Error>> {
12 let mut api = ContextApi::new()?;
13 let events = Arc::new(Mutex::new(VecDeque::<String>::new()));
14
15 for signal_type in [
16 SignalType::Clipboard,
17 SignalType::Selection,
18 SignalType::Focus,
19 ] {
20 if !matches!(api.signal_support(signal_type), SignalSupport::Supported) {
21 continue;
22 }
23
24 let events = Arc::clone(&events);
25 api.subscribe_enveloped(signal_type, move |envelope| {
26 let line = summarize_envelope(&envelope);
27 let mut queue = events.lock().expect("event queue mutex poisoned");
28 queue.push_front(line);
29 while queue.len() > MAX_EVENTS {
30 let _ = queue.pop_back();
31 }
32 })?;
33 }
34
35 loop {
36 draw_dashboard(&api, &events)?;
37 thread::sleep(Duration::from_millis(350));
38 }
39}
40
41fn draw_dashboard(
42 api: &ContextApi,
43 events: &Arc<Mutex<VecDeque<String>>>,
44) -> Result<(), Box<dyn std::error::Error>> {
45 print!("\x1B[2J\x1B[H");
46 println!("LCSA Live View");
47 println!(
48 "device={} platform={:?}",
49 api.device_context().device_name,
50 api.device_context().platform
51 );
52 println!();
53 println!("Signal Capabilities");
54 for signal_type in [
55 SignalType::Clipboard,
56 SignalType::Selection,
57 SignalType::Focus,
58 ] {
59 println!(
60 " {:<10} {:?}",
61 format!("{signal_type:?}"),
62 api.signal_support(signal_type)
63 );
64 }
65 println!();
66 println!("Recent Context Events");
67 println!(" (copy text, select text, or switch windows to see updates)");
68
69 let queue = events.lock().expect("event queue mutex poisoned");
70 if queue.is_empty() {
71 println!(" - waiting for signals...");
72 } else {
73 for line in queue.iter() {
74 println!(" - {line}");
75 }
76 }
77
78 io::stdout().flush()?;
79 Ok(())
80}
81
82fn summarize_envelope(envelope: &SignalEnvelope) -> String {
83 let secs = envelope
84 .emitted_at
85 .duration_since(UNIX_EPOCH)
86 .map(|duration| duration.as_secs())
87 .unwrap_or_default();
88
89 let summary = match &envelope.payload {
90 StructuralSignal::Clipboard(signal) => format!(
91 "clipboard type={:?} bytes={} source={}",
92 signal.content_type, signal.size_bytes, signal.source_app
93 ),
94 StructuralSignal::Selection(signal) => format!(
95 "selection type={:?} bytes={} source={}",
96 signal.content_type, signal.size_bytes, signal.source_app
97 ),
98 StructuralSignal::Focus(signal) => {
99 format!(
100 "focus target={:?} source={}",
101 signal.target, signal.source_app
102 )
103 }
104 StructuralSignal::Filesystem(signal) => format!(
105 "filesystem event={} action={:?}",
106 signal.event_name(),
107 signal.action
108 ),
109 };
110
111 format!("{secs} {summary}")
112}