GithubHelp home page GithubHelp logo

rodrigocfd / winsafe Goto Github PK

View Code? Open in Web Editor NEW
440.0 11.0 26.0 266.31 MB

Windows API and GUI in safe, idiomatic Rust.

Home Page: https://crates.io/crates/winsafe

License: MIT License

Rust 99.93% Shell 0.07%
rust gui win32 windows native ffi directx

winsafe's Introduction

WinSafe

Crates.io Crates.io total downloads License: MIT Lines of code

Windows API and GUI in safe, idiomatic Rust.

WinSafe has:

  • low-level Win32 API constants, functions and structs;
  • high-level structs to build native Win32 GUI applications.

WinSafe documentation:

Branch Docs
Stable docs.rs/winsafe
Nightly (master) rodrigocfd.github.io/winsafe/winsafe

Current status

Native FFI items implemented:

Native FFI item Count
Functions 776
Structs 235
Constants 13,376
Window messages 653
Handles 45
COM interfaces 82
COM methods 470

High-level GUI controls:

  • User custom window/dialog – main, modal, modeless, control, message-only.
  • Native controls – button, check box, combo box, date and time picker, edit, header, label, list box, list view, month calendar, progress bar, radio button, status bar, tab, track bar, tree view, up down.

Usage

Add the dependency in your Cargo.toml:

[dependencies]
winsafe = { version = "0.0.20", features = [] }

You can, alternatively, use the Nightly (master) branch directly, to get the latest features right away:

[dependencies]
winsafe = { git = "https://github.com/rodrigocfd/winsafe", features = [] }

Then you must enable the Cargo features you want to be included – these modules are named after native Windows DLL and library names, mostly.

The following Cargo features are available so far:

Feature Description
comctl ComCtl32.dll, for Common Controls
dshow DirectShow
dwm Dwmapi.dll, the Desktop Window Manager
dxgi DirectX Graphics Infrastructure
gdi Gdi32.dll, the Windows GDI
gui The WinSafe high-level GUI abstractions
kernel Kernel32.dll, Advapi32.dll and Ktmw32.dll – all others will include it
mf Media Foundation
ole OLE and basic COM support
oleaut OLE Automation
shell Shell32.dll, Shlwapi.dll, and Userenv.dll, the COM-based Windows Shell
taskschd Task Scheduler
user User32.dll and ComDlg32.dll, the basic Windows GUI support
uxtheme UxTheme.dll, extended window theming
version Version.dll, to manipulate *.exe version info

Although WinSafe already has a lot of Win32 APIs, it doesn't have everything, simply because Win32 API is gigantic. So if you're looking for a comprehensive Win32 coverage, take a look at winapi or windows crates, which are unsafe, but have everything.

Example

Note: You can find several examples in the dedicated repo: github.com/rodrigocfd/winsafe-examples

WinSafe allows you to create windows in two ways:

  • programmatically defining parameters; or
  • loading dialogs from a .res file created with a WYSIWYG resource editor.

The example below creates a window with a button programmatically. Note how the click event is handled with a closure:

Example 01

[dependencies]
winsafe = { version = "0.0.20", features = ["gui"] }
#![windows_subsystem = "windows"]

use winsafe::{self as w, prelude::*, gui};

fn main() {
    let my = MyWindow::new(); // instantiate our main window
    if let Err(e) = my.wnd.run_main(None) { // ... and run it
        eprintln!("{}", e);
    }
}


#[derive(Clone)]
pub struct MyWindow {
    wnd:       gui::WindowMain, // responsible for managing the window
    btn_hello: gui::Button,     // a button
}

impl MyWindow {
    pub fn new() -> Self {
        let wnd = gui::WindowMain::new( // instantiate the window manager
            gui::WindowMainOpts {
                title: "My window title".to_owned(),
                size: (300, 150),
                ..Default::default() // leave all other options as default
            },
        );

        let btn_hello = gui::Button::new(
            &wnd, // the window manager is the parent of our button
            gui::ButtonOpts {
                text: "&Click me".to_owned(),
                position: (20, 20),
                ..Default::default()
            },
        );

        let new_self = Self { wnd, btn_hello };
        new_self.events(); // attach our events
        new_self
    }

    fn events(&self) {
        let wnd = self.wnd.clone(); // clone so it can be passed into the closure
        self.btn_hello.on().bn_clicked(move || {
            wnd.hwnd().SetWindowText("Hello, world!")?;
            Ok(())
        });
    }
}

License

Licensed under MIT license, see LICENSE.md for details.

winsafe's People

Contributors

amithkk avatar anatawa12 avatar azam avatar caesay avatar cashtang avatar michidk avatar roblabla avatar rodrigocfd avatar sollyucko avatar tomzbench avatar y15un avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

winsafe's Issues

HWDP guard does not handle changed handles nor failures correctly

The RAII guard for an HDWP returned by BeginDeferWindowPos is implemented the same way as lots of other RAII guards. However, in the case of an HDWP, that is incorrect for two reasons:

  1. Per the return value documentation of DeferWindowPos, “The handle returned by this function may differ from the handle passed to the function. The new handle that this function returns should be passed during the next call to the DeferWindowPos or EndDeferWindowPos function.” In other words, the old handle must not be ended nor reused, and the new handle (which DeferWindowPos currently returns as an unguarded HDWP) must be ended. What should probably ideally happen is that the handle living in the guard is swapped out for the return value on each call, and maybe DeferWindowPos should return SysResult<()> instead of SysResult<HDWP>.
  2. Per the remarks documentation of that same function, “If a call to DeferWindowPos fails, the application should abandon the window-positioning operation and not call EndDeferWindowPos.” But as written, in case of failure, the RAII guard will still call EndDeferWindowPos.

Adding a safe wrapper around ChangeDisplaySettingsExW

Currently, this crate only has a wrapper around ChangeDisplaySettingsW, which is limited to changing device settings for the "Default" or primary display. However this crate already provides EnumDisplaySettingsExW that allows you to retrieve the DEVMODE struct for any display

This issue proposes adding ChangeDisplaySettingsExW which takes in a parameter to specify the device name so that one can both read and write device settings for all displays

File Open Dialog

How to show the file open dialog?

let mut foo: winsafe::IUnknown = winsafe::CoCreateInstance(
  &winsafe::shell::clsid::FileOpenDialog,
  None,
  winsafe::co::CLSCTX::INPROC_SERVER,
).unwrap();

This code runs without errors but doesn't show a file open dialog.

Circular reference via event handlers causes memory leak

I think there is a memory leak in the 02_combo_and_radios example, which might be best addressed in this repo rather than in the example. Remove the #[derive(Clone)] from MyWindow and implement that and Drop by hand, with instrumentation, to see it:

pub struct MyWindow {
	wnd:        gui::WindowMain,
	cmb_cities: gui::ComboBox,
	rad_seas:   gui::RadioGroup,
}

impl Clone for MyWindow {
	fn clone(&self) -> Self {
		println!("Clone");
		Self {
			wnd: self.wnd.clone(),
			cmb_cities: self.cmb_cities.clone(),
			rad_seas: self.rad_seas.clone(),
		}
	}
}

impl Drop for MyWindow {
	fn drop(&mut self) {
		println!("Drop");
	}
}

When you run the code you see “Clone” printed three times (so there are four instances of MyWindow, the one created directly in MyWindow::new and then the three clones captured in event handler closures), but “Drop” is only printed once! So three objects have been leaked. The problem, if I understand correctly, is that there is a circular reference:

  1. The MyWindow contains a gui::WindowMain.
  2. The WindowMain contains a RawDlg, in this case the RawDlg::Raw variant.
  3. The RawDlg::Raw contains a RawMain.
  4. The RawMain contains a Pin<Arc<Obj>>.
  5. The Obj contains a RawBase.
  6. The RawBase contains a Base.
  7. The Base contains a WindowEventsAll.
  8. The WindowEventsAll contains a FuncStore<(u16, co::NM), Box<dyn Fn(wm::Notify) -> AnyResult<Option<isize>>>.
  9. The FuncStore contains a Vec<Pair> with one element.
  10. The element of the Vec contains a Box.
  11. The Box contains a the closure passed to ComboBoxEvents::cbn_sel_change.
  12. The closure contains a MyWindow.

So the Pin<Arc<Obj>> in step 4 contains, via a very long chain, a reference to itself.

Application code could fix this if it was very careful: instead of cloning MyWindow, it could hold MyWindow behind an Rc and always downgrade the Rc to a Weak and capture the Weak as part of the closure, upgrading it on each call; thus step 12 would break the cycle (since the closure would only contain a Weak). However, this is unintuitive and it would be easy to get it wrong.

I noticed you are only allowed to register event handlers before the HWND has been created. I wonder, would it make any sense that, when the HWND is destroyed, you drop all the event handlers? After all, if the HWND has been destroyed, they aren’t needed any more (since no more events can be delivered to them), and dropping them would break the circular reference. Although I’m not sure how well that would work with dynamic creation and destruction of controls, since many event handlers conceptually belong to child windows (e.g. WM_COMMAND handlers for buttons) but are actually registered against the parent (since that’s where the WM_COMMAND is actually sent).

WebView2 control

I would like to keep using Rust instead of switching to C#, but I never did any win32 programming so winsafe is very helpful. I've looked into webview2-rs, but the public API is full of unsafe and I found the sample code too complicated compared to winsafe...

Could WebView2 be added to winsafe somehow? I understand WebView2 is very new relative to the classic controls, but I think it would be pretty amazing, if it's doable.

Reg{Create,Open}KeyTransacted should take transaction by reference

Lots of things that take a handle take it by reference, since the commit that made handles non-Copy and non-Clone—for example, SetParent takes an &HWND as the new parent, not an HWND. RegCreateKeyTransacted and RegOpenKeyTransacted currently take the HTRANSACTION by value; it probably makes sense for them to take it by reference instead, to avoid ugly calls to raw_copy in user code.

[0.0.11] Example from readme fails with Ordinal not found error

Windows 11, rust, added winsafe with gui feature, copy-pasted code from readme, cargo.exe build, cargo.exe run

rustc 1.64.0-nightly (1c7b36d4d 2022-07-12)
binary: rustc
commit-hash: 1c7b36d4db582cb47513a6c7176baaec1c3346ab
commit-date: 2022-07-12
host: x86_64-pc-windows-msvc
release: 1.64.0-nightly
LLVM version: 14.0.6

image
Output of cargo.exe run:

> cargo.exe run
   Compiling winsafe v0.0.11
   Compiling test v0.1.0 (<path>)
    Finished dev [unoptimized + debuginfo] target(s) in 15.00s
     Running `target\debug\test.exe`
error: process didn't exit successfully: `target\debug\test.exe` (exit code: 0xc0000138, STATUS_ORDINAL_NOT_FOUND)

With latest master:

> cargo.exe run
    Updating git repository `https://github.com/rodrigocfd/winsafe`
   Compiling winsafe v0.0.11 (https://github.com/rodrigocfd/winsafe#7c14269e)
   Compiling test v0.1.0 (<path>)
    Finished dev [unoptimized + debuginfo] target(s) in 22.23s
     Running `target\debug\test.exe`
error: process didn't exit successfully: `target\debug\test.exe` (exit code: 0xc0000138, STATUS_ORDINAL_NOT_FOUND)

WM_MENUSELECT

I can listen for menu list open events. But what about for when a specific menu item is clicked? I dont see WM_MENUSELECT in the documentation.

My menu is created in a resource file, if it matters.

screenshot

create gui with resedit ?

Hi,
when you say that "WinSafe works with both ordinary windows and resource dialogs", does it mean that it will be possible to use a tool like ResEdit to create the GUI visually (or parts of it..) with it's dialog editor and use the .rc file created ?

Feature request: MONITORINFOEX struct

It would be nice to have a GetMonitorInfo function on the HMONITOR struct that supports MONITORINFOEX as an input, or make the existing function support it.

[user] EnumDisplayDevices result

The EnumDisplayDevices function returns a WinResult. I don't see where Ok(true) is ever set. It also seems like it is not required because we also return a result.

Also, the current example in the docs of that function is a bit weird. The function is supposed to be called until the first error because that means that we finished iterating over all displays:

if !is_finished {

will never be the case, because the ? will return the error.

winsafe::MSG doesn't match layout of windows.h MSG on 32-bit

The lpPrivate field at the end here:

winsafe/src/user/structs.rs

Lines 433 to 443 in 6540aea

#[repr(C)]
#[derive(Clone)]
pub struct MSG {
pub hwnd: HWND,
pub message: co::WM,
pub wParam: usize,
pub lParam: isize,
pub time: u32,
pub pt: POINT,
lPrivate: u32,
}

Is only defined by windows.h if _MAC (macos / OS X) is defined:

typedef struct tagMSG {
    HWND        hwnd;
    UINT        message;
    WPARAM      wParam;
    LPARAM      lParam;
    DWORD       time;
    POINT       pt;
#ifdef _MAC
    DWORD       lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

On 64-bit, there's some implicit padding there anyways. That's a bit sketchy if MSG were ever to be combined with MaybeUninit, as lPrivate could end up being left uninitialized.

On 32-bit, this actually changes the struct size (winsafe::MSG is 32 bytes, windows.h MSG is 28 bytes.)

Perhaps use #[cfg(target_os = "macos")] ?

Load icon from file?

let name: u16 = // ?
let h_icon = HINSTANCE::NULL.LoadImageIcon(name, 16, 16, LR::LOADFROMFILE);

Why is name a u16? Shouldn't a filename string be accepted?

I would like to change the window icon at runtime from a file that isn't included in the resources.

Incorrect message parameters for WM_CHAR and friends

In commit 8335fe2, you changed the pub_struct_msg_char macro to generate a char_code field of type co::VK rather than u16. That macros is used for lots of messages right now, but their wParams aren’t all the same!

  • WM_CHAR gives a character code.
  • WM_KEYDOWN gives a virtual-key code.
  • WM_KEYUP gives a virtual-key code.
  • WM_SYSCHAR gives a character code.
  • WM_SYSDEADCHAR gives a character code.
  • WM_SYSKEYDOWN gives a virtual-key code.
  • WM_SYSKEYUP gives a virtual-key code.

Only those that carry a virtual-key code should be of type VK. The ones that carry a character code are either UTF-16 code units if the window class was registered with RegisterClassW or RegisterClassExW, or code page characters if registered with RegisterClassA or RegisterClassExA. So they should go back to being of type u16 (don’t use char since some sources claim that a non-BMP character could be delivered by two consecutive WM_CHAR messages each containing a surrogate, and a Rust char cannot contain a surrogate, so each message on its own would fail to decode; only later after reassembling both messages do you actually have a complete character).

Cannot infer type

I know this is probably more of a newbie rustacean issue, but I've tried match without any luck:

let color: winsafe::COLORREF = winsafe::COLORREF::new(0xff, 0x00, 0x00);
let brush: winsafe::HBRUSH = GdiHbrush::CreateSolidBrush(color).unwrap();
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type

I'm trying to set the main window's background color.

WindowControlOpts::ctrl_id does not auto-generate an ID

Create a WindowControl without setting WindowControlOpts::ctrl_id to anything. The documentation says a control ID will be auto-generated; in actual fact, the window is created with a child ID of zero. It works for other controls (at least the ones I checked), just not WindowControl.

ListBox missing SetCurSel event

The event is missing from the list in the documentation:

https://rodrigocfd.github.io/winsafe/winsafe/msg/lb/index.html

But it's in the Microsoft documentation:

https://docs.microsoft.com/en-us/windows/win32/controls/lb-setsel

I think it should look something like this:

winsafe::msg::lb::SetCurSel { 
  select: bool, 
  index: u32
}

Without it, there's no guaranteed way to uniquely select an item by index. SelectString can be used to select the first item beginning with a certain string, but fails to select Hello if that item is preceded by Hello World!.

Correct me if I'm wrong ...

[Question] How do I send mouse and key events

Hey, thank you for working on this project, its great !.
I am playing around with winapi in general for learning, so what is the correct way to send mouse and key events to a window since I could use this in a work project for automation of an app at work , it is already in production but I used global key events so this would be better using direct msg .

I wonder if this should work, cuz it doss not seem to work for me no right click happens,
and the FindWindow function should not not have the class name as an Optional?
and is the xy cords relative to the window or the screen? Thank you :)
rs```
let hwnd = HWND::FindWindow("Notepad", "Untitled - Notepad")?;
hwnd.SetForegroundWindow();
hwnd.SetFocus();
let m = wm::RButtonDown {
vkey_code: co::VK::RBUTTON,
coords: POINT { x: 8, y: 8 },
};
hwnd.SendMessage(m);

HKEY::QueryValueEx and GetValue contain race conditions and lack length-versus-type checking

These two functions work in two stages: first they do the query with a null buffer to discover the type and length of the data, then they do the query a second time with an allocated buffer of the specified length. However, another process, or another thread of the same process, could have modified the value in the registry in between the two calls. Thus:

  • If the other process made the data larger, the buffer will be too small and the second call will fail. This is arguably correct behaviour, but it seems like it would be more useful if the library were to loop and enlarge the buffer further in this case.
  • If the other process made the data smaller, the new length returned by the second query is not used, rather the full buffer is decoded, meaning you get a return value containing the new data followed by some filler.
  • If the other process changed the type of the data, then the new bytes are decoded based on the old data type.

Furthermore, even in the absence of race conditions, there is no checking that the returned length makes sense for the given type. This is not guaranteed by the Windows API: it is totally possible to construct a REG_DWORD that is three or seven bytes long, or a REG_SZ whose length is odd or that does not end in a NUL. You can even do this in RegEdit by right-clicking a value and clicking Modify Binary. This lack of checking means that, for example, if the registry contains a three-byte REG_DWORD, then when constructing the return value, u32::from_ne_bytes(buf.try_into().unwrap()) will panic because buf is not four bytes long. This should be caught and returned as a proper error, since an application should never be forced to panic under the control of external data.

Null value for bind_ctx

Hey all

I'm trying to translate a piece of c++ code (https://stackoverflow.com/questions/59926644/listing-of-installed-apps-on-windows-10) to rust. In it the author uses the BindToHandler function. I found that method in this crate as well but the problem is that in the original c++ code they pass a nullptr for the bindcontext. Since the bind context parameter in the BindToHandler method is not an option I can't pass None like I did with SHCreateItemFromParsingName. What would be the equivalent here of passing a NULL value in c++?

kr

RegOpenKeyEx doesn’t work

RegOpenKeyEx always returns a null HKEYon success. The problem is that a null HKEY is constructed, then hKey.as_ptr() is passed into the raw function, which means it will store the newly opened HKEY into wherever hKey pointed at, which is null, rather than into hKey itself. I changed the function to look like this and it now works, but I imagine raw types like c_void probably aren’t supposed to appear here; I just don’t really quite understand all the type aliases so it was the fastest and simplest fix I could come up with:

	fn RegOpenKeyEx(self, sub_key: &str,
		options: co::REG_OPTION, access_rights: co::KEY) -> SysResult<HKEY>
	{
		let mut hKey: *mut std::ffi::c_void = std::ptr::null_mut();

		match co::ERROR(
			unsafe {
				advapi::ffi::RegOpenKeyExW(
					self.as_ptr(),
					WString::from_str(sub_key).as_ptr(),
					options.0,
					access_rights.0,
					&mut hKey as _,
				)
			} as _,
		) {
			co::ERROR::SUCCESS => Ok(unsafe { HKEY::from_ptr(hKey) }),
			err => Err(err),
		}
	}

SS_CENTER flag on Label not working

I created the resource file in Resource Hacker, and saved the label with the SS_CENTER flag:

resource hacker screenshot

But in the program it's displayed as left-aligned:

bug screenshot

Code:

myself.lbl.set_text(&myself.input_val.borrow()).unwrap();

GetDlgCtrlId fails if the child window’s ID is actually zero

The wrapper for GetDlgCtrlId tries to distinguish between zero-as-success and zero-as-failure by calling GetLastError. However, it does not call SetLastError(ERROR_SUCCESS) before calling GetDlgCtrlId, which means if the last error code was non-successful on entry and the value is zero, a spurious error will be returned. The documentation is vague as to whether the last error code is cleared on success or not by the underlying Windows API call, but I encountered this personally and it is not, so you should probably do it in the wrapper.

Copyable handles are probably unsound

The fact that you can close a handle and then keep using it (since e.g. RegCloseKey takes self by value, but HANDLE impls Copy so you can keep using the value afterwards anyway) seems unsound. Immediate reuse generally results in the invalid handle error being reported, but if you wait and reuse it later after other code has run, the handle value might match a newly opened handle. That new handle would definitely point at the wrong thing (e.g. a registry key handle opened for one key, when reused, would now modify a totally different key), and might even be the wrong type (e.g. a registry key handle might now refer to a file instead). This seems like it should probably be considered to violate memory safety.

Way to instantiate an arbitrary control when using gui

It would be nice if there were a clean way to build most of a GUI using the safe, high-level gui module, but include one or two controls that are not supported by Winsafe (either Microsoft common controls that Winsafe doesn’t wrap yet, or perhaps third-party controls in a linked DLL). Here are the ways I could think of to achieve that, along with why they’re not ideal:

  • You can’t use WindowControl::new because it insists on registering the class, which is inappropriate for an already-registered class.
  • If you use CreateWindowEx with no control ID, you can’t use .on().wm_notify(…) to receive WM_NOTIFY messages relating to the control because you have no control ID, which is a mandatory parameter to the wm_notify method.
  • If you use CreateWindowEx with no control ID, you can’t use .on().wm(WM::NOTIFY, ...) and match the HWND in the NMHDR, because callbacks registered with .wm(WM::NOTIFY, …) don’t seem to get called at all (it seems like WM_NOTIFY must be special-cased and not handleable via general-purpose message handlers).
  • If you use CreateWindowEx with a control ID, it works, but the auto-generated control IDs that Winsafe uses under the hood for the gui module are not available for clients to allocate and the value range is undocumented AFAICT, so it’s impossible to choose a safe, non-colliding control ID purely from publicly documented information.

Can't construct TRACKMOUSEEVENT

Does winsafe support creating TRACKMOUSEEVENT?

I get an error related to private fields.

let mut mouse_event = winsafe::TRACKMOUSEEVENT {
  dwFlags: winsafe::co::TME::HOVER | winsafe::co::TME::LEAVE,
  hwndTrack: self.lbl.hwnd(),
  dwHoverTime: 0,
  ..Default::default()
//^^^^^^^^^^^^^^^^^^^^ field `cbSize` is private
};
winsafe::TrackMouseEvent(&mut mouse_event);

let label_events = self.lbl.on_subclass();
label_events.wm_mouse_hover({
  move |params| {
    println!("{},{}", params.coords.x, params.coords.y);
  }
});

How to change label text color?

As you can see in the code below, parms.hwnd is always the handle to my label, because it's the only control on the window. But the text is still black, not red as intended. Is this a bug, just an unsupported feature, or am I doing something wrong?

I've tried to translate what I've read on stackoverflow into the corresponding winsafe version. The answers there say to return an HBRUSH, but the compiler says it must return an HDC, so I figured setting the brush of the HDC is equivalent.

self.wnd.on().wm_ctl_color_static({
  let self2 = self.clone();
  move |parms| {
    println!("{:?}", parms.hwnd == self2.lbl.hwnd()); // true
    let red = COLORREF::new(0xff, 0x00, 0x00);
    let brush = HBRUSH::CreateSolidBrush(red).unwrap();
    parms.hdc.SelectObjectBrush(brush);
    parms.hdc.SetTextColor(red).unwrap();
    parms.hdc.SetBkMode(winsafe::co::BKMODE::TRANSPARENT);
    parms.hdc
  }
});

Add support for IEnumShellItems

hi

I'm trying to use the BindToHandler method to iterate over objects in a folder. According to the examples I have found I should use the EnumItems BHID for that. But that constant requires the IEnumShellItems interface which I don't believe is supported in this library. I have attemtped to use IShellItemArray instead like so let ish_arr = shi.BindToHandler::<IShellItemArray>(None, &BHID::EnumItems).unwrap(); but then I get a No such interface supported.

Is there an alternative to this or will this interface be supported in the near-future?

Thanks!

set background color of a label

This is a great piece of work.
But how do you set the bk color of a label (or set the mode to transparent)?
An example would be very helpful. Thanks!

WM_MOUSEMOVE for controls?

I would like to handle MOUSEMOVE for a label, but it doesn't have such an event. I don't think labels support custom messages like here, right?

I can't use self.wnd.on().wm_mouse_move({ because it stops firing when the cursor moves over a control.

DispatchMessage, SendMessage, DefWindowProc, and PostThreadMessage are all unsound.

Sending window messages in general should be unsafe. DispatchMessage - among others - is not.

winsafe/src/user/funcs.rs

Lines 100 to 103 in 06b1c08

/// [`DispatchMessage`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-dispatchmessagew)
/// function.
#[cfg_attr(docsrs, doc(cfg(feature = "user")))]
pub fn DispatchMessage(msg: &MSG) -> isize {

While the underlying DispatchMessage does a lot of parameter checking - including forbidding most system messages that interpret wparam/lparam as pointers by returning ERROR_MESSAGE_SYNC_ONLY - it cannot do the same for WM_USER+n or WM_APP+n messages which might interpret wparam/lparam as pointers.

For example, Microsoft's own TOOLBARCLASSNAME ("ToolbarWindow32") expects wparam to be a pointer to an array for TB_ADDBUTTONSW (WM_USER+68). While the docs recommend using SendMessage, DispatchMessage also works, and can crash / access violation if fed an invalid pointer. Other messages could even mutate what's pointed to, causing random memory corruption instead of a nice and clean access violation.

This means that if any middleware, library, injected DLL, etc. might ever create a window containing a toolbar on one of your threads, you can use DispatchMessage to cause undefined behavior.


SendMessage and DefWindowProc have similar issues, although there are traits involved muddying things up. Additionally, SendMessage won't reject messages with ERROR_MESSAGE_SYNC_ONLY to my knowledge. The traits involved are "safe" - it appears you're able to implement them to use e.g. WM::SETTEXT without any unsafe, despite the possibility of passing an invalid pointer in wparam for the string text.

PostThreadMessage at least won't dispatch to a wndproc... but I promise you that third parties will inject threads into your process (game overlays, debuggers, ...), and will treat wparam/lparam as pointers for some WM_USER+N messages.

CreateWindowEx is unsound

lpParam needs to be a valid pointer in some cases:

If an application calls CreateWindow to create a MDI client window, lpParam should point to a CLIENTCREATESTRUCT structure. If an MDI client window calls CreateWindow to create an MDI child window, lpParam should point to a MDICREATESTRUCT structure. lpParam may be NULL if no additional data is needed.
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw

However, CreateWindowEx is not unsafe, performs no relevant parameter validation, and makes lparam a simple to misconstruct Option<isize>.

/// [`CreateWindowEx`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw)
/// static method.
fn CreateWindowEx(
ex_style: co::WS_EX,
class_name: AtomStr,
title: Option<&str>,
style: co::WS,
pos: POINT,
size: SIZE,
hwnd_parent: Option<HWND>,
hmenu: IdMenu,
hinstance: HINSTANCE,
lparam: Option<isize>) -> WinResult<HWND>

Sending messages to unsupported controls

For example, the msctls_progress32 control does not seem to be supported yet. Is there a way to send custom messages to unsupported controls created in .resource files, using this function?

winsafe/src/handles/hwnd.rs

Lines 973 to 982 in 82bcca0

pub fn SendMessage<M: MsgSend>(self, uMsg: M) -> M::RetType {
let wmAny = uMsg.as_generic_wm();
uMsg.convert_ret(
unsafe {
user32::SendMessageW(
self.ptr, wmAny.msg_id.0, wmAny.wparam, wmAny.lparam,
)
},
)
}

In this specific case, I'd like to send PBM_SETSTEP messages.

HDC::GetDIBits

Hi, I'm trying to get a screenshot using winsafe, but there's no GetDIBits function, can it be added?

With windows-rs it looks atrocious:

let mut bmi = BITMAPINFO {
    bmiHeader: BITMAPINFOHEADER {
        biSize: std::mem::size_of::<BITMAPINFOHEADER>() as u32,
        biWidth: bmp.bmWidth,
        biHeight: -bmp.bmHeight,
        biPlanes: 1,
        biBitCount: 32,
        biCompression: BI_RGB as u32,
        biSizeImage: 0,
        biXPelsPerMeter: 0,
        biYPelsPerMeter: 0,
        biClrUsed: 0,
        biClrImportant: 0,
    },
    bmiColors: [RGBQUAD {
        rgbBlue: 0,
        rgbGreen: 0,
        rgbRed: 0,
        rgbReserved: 0,
    }],
};

//let size: usize = (width * height) as usize * 4;
let size: usize = (((bmp.bmWidth * (bmi.bmiHeader.biBitCount as i32) + 31) / 32) * 4 * bmp.bmHeight) as usize;
let mut data: Vec<u8> = Vec::with_capacity(size);
unsafe { data.set_len(size) };

unsafe {
    GetDIBits(
        hdc_mem, hbmp, 0, height as u32, &mut data[0] as *mut u8 as *mut c_void,
        &mut bmi,
        DIB_RGB_COLORS,
    );
}

Request: Wrapper for GetVolumeInformationW

I'd like to request a wrapper for GetVolumeInformation/GetVolumeInformationA/GetVolumeInformationW. Probably the last one, since that speaks Unicode.

(Use case: I need to do something cross-plaform, but don't have a modern Windows dev environment. So I want to cross-compile. Thanks.)

cargo fmt, maybe?

currently this repo indents with tabs, but the style guide recommends to use spaces (4, specifically).

this bugged me greatly because by default vscode with rust extension will do 'format document' automatically upon every save, and it tries to convert all the indenting tabs to spaces.

there are two options i'd like to recommend:

  1. switch to spaces: doing a cargo fmt run to get a sane-enough outcome now
    • warning: this will create a massive commit, but better do this now than later i guess?
  2. keep using tabs: have a rustfmt.toml added to the repo with hard_tabs set to true
    • this will at least prevent vscode trying to auto-reformat document on every save.

just my two cents!

There's SetClipboardData but not GetClipboardData yet?

Also, how does one use SetClipboardData?

I tried this:

    fn events(&self) {
        self.btn_hello.on().bn_clicked({
            let self2 = self.wnd.clone(); // clone so it can be passed into the closure
            move || {
                let mut ptr = b"Hello World\0".to_vec().as_mut_ptr() as *mut u8;
                self2.hwnd().OpenClipboard();
                SetClipboardData(CF::TEXT, ptr);
                CloseClipboard();

(modified from https://github.com/rodrigocfd/winsafe-examples/blob/941627b5ea93d7e7e564c91478a88a1d64e2dcf4/01_button_click/src/my_window.rs#L38-L41)

But when I click the button, the program crashes with:

error: process didn't exit successfully: `target\debug\app.exe` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.