visual-cortex 0.4.0

Watch screen regions and receive typed events over async streams when patterns match: OCR, template matching, and pixel conditions on live captures.
//! Manual smoke tests — run on macOS with Screen Recording permission granted:
//!   cargo test -p visual-cortex --test real_capture -- --ignored
#![cfg(target_os = "macos")]

use std::sync::mpsc;
use std::time::Duration;

use visual_cortex::{list_targets, FrameSource, ScapSource, Target, TargetInfo};

#[tokio::test]
#[ignore = "requires macOS Screen Recording permission and a live display"]
async fn captures_one_display_frame() {
    let mut src = ScapSource::new(&Target::display(0), true).expect("start capturer");
    let frame = src.capture_frame().await.expect("capture a frame");
    assert!(frame.width() > 0 && frame.height() > 0);
}

/// Try to pull one frame from the window with this id, with a timeout —
/// hidden/utility windows never render, so a blocking wait would hang.
fn try_window_frame(id: u32) -> Option<(u32, u32)> {
    let (tx, rx) = mpsc::channel();
    std::thread::spawn(move || {
        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .expect("runtime");
        if let Ok(mut src) = ScapSource::new(&Target::window_id(id), false) {
            if let Ok(frame) = rt.block_on(src.capture_frame()) {
                let _ = tx.send((frame.width(), frame.height()));
            }
        }
    });
    rx.recv_timeout(Duration::from_secs(3)).ok()
}

/// Cross-process window capture — the operation that never worked through
/// upstream scap 0.0.8 (issue #5) and how the gap survived milestone 3:
/// only display capture was live-tested.
#[test]
#[ignore = "requires macOS Screen Recording permission and at least one visible window"]
fn captures_one_window_frame() {
    let mut candidates: Vec<(u32, String)> = list_targets()
        .into_iter()
        .filter_map(|t| match t {
            TargetInfo::Window { id, title } if !title.is_empty() => Some((id, title)),
            _ => None,
        })
        .collect();
    assert!(!candidates.is_empty(), "no windows enumerable");
    // Prefer windows likely to be visibly rendering.
    candidates.sort_by_key(|(_, t)| !(t.contains("Chrome") || t.contains("YouTube")));

    for (id, title) in candidates.into_iter().take(10) {
        if let Some((w, h)) = try_window_frame(id) {
            println!("captured {w}x{h} from window {id} {title:?}");
            assert!(w > 0 && h > 0);
            return;
        }
    }
    panic!("no candidate window delivered a frame within its timeout");
}