WinSafe

repository·master·Indexed 20 days ago

https://github.com/rodrigocfd/winsafe

A Rust library providing safe, idiomatic wrappers for the Windows API and high-level GUI controls. It allows developers to build native Windows applications without raw FFI unsafety, offering both a low-level Win32 API layer and high-level abstractions for windows, dialogs, and native controls like buttons, list views, and tree views. Version 0.0.28.

Tokens
15K
Snippets
51
Records
64
Agent score
71%

What's inside winsafe

  1. Overview of WinSafe capabilities

    master

    WinSafe provides a safe, idiomatic Rust interface for the Windows API. It is divided into two main layers:

    1. Low-level Win32 API: Provides direct access to constants, functions, structs, window messages, handles, and COM interfaces.
    2. High-level GUI controls: Provides abstractions for building native applications, including custom windows/dialogs (main, modal, modeless, etc.) and native controls like buttons, combo boxes, list views, and progress bars.
  2. Choose a window type for your GUI application

    master

    WinSafe provides 5 types of windows that can host child controls. For most standard GUI applications, you should start by using WindowMain.

    Available window types:

    • WindowMain: The standard entry point for a GUI application.
    • WindowModal: A window that requires user interaction before returning to the parent window.
    • WindowModeless: A window that allows interaction with other windows while it is open.
    • WindowMessageOnly: A window that does not have a visible interface but can receive messages.
    • WindowControl: A custom, user-defined child control.
  3. How window messages work in winsafe

    master

    winsafe categorizes messages into two main types based on their capability:

    1. Sendable Messages: All messages can be sent to a window. These implement the MsgSend trait. When you call SendMessage, you pass a struct representing the message.
    2. Receivable Messages: Messages that can be handled (intercepted) by a window procedure implement the MsgSendRecv trait. This is useful when implementing custom window logic from scratch.

    All messages are fundamentally based on the Wm struct, which contains the msg_id, wparam, and lparam fields.

  4. Implement custom window messages

    master

    To define a custom message that can both be sent and received, you must:

    1. Define a unique message ID (typically by adding an offset to co::WM::USER).
    2. Create a struct to hold the message data.
    3. Implement MsgSend to define how the struct is converted into a generic msg::Wm for sending and what type it returns.
    4. Implement MsgSendRecv to define how a generic msg::Wm is converted back into your custom struct when received.
    use winsafe::{self as w, prelude::*, co, msg};
    
    /// The integer value of our message ID.
    pub const MAKE_TOAST: co::WM = unsafe { co::WM::from_raw(co::WM::USER.raw() + 20) };
    
    /// Our message with its parameter.
    struct MakeToast {
        how_many: u32,
    }
    
    impl MsgSend for MakeToast {
        type RetType = ();
    
        fn convert_ret(&self, _: isize) -> Self::RetType {
            ()
        }
    
        fn as_generic_wm(&mut self) -> msg::Wm {
            msg::Wm {
                msg_id: MAKE_TOAST,
                wparam: self.how_many as _,
                lparam: 0,
            }
        }
    }
    
    impl MsgSendRecv for MakeToast {
        fn from_generic_wm(p: msg::Wm) -> Self {
            Self {
                how_many: p.wparam as _,
            }
        }
    }
  5. Install WinSafe via Cargo

    master

    To use WinSafe in your Rust project, add it to your Cargo.toml. You must explicitly enable the Cargo features corresponding to the Windows DLLs and libraries you need (e.g., user for User32.dll, gdi for Gdi32.dll).

    Note that the gui feature is required to use the high-level GUI abstractions.

    [dependencies]
    winsafe = { version = "0.0.28", features = ["gui", "user"] }
  6. Send messages to windows using HWND::SendMessage

    master

    To interact with Windows controls (like a ListView), you can send specific messages using the HWND::SendMessage method. In winsafe, messages are represented as structs that encapsulate the message ID and its parameters (WPARAM and LPARAM). The message struct also defines the return type of the operation via its RetType associated type.

    For example, to delete an item from a ListView, you use the LvmDeleteItem struct.

    use winsafe::{self as w, prelude::*, msg};
    
    let hlistview: w::HWND; // initialized somewhere
    
    hlistview.SendMessage(
        msg::LvmDeleteItem {
            index: 2,
        },
    ).expect("Failed to delete item 2.");
  7. How TreeView event handling works

    master

    In winsafe, TreeView event methods (defined in GuiEventsTreeView) are not handled directly by the TreeView control itself, but are proxies to the GuiEventsParent of the parent window. The parent window is the entity actually responsible for receiving and dispatching child control events in the Win32 message loop.

    When you call an event handler like tree.on().tvn_sel_changed(...), you are registering a callback that the parent window will execute when the specific notification is received from the TreeView control.

  8. Handle Windows messages via `winsafe::msg`

    master
    The winsafe::msg module provides access to Windows messages and message-related types. Depending on the enabled features, this includes user-defined messages, common control messages, GDI messages, and shell messages. This is the primary way to interact with the Windows message loop and window procedures.
  9. How ListView event handling works

    master

    In winsafe, ListView notifications are exposed via the GuiEventsListView trait. These methods act as proxies to the GuiEventsParent of the parent window, as the parent window is responsible for handling child control events.

    To use these event handlers, you should import the winsafe::prelude::* trait, which makes these methods available on your ListView instance via the .on() method.

    Note: These event methods are only available when the gui Cargo feature is enabled.

    use winsafe::prelude::*;
    
    // Assuming 'list' is a gui::ListView instance
    list.on().lvn_item_changed(|p: &NMLISTVIEW| -> AnyResult<()> {
        println!("Item changed: {}", p.iItem);
        Ok(())
    });
  10. Access native constants and error types via `winsafe::co`

    master
    The winsafe::co module provides access to native Windows constants. This includes error types such as CDERR, ERROR, and HRESULT. The available constants in this module are determined by the features enabled in your project (e.g., advapi, kernel, user, etc.).
  11. How GuiParent and thread management work

    master

    The GuiParent trait is implemented by windows that can host child controls. It provides two critical methods for managing concurrency and preventing UI deadlocks:

    1. spawn_thread: Spawns a new thread using std::thread::spawn, but wraps the closure to return an AnyResult<()>. If the closure returns an error, that error is forwarded to the original UI thread, where it can be caught at WindowMain::run_main. This ensures graceful termination on unexpected errors.
    2. run_ui_thread: Used when working in a parallel thread to update the UI. Because updating UI elements from a non-UI thread can cause deadlocks or crashes, run_ui_thread blocks the current thread, switches execution to the window's original UI thread to run the provided closure, and then switches back and unblocks the original thread.

    Rule of thumb: When performing long tasks in parallel, you must call run_ui_thread to perform any UI updates.

    use winsafe::{self as w, prelude::*, gui};
    
    // Inside a button click handler or similar event:
    btn.on().bn_clicked({
        let wnd = wnd.clone();
        move || -> w::AnyResult<()> {
            // 1. Start a long task in a parallel thread
            std::thread::spawn({
                let wnd = wnd.clone();
                move || {
                    w::Sleep(2000);
    
                    // 2. Use run_ui_thread to safely update the UI from the parallel thread
                    wnd.run_ui_thread({
                        let wnd = wnd.clone();
                        move || -> w::AnyResult<()> {
                            wnd.hwnd().SetWindowText("Status... 50%")?;
                            Ok(())
                        }
                    });
    
                    w::Sleep(2000);
    
                    wnd.run_ui_thread({
                        let wnd = wnd.clone();
                        move || -> w::AnyResult<()> {
                            wnd.hwnd().SetWindowText("Status... 100%")?;
                            Ok(())
                        }
                    });
                }
            });
    
            Ok(())
        }
    });
  12. Manage resources with `winsafe::guard` RAII implementations

    master
    The winsafe::guard module provides RAII (Resource Acquisition Is Initialization) implementations for various Windows resources. These guards are designed to automatically perform necessary cleanup routines (such as releasing handles or closing objects) when the guard object goes out of scope. Guards are typically named after the specific function they wrap or the resource they manage.