Skip to main content

capture_demo/
capture_demo.rs

1//! Demonstration of the Synheart Sensor Agent event capture.
2//!
3//! This example shows how to:
4//! 1. Check for Input Monitoring permission
5//! 2. Create and start a collector
6//! 3. Receive and process events
7//! 4. Compute features from event windows
8//! 5. Generate HSI snapshots
9//!
10//! Run with: cargo run --example capture_demo
11//!
12//! Note: Requires Input Monitoring permission on macOS.
13//! Grant permission in System Preferences > Security & Privacy > Privacy > Input Monitoring
14
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::Arc;
17use std::time::Duration;
18
19use synheart_sensor_agent::{
20    collector::{check_permission, Collector, CollectorConfig, SensorEvent},
21    core::{compute_features, HsiBuilder, WindowManager},
22    transparency::TransparencyLog,
23    PRIVACY_DECLARATION,
24};
25
26fn main() {
27    println!("Synheart Sensor Agent - Capture Demo");
28    println!("=====================================");
29    println!();
30
31    // Display privacy declaration
32    println!("{PRIVACY_DECLARATION}");
33    println!();
34
35    // Check for permission
36    print!("Checking Input Monitoring permission... ");
37    if check_permission() {
38        println!("OK ✓");
39    } else {
40        println!("FAILED ✗");
41        println!();
42        println!("Please grant Input Monitoring permission:");
43        println!("1. Open System Preferences");
44        println!("2. Go to Security & Privacy > Privacy > Input Monitoring");
45        println!("3. Add this application");
46        println!("4. Restart this demo");
47        return;
48    }
49    println!();
50
51    // Create components
52    let config = CollectorConfig {
53        capture_keyboard: true,
54        capture_mouse: true,
55    };
56
57    let mut collector = Collector::new(config);
58    let mut window_manager = WindowManager::new(10, 300); // 10s windows, 5min session gap
59    let hsi_builder = HsiBuilder::new();
60    let transparency_log = TransparencyLog::new();
61
62    println!("Instance ID: {}", hsi_builder.instance_id());
63    println!();
64    println!("Starting capture for 30 seconds...");
65    println!("Try typing and moving your mouse!");
66    println!();
67
68    // Start collection
69    if let Err(e) = collector.start() {
70        eprintln!("Error starting collector: {e}");
71        return;
72    }
73
74    // Set up stop flag
75    let running = Arc::new(AtomicBool::new(true));
76    let r = running.clone();
77
78    // Set up Ctrl+C handler
79    ctrlc::set_handler(move || {
80        r.store(false, Ordering::SeqCst);
81    })
82    .expect("Error setting Ctrl+C handler");
83
84    // Run for 30 seconds
85    let start = std::time::Instant::now();
86    let receiver = collector.receiver().clone();
87    let mut event_count = 0;
88
89    while running.load(Ordering::SeqCst) && start.elapsed() < Duration::from_secs(30) {
90        // Receive events with timeout
91        match receiver.recv_timeout(Duration::from_millis(100)) {
92            Ok(event) => {
93                event_count += 1;
94
95                // Log event type
96                match &event {
97                    SensorEvent::Keyboard(e) => {
98                        transparency_log.record_keyboard_event();
99                        if event_count <= 10 || event_count % 50 == 0 {
100                            println!(
101                                "  Keyboard event: {} at {}",
102                                if e.is_key_down { "down" } else { "up" },
103                                e.timestamp.format("%H:%M:%S%.3f")
104                            );
105                        }
106                    }
107                    SensorEvent::Mouse(e) => {
108                        transparency_log.record_mouse_event();
109                        if event_count <= 10 || event_count % 100 == 0 {
110                            println!(
111                                "  Mouse event: {:?} at {}",
112                                e.event_type,
113                                e.timestamp.format("%H:%M:%S%.3f")
114                            );
115                        }
116                    }
117                }
118
119                // Process in window manager
120                window_manager.process_event(event);
121            }
122            Err(crossbeam_channel::RecvTimeoutError::Timeout) => {
123                // Check for window expiry
124                window_manager.check_window_expiry();
125            }
126            Err(crossbeam_channel::RecvTimeoutError::Disconnected) => {
127                break;
128            }
129        }
130
131        // Process completed windows
132        for window in window_manager.take_completed_windows() {
133            transparency_log.record_window_completed();
134
135            let features = compute_features(&window);
136            let snapshot = hsi_builder.build(&window, &features);
137
138            println!();
139            println!("=== Window Completed ===");
140            println!("  Duration: {:.1}s", window.duration_secs());
141            println!("  Keyboard events: {}", window.keyboard_events.len());
142            println!("  Mouse events: {}", window.mouse_events.len());
143            println!();
144            println!("  Keyboard Features:");
145            println!(
146                "    Typing rate: {:.2} keys/sec",
147                features.keyboard.typing_rate
148            );
149            println!("    Pause count: {}", features.keyboard.pause_count);
150            println!("    Burst index: {:.3}", features.keyboard.burst_index);
151            println!();
152            println!("  Mouse Features:");
153            println!(
154                "    Activity rate: {:.2} moves/sec",
155                features.mouse.mouse_activity_rate
156            );
157            println!("    Mean velocity: {:.2}", features.mouse.mean_velocity);
158            println!(
159                "    Click rate: {:.2} clicks/sec",
160                features.mouse.click_rate
161            );
162            println!();
163            println!("  Behavioral Signals:");
164            println!(
165                "    Interaction rhythm: {:.3}",
166                features.behavioral.interaction_rhythm
167            );
168            println!("    Friction: {:.3}", features.behavioral.friction);
169            println!(
170                "    Motor stability: {:.3}",
171                features.behavioral.motor_stability
172            );
173            println!(
174                "    Focus continuity: {:.3}",
175                features.behavioral.focus_continuity_proxy
176            );
177            println!();
178
179            // Show snippet of HSI JSON
180            let json = serde_json::to_string_pretty(&snapshot).unwrap();
181            println!("  HSI Snapshot (truncated):");
182            for line in json.lines().take(20) {
183                println!("    {line}");
184            }
185            println!("    ...");
186            println!();
187        }
188
189        // Show progress
190        if event_count > 0 && event_count % 200 == 0 {
191            let elapsed = start.elapsed().as_secs();
192            println!("  [{elapsed}/30s] Processed {event_count} events...");
193        }
194    }
195
196    // Stop collection
197    println!();
198    println!("Stopping capture...");
199    collector.stop();
200
201    // Flush remaining window
202    window_manager.flush();
203    for window in window_manager.take_completed_windows() {
204        println!("Final window: {} events", window.event_count());
205        transparency_log.record_window_completed();
206    }
207
208    // Final statistics
209    println!();
210    println!("{}", transparency_log.summary());
211    println!();
212    println!("Demo complete!");
213}