Home > Blockchain >  How to make a clipboard board monitor in rust
How to make a clipboard board monitor in rust

Time:01-02

I am trying to make a clipboard manager for windows in rust and am using winapi-rs crate to use winapi.

My current implementation is as follows:

src/clipboard.rs:

use std::mem::zeroed;
use std::ptr;

use winapi::shared::windef::HWND;
use winapi::um::libloaderapi::GetModuleHandleW;
use winapi::um::winuser::{
    AddClipboardFormatListener,
    RemoveClipboardFormatListener,
    CreateWindowExW,
    RegisterClassW,
    WNDCLASSW,
    WM_CREATE,
    WM_DESTROY,
    WM_CLIPBOARDUPDATE,
    HWND_MESSAGE,
    DefWindowProcW
};
use winapi::shared::minwindef::{LRESULT, UINT, WPARAM, LPARAM, BOOL};

static mut ADDED_LISTENER: BOOL = 0;

unsafe extern "system" fn callback_proc(h_wnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
    println!("called");
    (match msg {
        WM_CREATE => {
            ADDED_LISTENER = AddClipboardFormatListener(h_wnd);
            if ADDED_LISTENER == 1 { 0 } else { -1 }
        }
        WM_DESTROY => {
            if ADDED_LISTENER == 1 {
                RemoveClipboardFormatListener(h_wnd);
                ADDED_LISTENER = 0;
            }
            0
        }
        WM_CLIPBOARDUPDATE => {
            println!("clipboard updated.");
            0
        },
        _ => DefWindowProcW(h_wnd, msg, wparam, lparam)
    }) as LRESULT
}

pub fn run() {
    unsafe {
        let hinst = GetModuleHandleW(ptr::null_mut());
        let wnd_class = WNDCLASSW {
            hInstance: hinst,
            lpfnWndProc: Some(callback_proc),
            lpszClassName: &[67 as u16, 108 as u16, 105 as u16, 112 as u16, 98 as u16, 111 as u16, 97 as u16, 114 as u16, 100 as u16, 77 as u16, 101 as u16, 115 as u16, 115 as u16, 97 as u16, 103 as u16, 101 as u16, 87 as u16, 105 as u16, 110 as u16, 100 as u16, 111 as u16, 119 as u16] as *const u16,
            ..zeroed::<WNDCLASSW>()
        };

        let class_atom = RegisterClassW(&wnd_class as *const WNDCLASSW);

        CreateWindowExW(class_atom.into(), wnd_class.lpszClassName, ptr::null(), 0, 0, 0, 0, 0, HWND_MESSAGE, ptr::null_mut(), hinst, ptr::null_mut());
    }

    loop { } // Added this as the code was exiting immediately
}

src/main.rs:

mod clipboard;

fn main() {
    clipboard::run();
}

I got help from a c impl from this post from stackoverflow and this implementation in python.

But here I am not getting any output nor any error messages.

Note: using Rust v1.66.0-stable

CodePudding user response:

There are multiple things I would like to annotate about your code.

  • Don't hard-code a string through a manually specified &[u16] array. That's just absolutely impossible to read. Use a to_wstring() helper function and an actual string.
  • You have to actually receive and process the messages. An empty loop does exactly what it should do: Nothing :) (and that with 100% CPU power)
  • There is a return value check missing after CreateWindowExW, otherwise you would realize that this function fails.
  • Why do you feed class_atom.into() into CreateWindowExW as the first argument? I couldn't find any reference what that would accomplish; the first argument is dwExStyle, and class_atom is the class handle. Those have nothing in common; this is most likely the reason why CreateWindowExW fails. If at all, it should be the second argument, but as you already provide lpszClassName, you simply don't need the class atom.
  • Avoid static mut in Rust. It's not thread-safe and requires unsafe to be accessed. Use AtomicBool instead.

That said, here is a version that works for me:

use winapi::shared::minwindef::*;
use winapi::shared::windef::*;
use winapi::um::libloaderapi::*;
use winapi::um::winuser::*;

use std::ffi::OsStr;
use std::mem::zeroed;
use std::os::windows::ffi::OsStrExt;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;

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

static ADDED_LISTENER: AtomicBool = AtomicBool::new(false);

pub unsafe extern "system" fn window_proc(
    hwnd: HWND,
    msg: UINT,
    wparam: WPARAM,
    lparam: LPARAM,
) -> LRESULT {
    println!("called {}", msg);

    match msg {
        WM_CREATE => {
            let add_result = AddClipboardFormatListener(hwnd);
            if add_result == 1 {
                ADDED_LISTENER.store(true, Ordering::Relaxed);
                0
            } else {
                -1
            }
        }
        WM_DESTROY => {
            if ADDED_LISTENER.swap(false, Ordering::Relaxed) {
                RemoveClipboardFormatListener(hwnd);
            }
            PostQuitMessage(0);
            0
        }
        WM_CLIPBOARDUPDATE => {
            println!("clipboard updated.");
            0
        }
        _ => DefWindowProcW(hwnd, msg, wparam, lparam),
    }
}

fn main() {
    unsafe {
        let hinst = GetModuleHandleW(std::ptr::null_mut());
        let wnd_class = WNDCLASSW {
            hInstance: hinst,
            lpfnWndProc: Some(window_proc),
            lpszClassName: to_wstring("ClipboardMessageWindow").as_ptr(),
            ..zeroed::<WNDCLASSW>()
        };
        if RegisterClassW(&wnd_class) == 0 {
            panic!("RegisterClassEx failed");
        }

        let hwnd = CreateWindowExW(
            0,
            wnd_class.lpszClassName,
            std::ptr::null(),
            0,
            0,
            0,
            0,
            0,
            HWND_MESSAGE,
            std::ptr::null_mut(),
            hinst,
            std::ptr::null_mut(),
        );
        if hwnd == std::ptr::null_mut() {
            panic!("CreateWindowEx failed");
        }

        let mut msg = std::mem::zeroed();
        loop {
            let res = GetMessageW(&mut msg, std::ptr::null_mut(), 0, 0);
            if res == 0 || res == -1 {
                break;
            }

            DispatchMessageW(&msg);
        }
    }
}
  • Related