rust_droid/
device.rs

1use std::net::SocketAddrV4;
2use std::time::Duration;
3
4use adb_client::{ADBDeviceExt, ADBServer, ADBServerDevice};
5use image::DynamicImage;
6
7use crate::common::point::Point;
8use crate::error::{DroidError, Result};
9
10pub struct DeviceController {
11    device: ADBServerDevice,
12}
13
14impl DeviceController {
15    /// 创建一个新的设备控制器。
16    /// 如果 `device_identifier` 是 Some,则连接到指定设备。
17    /// 如果是 None,则自动连接到第一个可用的设备。
18    pub fn new(device_identifier: Option<&str>, adb_addr: SocketAddrV4) -> Result<Self> {
19        let mut server = ADBServer::new(adb_addr);
20
21        let devices = server
22            .devices()
23            .map_err(|e| DroidError::AdbError(e.to_string()))?;
24        if devices.is_empty() {
25            return Err(DroidError::DeviceNotFound);
26        }
27
28        let target_identifier = match device_identifier {
29            Some(identifier) => devices
30                .iter()
31                .find(|d| d.identifier == identifier)
32                .map(|d| d.identifier.clone())
33                .ok_or(DroidError::DeviceNotFound)?,
34            None => devices[0].identifier.clone(),
35        };
36
37        log::info!(
38            "Connecting to device '{}' via ADB server at {}",
39            target_identifier,
40            adb_addr
41        );
42        let device = server
43            .get_device_by_name(&target_identifier)
44            .map_err(|e| DroidError::AdbError(e.to_string()))?;
45
46        Ok(Self { device })
47    }
48
49    /// Executes a raw shell command string.
50    /// This is the new, safer internal method.
51    fn shell(&mut self, command: &str) -> Result<String> {
52        log::debug!("Executing ADB shell command: {}", command);
53        let args: Vec<&str> = command.split_whitespace().collect();
54        let mut output_buffer: Vec<u8> = Vec::new();
55
56        self.device
57            .shell_command(&args, &mut output_buffer)
58            .map_err(|e| DroidError::AdbError(e.to_string()))?;
59
60        String::from_utf8(output_buffer)
61            .map_err(|e| DroidError::AdbError(format!("Shell output is not valid UTF-8: {}", e)))
62    }
63
64    /// 截取当前设备屏幕
65    pub fn screenshot(&mut self) -> Result<DynamicImage> {
66        log::debug!("Capturing screenshot...");
67        let png_data = self
68            .device
69            .framebuffer_bytes()
70            .map_err(|e| DroidError::AdbError(e.to_string()))?;
71
72        image::load_from_memory(&png_data).map_err(DroidError::ImageError)
73    }
74
75    /// Taps a point on the screen.
76    pub fn tap(&mut self, point: Point) -> Result<()> {
77        let cmd = format!("input tap {} {}", point.x, point.y);
78        self.shell(&cmd)?;
79        Ok(())
80    }
81
82    /// 在屏幕上滑动
83    pub fn swipe(&mut self, start: Point, end: Point, duration: Duration) -> Result<()> {
84        let cmd = format!(
85            "input swipe {} {} {} {} {}",
86            start.x,
87            start.y,
88            end.x,
89            end.y,
90            duration.as_millis()
91        );
92        self.shell(&cmd)?;
93        Ok(())
94    }
95
96    /// Inputs text.
97    pub fn input_text(&mut self, text: &str) -> Result<()> {
98        // ADB shell requires escaping spaces. %s is a common way.
99        let escaped_text = text.replace(' ', "%s");
100        let cmd = format!("input text {}", escaped_text);
101        self.shell(&cmd)?;
102        Ok(())
103    }
104
105    /// 发送一个按键事件
106    pub fn input_keyevent(&mut self, key_code: i32) -> Result<()> {
107        let cmd = format!("input keyevent {}", key_code);
108        self.shell(&cmd)?;
109        Ok(())
110    }
111}