Rust 如何与窗口交互

Thr*_*ror 5 automation window rust

介绍

目前,我正在为一位客户工作,他希望自动化其会计应用程序中的某些操作。

问题

我在一个箱子中搜索了这个,但没有找到任何东西,无法读取另一个窗口的屏幕,并发布一些消息,例如按键交互或单击交互。

问题

有人知道 Rust 中有一个与另一个窗口交互的箱子吗?我需要交互:窗口屏幕阅读,发布一些关键消息,并向该窗口发布一些点击消息。

fra*_*pps 1

有各种包可以让您甚至以跨平台的方式模拟用户输入(例如鼠标和键盘输入):

以及用于截图的板条箱,例如

除此之外,还有一个autopilot可以让您两者兼得。

autopilot以下是使用和捕获主窗口屏幕的示例image(用于实际存储图像):

use image::{GenericImageView, png::PNGEncoder};

fn main() {
    let bitmap = autopilot::bitmap::capture_screen().expect("Failed to capture main screen.");

    let mut buf = Vec::new();
    let encoder = PNGEncoder::new(
        &mut buf
    );
    encoder
        .encode(
            &bitmap.image.as_rgb8().unwrap(),
            bitmap.image.width(),
            bitmap.image.height(),
            image::ColorType::RGB(8),
        )
        .expect("Failed to encode png.");

    std::fs::write("test.png", buf).expect("Failed to write screenshot to disk.");
}
Run Code Online (Sandbox Code Playgroud)

这是鼠标输入的示例(将光标移动一个圆圈):

use image::{GenericImageView, png::PNGEncoder};

fn main() {
    let bitmap = autopilot::bitmap::capture_screen().expect("Failed to capture main screen.");

    let mut buf = Vec::new();
    let encoder = PNGEncoder::new(
        &mut buf
    );
    encoder
        .encode(
            &bitmap.image.as_rgb8().unwrap(),
            bitmap.image.width(),
            bitmap.image.height(),
            image::ColorType::RGB(8),
        )
        .expect("Failed to encode png.");

    std::fs::write("test.png", buf).expect("Failed to write screenshot to disk.");
}
Run Code Online (Sandbox Code Playgroud)

如果您只想捕获窗口区域,可以通过截取完整桌面的屏幕截图,然后仅将其裁剪到窗口来实现。在 Windows 上,您可以使用 获取某个窗口的窗口矩形GetWindowRect。下面是使用 ID 或名称获取窗口矩形的代码片段。

根据评论中的要求进行更新

以下是如何仅捕获包含给定窗口的屏幕特定部分的示例(仅适用于窗口,并且窗口必须在屏幕上完全可见):

const MARGIN: f64 = 10.0;
const MILLIS: u64 = 10;

struct Center {
    x: f64,
    y: f64,
}

fn main() {
    circle_mouse().expect("Unable to move mouse");
}

fn circle_mouse() -> Result<(), autopilot::mouse::MouseError> {
    let screen_size = autopilot::screen::size();
    let scoped_height = screen_size.height / 2.0 - MARGIN;
    let scoped_width = screen_size.width / 2.0 - MARGIN;
    let scoped_radius;

    if scoped_height > scoped_width {
        scoped_radius = scoped_width;
    }
    else {
        scoped_radius = scoped_height;
    }

    let center = Center { x: scoped_width, y: scoped_height };

    for i in 0..360 {
        let x = (i as f64 / 180.0 * std::f64::consts::PI).cos() * scoped_radius;
        let y = (i as f64 / 180.0 * std::f64::consts::PI).sin() * scoped_radius;
        autopilot::mouse::move_to(autopilot::geometry::Point::new(
            center.x + x as f64,
            center.y + y as f64,
        ))?;
        std::thread::sleep(std::time::Duration::from_millis(MILLIS));
    }

    Ok(())
}
Run Code Online (Sandbox Code Playgroud)

该示例具有以下依赖项:

autopilot = "0.4.0"
windows-sys = { version = "0.36.1", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging"] }
Run Code Online (Sandbox Code Playgroud)

请注意,这确实包括您可能不想要的不可见窗口边框。您可以使用DwmGetWindowAttribute以下方法来校正视觉偏移:

use autopilot::geometry::{Point, Rect, Size};
use std::{ffi::OsString, iter::once, os::windows::prelude::OsStrExt, ptr::null};
use windows_sys::Win32::{
    Foundation::{HWND, RECT},
    UI::WindowsAndMessaging::{FindWindowW, GetWindowRect},
};

fn main() {
    // The title of the window (is shown when hovering over the window in the taskbar):
    let window_name = OsString::from("autopilot");
    let window_name: Vec<u16> = window_name
        .as_os_str()
        .encode_wide()
        .chain(once(0))
        .collect();
    let id: HWND = unsafe { FindWindowW(null(), window_name.as_ptr()) };
    let mut rect = RECT {
        left: 0,
        top: 0,
        right: 0,
        bottom: 0,
    };
    if id != 0 && unsafe { GetWindowRect(id, &mut rect) } != 0 {
        /* println!(
            "HWND: {}\nLocation: {} {}\nSize: {} {}",
            id,
            rect.left,
            rect.top,
            rect.right - rect.left,
            rect.bottom - rect.top
        ); */

        let bitmap = autopilot::bitmap::capture_screen_portion(Rect::new(
            Point::new(rect.left as f64, rect.top as f64),
            Size::new(
                (rect.right - rect.left) as f64,
                (rect.bottom - rect.top) as f64,
            ),
        ))
        .expect("Failed to capture screen portion.");
        bitmap
            .image
            .save("screen_portion.png")
            .expect("Failed to write image to disk.");
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这些依赖项

autopilot = "0.4.0"
windows-sys = { version = "0.36.1", features = ["Win32_Foundation", "Win32_Graphics_Dwm", "Win32_UI_WindowsAndMessaging"] }
Run Code Online (Sandbox Code Playgroud)

由于另一条评论而更新

是的,您也可以在 Rust 中使用WinAPIPostMessageW中的函数。这是一个简单的示例,其中包含链接示例的基本思想:

autopilot = "0.4.0"
windows-sys = { version = "0.36.1", features = ["Win32_Foundation", "Win32_UI_WindowsAndMessaging"] }
Run Code Online (Sandbox Code Playgroud)

这取决于

windows-sys = { version = "0.36.1", features = ["Win32_Foundation", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_WindowsAndMessaging"] }
Run Code Online (Sandbox Code Playgroud)

如果您想检测屏幕上的某些 UI 元素并获取它们的位置,您可能需要使用模式匹配/计算机视觉自行实现这一点,使用类似的东西opencv并使用事先截取的屏幕截图作为输入。