lingxia-devtool 0.11.1

Devtool runtime bridge and protocol helpers for LingXia apps
Documentation
use crate::util::{png_dimensions, png_response, run_async};
use lingxia_devtool_protocol::handlers;
use serde::Deserialize;
use serde_json::{Value, json};

pub(crate) fn handle_app_command(
    handler: &str,
    args: Option<Value>,
) -> Option<Result<Option<Value>, String>> {
    if !handler.starts_with("app.") {
        return None;
    }
    Some(handle_app_command_impl(handler, args))
}

#[derive(Default, Deserialize)]
struct AppScreenshotArgs {
    #[serde(default)]
    window_id: Option<String>,
}

fn handle_app_command_impl(handler: &str, args: Option<Value>) -> Result<Option<Value>, String> {
    match handler {
        handlers::app::DOCTOR => Ok(Some(build_app_doctor())),
        handlers::app::SCREENSHOT => {
            let parsed: AppScreenshotArgs = match args {
                Some(value) => serde_json::from_value(value)
                    .map_err(|e| format!("invalid args for {}: {}", handler, e))?,
                None => AppScreenshotArgs::default(),
            };
            let window_id = parsed.window_id;
            let (window, bytes) = run_async(lingxia::dev::take_app_screenshot_with_info(
                window_id.as_deref(),
            ))?;
            let (pixel_width, pixel_height) = png_dimensions(&bytes).unwrap_or_default();
            let scale_x =
                (window.width > 0).then(|| f64::from(pixel_width) / f64::from(window.width));
            let scale_y =
                (window.height > 0).then(|| f64::from(pixel_height) / f64::from(window.height));
            Ok(Some(png_response(
                "app",
                app_window_coordinate_space(),
                &bytes,
                [
                    ("window_id", json!(window.id)),
                    ("window", json!(window)),
                    ("content_width", json!(window.width)),
                    ("content_height", json!(window.height)),
                    ("scale_x", json!(scale_x)),
                    ("scale_y", json!(scale_y)),
                ],
            )))
        }
        handlers::app::WINDOWS => {
            let windows = run_async(lingxia::dev::list_app_windows())?;
            serde_json::to_value(windows)
                .map(Some)
                .map_err(|err| err.to_string())
        }
        handlers::app::MOUSE => {
            let request: lingxia::dev::AppMouseRequest = match args {
                Some(value) => serde_json::from_value(value)
                    .map_err(|e| format!("invalid args for {}: {}", handler, e))?,
                None => return Err(format!("missing args for {}", handler)),
            };
            let result = run_async(lingxia::dev::perform_app_mouse(request))?;
            serde_json::to_value(result)
                .map(Some)
                .map_err(|err| err.to_string())
        }
        handlers::app::KEYBOARD => {
            let request: lingxia::dev::AppKeyboardRequest = match args {
                Some(value) => serde_json::from_value(value)
                    .map_err(|e| format!("invalid args for {}: {}", handler, e))?,
                None => return Err(format!("missing args for {}", handler)),
            };
            let result = run_async(lingxia::dev::perform_app_keyboard(request))?;
            serde_json::to_value(result)
                .map(Some)
                .map_err(|err| err.to_string())
        }
        other => Err(format!("unknown app handler: {}", other)),
    }
}

fn build_app_doctor() -> Value {
    let platform = if cfg!(target_os = "windows") {
        "windows"
    } else if cfg!(target_os = "macos") {
        "macos"
    } else if cfg!(target_os = "android") {
        "android"
    } else if cfg!(target_os = "ios") {
        "ios"
    } else if cfg!(all(target_os = "linux", target_env = "ohos")) {
        "harmony"
    } else {
        "unknown"
    };
    let desktop = cfg!(any(target_os = "windows", target_os = "macos"));
    let modifiers = if cfg!(target_os = "windows") {
        json!({
            "supported": true,
            "reliability": "best_effort",
            "reason": "message injection cannot update GetKeyState for chorded shortcuts",
        })
    } else {
        json!({ "supported": desktop, "reliability": if desktop { "native" } else { "unsupported" } })
    };
    json!({
        "target": "app",
        "platform": platform,
        "capabilities": {
            "windows": { "supported": true },
            "screenshot": { "supported": true },
            "mouse": { "supported": desktop },
            "keyboard": { "supported": desktop },
            "keyboard_modifiers": modifiers,
        },
        "coordinate_spaces": {
            "window": app_window_coordinate_space(),
        }
    })
}

#[cfg(target_os = "windows")]
fn app_window_coordinate_space() -> &'static str {
    "window_content_pixels"
}

#[cfg(any(target_os = "macos", target_os = "ios"))]
fn app_window_coordinate_space() -> &'static str {
    "window_content_points"
}

#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "ios")))]
fn app_window_coordinate_space() -> &'static str {
    "screen_pixels"
}