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                    SensorEvent::Shortcut(e) => {
118                        transparency_log.record_shortcut_event();
119                        if event_count <= 10 || event_count % 50 == 0 {
120                            println!(
121                                "  Shortcut event: {:?} at {}",
122                                e.shortcut_type,
123                                e.timestamp.format("%H:%M:%S%.3f")
124                            );
125                        }
126                    }
127                }
128
129                // Process in window manager
130                window_manager.process_event(event);
131            }
132            Err(crossbeam_channel::RecvTimeoutError::Timeout) => {
133                // Check for window expiry
134                window_manager.check_window_expiry();
135            }
136            Err(crossbeam_channel::RecvTimeoutError::Disconnected) => {
137                break;
138            }
139        }
140
141        // Process completed windows
142        for window in window_manager.take_completed_windows() {
143            transparency_log.record_window_completed();
144
145            let features = compute_features(&window);
146            let snapshot = hsi_builder.build(&window, &features);
147
148            println!();
149            println!("=== Window Completed ===");
150            println!("  Duration: {:.1}s", window.duration_secs());
151            println!("  Keyboard events: {}", window.keyboard_events.len());
152            println!("  Mouse events: {}", window.mouse_events.len());
153            println!();
154            println!("  Keyboard Features:");
155            println!(
156                "    Typing rate: {:.2} keys/sec",
157                features.keyboard.typing_rate
158            );
159            println!("    Pause count: {}", features.keyboard.pause_count);
160            println!("    Burst index: {:.3}", features.keyboard.burst_index);
161            println!();
162            println!("  Mouse Features:");
163            println!(
164                "    Activity rate: {:.2} moves/sec",
165                features.mouse.mouse_activity_rate
166            );
167            println!("    Mean velocity: {:.2}", features.mouse.mean_velocity);
168            println!(
169                "    Click rate: {:.2} clicks/sec",
170                features.mouse.click_rate
171            );
172            println!();
173            println!("  Behavioral Signals:");
174            println!(
175                "    Interaction rhythm: {:.3}",
176                features.behavioral.interaction_rhythm
177            );
178            println!("    Friction: {:.3}", features.behavioral.friction);
179            println!(
180                "    Motor stability: {:.3}",
181                features.behavioral.motor_stability
182            );
183            println!(
184                "    Focus continuity: {:.3}",
185                features.behavioral.focus_continuity_proxy
186            );
187            println!();
188
189            // Show snippet of HSI JSON
190            let json = serde_json::to_string_pretty(&snapshot).unwrap();
191            println!("  HSI Snapshot (truncated):");
192            for line in json.lines().take(20) {
193                println!("    {line}");
194            }
195            println!("    ...");
196            println!();
197        }
198
199        // Show progress
200        if event_count > 0 && event_count % 200 == 0 {
201            let elapsed = start.elapsed().as_secs();
202            println!("  [{elapsed}/30s] Processed {event_count} events...");
203        }
204    }
205
206    // Stop collection
207    println!();
208    println!("Stopping capture...");
209    collector.stop();
210
211    // Flush remaining window
212    window_manager.flush();
213    for window in window_manager.take_completed_windows() {
214        println!("Final window: {} events", window.event_count());
215        transparency_log.record_window_completed();
216    }
217
218    // Final statistics
219    println!();
220    println!("{}", transparency_log.summary());
221    println!();
222    println!("Demo complete!");
223}