focus-tracker 1.1.0

Cross-platform focus tracker for Linux (X11), macOS and Windows
Documentation
//! Advanced example showing all configuration options and features
//!
//! This example demonstrates:
//! - Custom FocusTrackerConfig with all available options
//! - Using track_focus with stop_signal for manual control
//! - Icon extraction and saving to files
//! - Custom polling intervals and icon sizes
//! - Proper signal handling and graceful shutdown
//!
//! Usage: cargo run --example advanced

use focus_tracker::{
    FocusTracker, FocusTrackerConfig, FocusTrackerResult, FocusedWindow, IconConfig, IgnoreRule,
    WindowTitleMatch,
};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};

fn save_icon_to_file(
    icon_data: &image::RgbaImage,
    filename: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    icon_data.save(filename)?;
    println!("💾 Icon saved: {}", filename);
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::DEBUG)
        .init();

    println!("🚀 Starting ADVANCED focus tracking example!");
    println!("   This demo showcases all configuration options and features.");
    println!("   Icons will be saved to examples/recorded_icons/");
    println!("   Press Ctrl+C for graceful shutdown.");
    println!();

    let config = FocusTrackerConfig::builder()
        .poll_interval(std::time::Duration::from_millis(50))?
        .icon(IconConfig::builder().size(64)?.build())
        // Filter out Explorer.EXE noise on Windows: the Alt-Tab "Task
        // Switching" overlay and titleless Explorer pseudo-windows are
        // both reported as focus events, but neither is a real
        // user-facing application context.
        .windows_ignore_rules([
            IgnoreRule::builder()
                .process_name("Explorer.EXE")
                .window_title(WindowTitleMatch::Exact("Task Switching".into()))
                .build(),
            IgnoreRule::builder()
                .process_name("Explorer.EXE")
                .window_title(WindowTitleMatch::Missing)
                .build(),
        ])
        .build();

    println!("⚙️  Configuration:");
    println!("   Poll interval: {:?}", config.poll_interval);
    println!(
        "   Icon size: {}x{}",
        config.icon.get_size_or_default(),
        config.icon.get_size_or_default()
    );
    println!();

    let tracker = FocusTracker::builder().config(config).build();

    let stop_signal = Arc::new(AtomicBool::new(false));
    let stop_signal_clone = stop_signal.clone();

    ctrlc::set_handler(move || {
        println!("\n🛑 Interrupt signal received, initiating graceful shutdown...");
        stop_signal_clone.store(true, Ordering::SeqCst);
    })?;

    let mut event_count = 0u64;
    let mut icons_saved = 0u64;
    let mut unique_processes = std::collections::HashSet::new();
    let start_time = std::time::Instant::now();

    println!("🎯 Focus tracking active! Switch between applications...");
    println!();

    let result = tracker
        .track_focus()
        .on_focus(|window: FocusedWindow| {
            event_count += 1;
            let count = event_count;

            let window_title = window
                .window_title
                .as_deref()
                .unwrap_or("Unknown")
                .to_string();
            let process_name = window.process_name.clone();

            unique_processes.insert(process_name.clone());

            let icon_result: FocusTrackerResult<()> = {
                println!("🔄 Focus Event #{}", count);
                println!("   📋 Title: {}", window_title);
                println!(
                    "   ⚙️  Process: {} (PID: {:?})",
                    process_name, window.process_id
                );

                if let Some(icon) = &window.icon {
                    let (width, height) = (icon.width(), icon.height());
                    println!("   🖼️  Icon: {}x{} pixels", width, height);

                    icons_saved += 1;
                    let saved = icons_saved;
                    let filename = format!(
                        "examples/recorded_icons/advanced_{:03}_{}.png",
                        saved,
                        process_name.replace("/", "_").replace(" ", "_")
                    );

                    match save_icon_to_file(icon, &filename) {
                        Ok(_) => println!("   ✅ Icon saved successfully"),
                        Err(e) => println!("   ❌ Failed to save icon: {}", e),
                    }
                } else {
                    println!("   🚫 No icon available");
                }

                println!("   ⏱️  Uptime: {:?}", start_time.elapsed());
                println!();

                Ok(())
            };

            async move { icon_result }
        })
        .stop_signal(&stop_signal)
        .call()
        .await;

    match result {
        Ok(_) => println!("✅ Focus tracking completed successfully"),
        Err(e) => println!("❌ Focus tracking error: {}", e),
    }

    println!();
    println!("📊 SESSION STATISTICS:");
    println!("   Total events: {}", event_count);
    println!("   Icons saved: {}", icons_saved);
    println!("   Unique processes: {}", unique_processes.len());
    println!("   Session duration: {:?}", start_time.elapsed());
    println!(
        "   Average events/min: {:.1}",
        event_count as f64 / start_time.elapsed().as_secs_f64() * 60.0
    );

    if !unique_processes.is_empty() {
        println!("   Processes seen:");
        for (i, process) in unique_processes.iter().enumerate() {
            println!("     {}. {}", i + 1, process);
        }
    }

    println!();
    println!("🎉 Advanced example completed!");
    println!("   Check examples/recorded_icons/ for saved icons");

    Ok(())
}