kamft_desktop 0.0.4

Kamft desktop utilities including screenshot capture and WeChat Work integration
use crate::core::log;
use crate::wxwork::send_to_wecom;
use xcap::Monitor;
use std::io::Cursor;
use std::sync::atomic::{AtomicBool, Ordering};

/// 将 RGBA 像素编码为 PNG 字节
fn encode_png(width: u32, height: u32, rgba: &[u8]) -> Result<Vec<u8>, String> {
    let mut buf = Cursor::new(Vec::new());
    {
        let mut encoder = png::Encoder::new(&mut buf, width, height);
        encoder.set_color(png::ColorType::Rgba);
        encoder.set_depth(png::BitDepth::Eight);
        let mut writer = encoder
            .write_header()
            .map_err(|e| format!("PNG 写入头失败: {}", e))?;
        writer
            .write_image_data(rgba)
            .map_err(|e| format!("PNG 写入数据失败: {}", e))?;
    }
    Ok(buf.into_inner())
}

/// 防止同时执行多次截图上传(pub 供 auto_capture 模块访问)
pub static CAPTURING: AtomicBool = AtomicBool::new(false);

/// 获取所有显示器的截图,返回 (标签, PNG字节) 列表
fn capture_all_monitors() -> Result<Vec<(String, Vec<u8>)>, String> {
    let monitors = Monitor::all().map_err(|e| format!("获取显示器列表失败: {}", e))?;

    log::info!("检测到 {} 个显示器:", monitors.len());
    for m in &monitors {
        let id = m.id().unwrap_or(0);
        let x = m.x().unwrap_or(0);
        let y = m.y().unwrap_or(0);
        let w = m.width().unwrap_or(0);
        let h = m.height().unwrap_or(0);
        log::info!(
            "  - id={}: ({}, {}) -> ({}, {}), {}x{}",
            id, x, y, x + w as i32, y + h as i32, w, h,
        );
    }

    if monitors.is_empty() {
        return Err("未检测到任何显示器".into());
    }

    let mut results: Vec<(String, Vec<u8>)> = Vec::new();

    for monitor in &monitors {
        let id = monitor.id().unwrap_or(0);
        let label = format!("id={}", id);

        let image = monitor
            .capture_image()
            .map_err(|e| format!("截图失败 ({}): {}", label, e))?;

        // 转为 PNG 字节
        let (w, h) = (image.width(), image.height());
        let rgba = image.into_raw();
        let bytes =
            encode_png(w, h, &rgba).map_err(|e| format!("编码 PNG 失败 ({}): {}", label, e))?;

        // 企业微信限制 2MB
        let size_mb = bytes.len() as f64 / (1024.0 * 1024.0);
        log::info!(
            "截图 {}: ({}, {}) {}x{} 大小 {:.2} MB",
            label,
            monitor.x().unwrap_or(0),
            monitor.y().unwrap_or(0),
            monitor.width().unwrap_or(0),
            monitor.height().unwrap_or(0),
            size_mb,
        );

        if bytes.len() > 2 * 1024 * 1024 {
            log::warn!(
                "警告: {} 截图超过 2MB ({:.2} MB),企业微信可能拒绝",
                label,
                size_mb
            );
        }

        results.push((label, bytes));
    }

    Ok(results)
}

/// 执行截图并发送(pub 供 auto_capture 模块复用)
pub fn do_capture_and_send() {
    let images = match capture_all_monitors() {
        Ok(p) => {
            log::info!("共截取 {} 个显示器", p.len());
            p
        }
        Err(e) => {
            log::warn!("截图失败: {}", e);
            CAPTURING.store(false, Ordering::SeqCst);
            return;
        }
    };

    std::thread::spawn(move || {
        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .expect("创建 tokio runtime 失败");

        let result = rt.block_on(send_to_wecom(&images));

        match result {
            Ok(()) => log::info!("=== 截图发送完成 ==="),
            Err(e) => log::warn!("发送失败: {}", e),
        }

        CAPTURING.store(false, Ordering::SeqCst);
    });
}

/// F3 触发:全屏/多屏幕截图 → base64 发送到企业微信机器人
pub fn capture_and_upload() {
    if CAPTURING.swap(true, Ordering::SeqCst) {
        log::info!("截图发送正在进行中,跳过本次触发");
        return;
    }

    log::info!("=== 开始截图 (F3) ===");
    do_capture_and_send();
}