0

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

Timsib Adnap
  • 54
  • 1
  • 6
  • 3
    @cafce25 Doesn't even contain a `main()` function. Is `run()` supposed to be `main()`, or is this a windows thing I don't know about? – Finomnis Jan 01 '23 at 14:42
  • 1
    It works with [this code](https://gist.github.com/rust-play/1c3c4fbc2f116e8850471e72292f6fc1), based on [this gist](https://gist.github.com/LNSEAB/29faf2cfe786c5b6f748f43f260ad1e5). Now the question what's different. – Finomnis Jan 01 '23 at 15:08
  • @Finomnis it is to be called as a library. And the main function is implemented else where. But i am calling from main – Timsib Adnap Jan 01 '23 at 15:37
  • 2
    Then please show that, we can't read your mind ;) Please read how to provide a [MRE], and also the [Rust-specific MRE tips](https://stackoverflow.com/tags/rust/info). Usually, if your problem can't be reproduced by others, others won't be able to help you. So do them a favour and post all the code necessary to reproduce your problem. Further, specify what your problem actually is. – Finomnis Jan 01 '23 at 15:39

1 Answers1

1

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);
        }
    }
}
Finomnis
  • 18,094
  • 1
  • 20
  • 27
  • 2
    *"Use a `to_wstring()` helper function and an actual string."* - Or use a binding crate with more utility support, like [windows](https://crates.io/crates/windows), which provides the handy [`w!`](https://microsoft.github.io/windows-docs-rs/doc/windows/macro.w.html) macro specifically for this purpose. – IInspectable Jan 01 '23 at 15:46
  • @IInspectable Sure, that works too :) I just didn't want to deviate from OPs code too much. – Finomnis Jan 01 '23 at 15:48
  • 1
    Thanks a lot, for the answer as well as for the faults found out. Sorry for the low level mistakes (I am just 2 months into Rust). I added that class_atom as I saw that from the python post. Even without it the code was failing as I did not about know about the `GetMessageW` and `DispatchMessageW`. – Timsib Adnap Jan 01 '23 at 15:48
  • 1
    @TimsibAdnap No problem :) Just as a rule of thumb in programming, don't just randomly try things out, try to get a proper error message somewhere and then try to follow what it points to. Error messages are always the number one priority :) In your case, `CreateWindowExW` would have provided an error message. – Finomnis Jan 01 '23 at 15:49
  • 1
    @TimsibAdnap If that doesn't work, try to google if other people already achieved [something similar](https://gist.github.com/LNSEAB/29faf2cfe786c5b6f748f43f260ad1e5) (like in this case, just simply open a window and receive messages) and then from that on modify it step by step until it breaks, then you know exactly what change broke it. That's how I approached this problem. – Finomnis Jan 01 '23 at 15:50
  • @TimsibAdnap Third, [read the APIs](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw) ;) It's quite obvious that `CreateWIndowExW` does not expect this value as its first argument. – Finomnis Jan 01 '23 at 15:53
  • Is the global static mut the right way to go or is there any other more _rusty_ way? – Timsib Adnap Jan 01 '23 at 15:54
  • @TimsibAdnap Ah yah, I forgot to talk about that :) No, `static mut` is definitely not good, as it isn't threadsafe. The proper way is `static AtomicBool`. I'll update the answer to include that. – Finomnis Jan 01 '23 at 15:56
  • @TimsibAdnap Updated. Note that this only works for primitive types that support `Atomic`. For more complex types, use `static Mutex<_>`. – Finomnis Jan 01 '23 at 16:00
  • 1
    Thanks a lot for the help provided. And a very happy new year to you. – Timsib Adnap Jan 01 '23 at 16:01
  • @TimsibAdnap Global `static` variables should never be `mut`, because Rust can't ensure thread safety. They should use [interior mutability](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html), or more specifically, the [threadsafe version of it](https://doc.rust-lang.org/book/ch16-03-shared-state.html). Don't be scared too much, though, if you do this wrong, it becomes a compiler error or at least requires `unsafe` to access, which should indicate that something is wrong. – Finomnis Jan 01 '23 at 16:07
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/250773/discussion-between-timsib-adnap-and-finomnis). – Timsib Adnap Jan 01 '23 at 16:11