native-devtools-mcp 0.10.1

MCP server for computer use & browser automation — screenshot, OCR, click, type, find_text, Chrome/Electron CDP, template matching. macOS, Windows & Android.
use super::device::AndroidDevice;

pub fn click(device: &mut AndroidDevice, x: f64, y: f64) -> Result<(), String> {
    device.shell_args(&["input", "tap", &x.to_string(), &y.to_string()])?;
    Ok(())
}

pub fn swipe(
    device: &mut AndroidDevice,
    start_x: f64,
    start_y: f64,
    end_x: f64,
    end_y: f64,
    duration_ms: Option<u32>,
) -> Result<(), String> {
    let sx = start_x.to_string();
    let sy = start_y.to_string();
    let ex = end_x.to_string();
    let ey = end_y.to_string();
    let mut cmd = vec!["input", "swipe", &sx, &sy, &ex, &ey];
    let ms_str;
    if let Some(ms) = duration_ms {
        ms_str = ms.to_string();
        cmd.push(&ms_str);
    }
    device.shell_args(&cmd)?;
    Ok(())
}

pub fn type_text(device: &mut AndroidDevice, text: &str) -> Result<(), String> {
    let escaped = escape_for_input(text);
    device.shell_args(&["input", "text", &escaped])?;
    Ok(())
}

pub fn press_key(device: &mut AndroidDevice, key: &str) -> Result<(), String> {
    device.shell_args(&["input", "keyevent", key])?;
    Ok(())
}

/// Escape text for `adb shell input text`. Spaces become `%s` and shell
/// metacharacters are backslash-escaped.
fn escape_for_input(text: &str) -> String {
    let mut result = String::with_capacity(text.len() * 2);
    for c in text.chars() {
        match c {
            ' ' => result.push_str("%s"),
            '\\' => result.push_str("\\\\"),
            '"' => result.push_str("\\\""),
            '\'' => result.push_str("\\'"),
            '&' => result.push_str("\\&"),
            '|' => result.push_str("\\|"),
            ';' => result.push_str("\\;"),
            '(' => result.push_str("\\("),
            ')' => result.push_str("\\)"),
            '<' => result.push_str("\\<"),
            '>' => result.push_str("\\>"),
            '`' => result.push_str("\\`"),
            '$' => result.push_str("\\$"),
            '!' => result.push_str("\\!"),
            _ => result.push(c),
        }
    }
    result
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_escape_spaces() {
        assert_eq!(escape_for_input("hello world"), "hello%sworld");
    }

    #[test]
    fn test_escape_special_chars() {
        assert_eq!(escape_for_input("a&b"), "a\\&b");
        assert_eq!(escape_for_input("it's"), "it\\'s");
        assert_eq!(escape_for_input("a\"b"), "a\\\"b");
        assert_eq!(escape_for_input("$HOME"), "\\$HOME");
        assert_eq!(escape_for_input("wow!"), "wow\\!");
    }

    #[test]
    fn test_escape_no_special() {
        assert_eq!(escape_for_input("hello123"), "hello123");
    }
}