Rust 实现 Windows 剪贴板监听

前言

本文主要涉及如何使用 Windows API 实现剪贴板监听器,以及如何在 Rust 中使用指针和内存管理来操作Windows API。

监听 Windows 剪贴板

Windows 剪贴板监听基本原理

Windows 有三种方法可以监视剪贴板的更改:最早的方法是创建剪贴板查看器窗口,Windows 2000 添加了查询剪贴板序列号的功能,Windows Vista 添加了剪贴板格式侦听器。对于新程序,建议使用剪贴板格式侦听器或剪贴板序列号,尤其是侦听器。

剪贴板格式侦听器是一个注册的窗口,它会在剪贴板内容发生更改时收到通知。窗口通过调用 AddClipboardFormatListener 函数注册为剪贴板格式侦听器,当剪贴板的内容发生更改时,会收到一条 WM_CLIPBOARDUPDATE 通知。

通过 CreateWindowEx 方法可以创建一个不可见的消息窗口,它只能收发消息,可以用该窗口注册一个剪贴板侦听器。

代码实现

Rust 最小代码

use std::{
    ffi::OsStr,
    os::windows::prelude::OsStrExt,
    ptr::{null, null_mut},
};

use winapi::um::winuser::{
    AddClipboardFormatListener, CreateWindowExW, GetMessageW, HWND_MESSAGE, WM_CLIPBOARDUPDATE,
};

fn main() {
    // 创建消息窗口
    let hwnd = unsafe {
        CreateWindowExW(
            0,
            str_to_lpcwstr("STATIC").as_ptr(),
            null(),
            0,
            0,
            0,
            0,
            0,
            HWND_MESSAGE,
            null_mut(),
            // wnd_class.hInstance,
            null_mut(),
            null_mut(),
        )
    };
    if hwnd == null_mut() {
        panic!("CreateWindowEx failed");
    }

    // 添加剪贴板监听器
    unsafe { AddClipboardFormatListener(hwnd) };

    // 监听窗口消息
    let mut msg = unsafe { std::mem::zeroed() };
    loop {
        let ret = unsafe { GetMessageW(&mut msg, hwnd, 0, 0) };
        if ret != 1 {
            break;
        }
        match msg.message {
            WM_CLIPBOARDUPDATE => {
                println!("WM_CLIPBOARDUPDATE ");
            }
            _ => (),
        }
    }
}

fn str_to_lpcwstr(s: &str) -> Vec<u16> {
    OsStr::new(s)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect()
}
复制代码

Rust 封装代码

use std::{
    ffi::OsStr,
    os::windows::prelude::OsStrExt,
    ptr::{null, null_mut},
};

use winapi::{
    shared::windef::HWND,
    um::winuser::{
        AddClipboardFormatListener, CreateWindowExW, GetMessageW, HWND_MESSAGE, MSG,
        WM_CLIPBOARDUPDATE,
    },
};

fn main() {
    ClipboardListen::run(move || {
        println!("clipboard updated.");
        //TODO::剪贴板内容获取
    });
}

pub struct ClipboardListen {}

impl ClipboardListen {
    pub fn run<F: Fn() + Send + 'static>(callback: F) {
        std::thread::spawn(move || {
            for msg in Message::new() {
                match msg.message {
                    WM_CLIPBOARDUPDATE => callback(),
                    _ => (),
                }
            }
        });
    }
}

pub struct Message {
    hwnd: HWND,
}

impl Message {
    pub fn new() -> Self {
        // 创建消息窗口
        let hwnd = unsafe {
            CreateWindowExW(
                0,
                str_to_lpcwstr("STATIC").as_ptr(),
                null(),
                0,
                0,
                0,
                0,
                0,
                HWND_MESSAGE,
                null_mut(),
                // wnd_class.hInstance,
                null_mut(),
                null_mut(),
            )
        };
        if hwnd == null_mut() {
            panic!("CreateWindowEx failed");
        }

        unsafe { AddClipboardFormatListener(hwnd) };

        Self { hwnd }
    }

    fn get(&self) -> Option<MSG> {
        let mut msg = unsafe { std::mem::zeroed() };
        let ret = unsafe { GetMessageW(&mut msg, self.hwnd, 0, 0) };
        if ret == 1 {
            Some(msg)
        } else {
            None
        }
    }
}

impl Iterator for Message {
    type Item = MSG;

    fn next(&mut self) -> Option<Self::Item> {
        self.get()
    }
}

fn str_to_lpcwstr(s: &str) -> Vec<u16> {
    OsStr::new(s)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect()
}
复制代码

总结

尽管最终代码只有短短不到百行,但实现过程异常曲折。

首先,相关资料相对匮乏,而且各种实现方式五花八门,甚至有使用定时器定期检查剪贴板内容差异的方法。

通过查看各种开源库的代码,最终选择了添加剪贴板监听器的方案。但由于个人不熟悉Windows API开发,对于为什么需要创建一个窗口来监听剪贴板,进行了大量的资料查阅和理解,但结果仍是一知半解(现在的理解是大概 Windows 万物皆窗口吧x)。

猜你喜欢

转载自blog.csdn.net/qq_41221596/article/details/130142776