Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SendMessage Question #1052

Closed
RKennedy9064 opened this issue Jul 18, 2019 · 9 comments
Closed

SendMessage Question #1052

RKennedy9064 opened this issue Jul 18, 2019 · 9 comments
Labels
DS - windows F - question There's no such thing as a stupid one

Comments

@RKennedy9064
Copy link

So I've been looking at the alpha version and saw that you can send UserEvents to the event_loop by using an event_loop_proxy. Is it also possible to send a custom message to the event_loop from another application. I've tried using FindWindowA to get the hwnd for my application, then send a custom message to it by using SendMessageA, but it doesn't seem like the event_loop receives the message. Is this something that isn't currently possible using winit?

@goddessfreya goddessfreya added DS - windows F - question There's no such thing as a stupid one labels Jul 18, 2019
@goddessfreya
Copy link
Contributor

cc @Osspial

@Osspial
Copy link
Contributor

Osspial commented Jul 22, 2019

The low-level Windows event loop should be receiving your event, but Winit doesn't have any logic to translate your custom Windows message to a Winit message, so the high-level Winit event loop isn't dispatching it to the event handler closure.

If you want to translate your message into a Winit message, you should be able to use the Windows subclassing API to catch your custom message, than use send_event to dispatch your message into the Winit event loop.

@RKennedy9064
Copy link
Author

Thanks for the information so far. I've done some messing around and was able to register a subclass and successfully send a custom message, but I'm not quite sure how I should translate that back to the event loop proxy. This is a basic example of what I've come up with so far.

const WM_NS_LAUNCH: UINT = 0x0401;

#[derive(Debug, Clone, Copy)]
enum NsEvent {
    Launch,
}

#[derive(Debug)]
pub struct App {
    event_loop: EventLoop<NsEvent>,
    event_loop_proxy: EventLoopProxy<NsEvent>,
    window: Window,
}

pub fn run(self) {
    let hwnd = self.window.hwnd() as HWND;

    self.event_loop.run(move |event, _, control_flow| {
        match event {
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                ..
            } => {
                *control_flow = ControlFlow::Exit
            },

            Event::UserEvent(event) => {
                println!("{:?}", event);
                unsafe {
                    winuser::SendMessageW(
                        hwnd, 
                        WM_NS_LAUNCH, 
                        0, 
                        0
                        );
                }
            },

            _ => *control_flow = ControlFlow::Wait,
        }
    });
}

fn set_window_subclass(&self) {
    unsafe {
        commctrl::SetWindowSubclass(
            self.window.hwnd() as HWND, 
            Some(App::subclass_proc), 
            0, 
            0 // What should be passed here to make the even_loop_proxy callable.
        );
    }
}

unsafe extern "system" fn subclass_proc(
    hwnd: HWND,
    msg: UINT,
    wparam: WPARAM,
    lparam: LPARAM,
    _id: UINT_PTR,
    _data: DWORD_PTR,
) -> LRESULT {
    match msg {
        WM_NS_LAUNCH => {
            println!("Message received");
            // What would I need to cast data to in order to send my custom message to the event_loop_proxy
            TRUE as LRESULT
        },

        _ => commctrl::DefSubclassProc(hwnd, msg, wparam, lparam),
    }
}

At first I tried casting my app like this to send it in.

self as *mut App as usize

and then once inside the subclass_proc, I tried to access it like this, but I kept getting access violations.

let app = data as *mut App;
(*app).event_loop_proxy.send_event(NsEvent::Launch).ok();

@Osspial Do you have any advice, or possibly an example of the proper way to pass in my event_loop_proxy to the subclass_proc so that I can trigger my event in the main event loop? Any additional help would be greatly appreciated.

@Osspial
Copy link
Contributor

Osspial commented Jul 23, 2019

@RKennedy9064 Your original attempt to cast *mut App into a usize and pass it into SetWindowSubclass had the right idea. I'd guess that it didn't work because the App struct ended up getting moved after the SetWindowSubclass call, invalidating the pointer. Try putting App in a Box before passing it into the subclass creation function, so you can guarantee that it has a stable address and you don't end up with access violations.

We use that technique internally in Winit to pass custom data into the window callback, and if you'd like reference code I've linked the places we use it below (specifically, check out the subclass_window function and WM_DESTROY handler). I hope that helps!

pub(crate) fn subclass_window<T>(window: HWND, subclass_input: SubclassInput<T>) {
let input_ptr = Box::into_raw(Box::new(subclass_input));
let subclass_result = unsafe {
commctrl::SetWindowSubclass(
window,
Some(public_window_callback::<T>),
WINDOW_SUBCLASS_ID,
input_ptr as DWORD_PTR,
)
};
assert_eq!(subclass_result, 1);
}
/// Any window whose callback is configured to this function will have its events propagated
/// through the events loop of the thread the window was created in.
//
// This is the callback that is called by `DispatchMessage` in the events loop.
//
// Returning 0 tells the Win32 API that the message has been processed.
// FIXME: detect WM_DWMCOMPOSITIONCHANGED and call DwmEnableBlurBehindWindow if necessary
unsafe extern "system" fn public_window_callback<T>(
window: HWND,
msg: UINT,
wparam: WPARAM,
lparam: LPARAM,
_: UINT_PTR,
subclass_input_ptr: DWORD_PTR,
) -> LRESULT {
let subclass_input = &mut *(subclass_input_ptr as *mut SubclassInput<T>);
match msg {
winuser::WM_ENTERSIZEMOVE => {
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
if let Some(ref mut runner) = *runner {
runner.in_modal_loop = true;
}
0
}
winuser::WM_EXITSIZEMOVE => {
let mut runner = subclass_input.event_loop_runner.runner.borrow_mut();
if let Some(ref mut runner) = *runner {
runner.in_modal_loop = false;
}
0
}
winuser::WM_NCCREATE => {
enable_non_client_dpi_scaling(window);
commctrl::DefSubclassProc(window, msg, wparam, lparam)
}
winuser::WM_NCLBUTTONDOWN => {
// jumpstart the modal loop
winuser::RedrawWindow(
window,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT,
);
if wparam == winuser::HTCAPTION as _ {
winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, 0);
}
commctrl::DefSubclassProc(window, msg, wparam, lparam)
}
winuser::WM_CLOSE => {
use crate::event::WindowEvent::CloseRequested;
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: CloseRequested,
});
0
}
winuser::WM_DESTROY => {
use crate::event::WindowEvent::Destroyed;
ole2::RevokeDragDrop(window);
subclass_input.send_event(Event::WindowEvent {
window_id: RootWindowId(WindowId(window)),
event: Destroyed,
});
Box::from_raw(subclass_input);
drop(subclass_input);
0
}

@RKennedy9064
Copy link
Author

@Osspial I took your advice and put my struct in a box. I was able to pass it to the subclass_proc, but now I'm getting an Err(EventLoopClosed) when I try to use event_loop_proxy to send an event. This is the updated code I have so far.

fn set_window_subclass(&mut self) {
    unsafe {
        commctrl::SetWindowSubclass(
            self.window.hwnd() as HWND, 
            Some(App::subclass_proc), 
            0, 
            Box::into_raw(Box::new(self)) as DWORD_PTR,
        );
    }
}

unsafe extern "system" fn subclass_proc(
    hwnd: HWND,
    msg: UINT,
    wparam: WPARAM,
    lparam: LPARAM,
    _id: UINT_PTR,
    data: DWORD_PTR,
) -> LRESULT {
    match msg {
        WM_NS_LAUNCH => {
            let app = &mut *(data as *mut App);
            let event_loop = app.event_loop.borrow_mut();
            let event_loop_proxy = app.event_loop_proxy.borrow_mut();
            println!("{:?}", event_loop.primary_monitor());
            println!("{:?}", event_loop_proxy.send_event(NsEvent::Launch));

            0
        },

        _ => commctrl::DefSubclassProc(hwnd, msg, wparam, lparam),
    }
}

The part I'm confused about is why I'm getting an Err(EventLoopClosed). My main event loop is still responding to messages. I also tried to use the actual event_loop before using send_event and the output of primary_monitor returned this.

MonitorHandle { inner: MonitorHandle { hmonitor: HMonitor(0x10001), monitor_name: "\\.\DISPLAY1", primary: true, position: (0, 0), dimensions: (1920, 1080), hidpi_factor: 1.0 } }

Is there any reason why the event_loop_proxy would think the event_loop is closed? I feel like I'm missing something obvious here.

@Osspial
Copy link
Contributor

Osspial commented Jul 23, 2019

@RKennedy9064 can you post your full source code? It's hard to thoroughly understand your problem with just the snippet you posted.

@RKennedy9064
Copy link
Author

RKennedy9064 commented Jul 23, 2019

@Osspial Sure, here's a gist of my code. I haven't really used winit much, so it's possible I'm just doing things incorrectly. https://gist.github.com/RKennedy9064/d6d78ab3b30ee6c667c25f25ab304b61

Edit: I'm sending my message Event::NewEvents(StartCause::Init) for now, but the end goal is to send the message from a different application, then fire my user event to the event loop for processing.

@Osspial
Copy link
Contributor

Osspial commented Jul 23, 2019

@RKennedy9064 I think you're still invoking undefined behavior there, actually. Since set_window_subclass takes an &mut App, you're passing a *mut &mut App to subclass_proc instead of an *mut App.

I've modified the code to only pass the proxy, which should work:

#[derive(Debug)]
pub struct App {
    event_loop: EventLoop<NsEvent>,
    window: Window,
}

type SubclassInput = EventLoopProxy<NsEvent>;

impl App {
    pub fn new() -> Result<App, Box<dyn Error>> {
        let (event_loop, window) = init()?;

        let mut app = App {
            event_loop,
            window,
        };

        app.set_window_subclass();

        Ok(app)
    }

    pub fn run(self) {
        let hwnd = self.window.hwnd() as HWND;

        self.event_loop.run(move |event, _, control_flow| {
            match event {
                Event::NewEvents(StartCause::Init) => {
                    unsafe {
                        winuser::SendMessageW(
                            hwnd,
                            WM_NS_LAUNCH,
                            0,
                            0
                        );
                    }
                },

                Event::WindowEvent {
                    event: WindowEvent::CloseRequested,
                    ..
                } => {
                    *control_flow = ControlFlow::Exit
                },

                Event::UserEvent(event) => {
                    println!("{:?}", event);
                },

                _ => *control_flow = ControlFlow::Wait,
            }
        });
    }

    fn set_window_subclass(&mut self) {
        unsafe {
            let sender_box: Box<SubclassInput> = Box::new(self.event_loop.create_proxy());
            commctrl::SetWindowSubclass(
                self.window.hwnd() as HWND,
                Some(App::subclass_proc),
                0,
                Box::into_raw(sender_box) as DWORD_PTR,
            );
        }
    }

    unsafe extern "system" fn subclass_proc(
        hwnd: HWND,
        msg: UINT,
        wparam: WPARAM,
        lparam: LPARAM,
        _id: UINT_PTR,
        data: DWORD_PTR,
    ) -> LRESULT {
        match msg {
            WM_NS_LAUNCH => {
                let proxy = &mut *(data as *mut SubclassInput);
                println!("{:?}", proxy.send_event(NsEvent::Launch));

                0
            },

            winuser::WM_DESTROY => {
                // Clean up our allocated data.
                Box::from_raw(data as *mut SubclassInput);

                0
            }

            _ => commctrl::DefSubclassProc(hwnd, msg, wparam, lparam),
        }
    }
}

@RKennedy9064
Copy link
Author

@Osspial That worked perfectly. I just tested it out and received the event. I'm guessing I don't need to keep a reference of the proxy in the struct since it's being created by the event loop? I had issues with my window closing if I only stored the event_loop in my struct, so I figured I would store the proxy there also to be safe.

Thanks for taking the time to look into this and showing me how to properly pass in a pointer to a win32 proc from Rust. I'm still used to the C/C++ way of only needing to cast the reference to the pointer type and then recast it back into the struct in the callback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DS - windows F - question There's no such thing as a stupid one
Development

No branches or pull requests

3 participants