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

Add support for WM_CREATE and WM_COMMAND messages on Windows #506

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
- Added `WindowBuilder::with_window_icon` and `Window::set_window_icon`, finally making it possible to set the window icon on Windows and X11. The `icon_loading` feature can be enabled to allow for icons to be easily loaded; see example program `window_icon.rs` for usage.
- Windows additionally has `WindowBuilderExt::with_taskbar_icon` and `WindowExt::set_taskbar_icon`.
- On Windows, fix panic when trying to call `set_fullscreen(None)` on a window that has not been fullscreened prior.
- Added `WindowBuilder::with_create_callback` on Windows, allowing to add custom application menus (see the `menu_bar_win32` example)
- Added the `WindowEvent::Command` to catch custom Win32 messages (see the `menu_bar_win32` for a usage example)
- Publicly exported the `winapi` crate, so that other crates can depend on the exact version of `winapi` that `winit` is using (to prevent version conflicts between different versions of `winapi` for external libraries)

# Version 0.13.1 (2018-04-26)

Expand Down
104 changes: 104 additions & 0 deletions examples/menu_bar_win32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
extern crate winit;

#[cfg(not(target_os = "windows"))]
fn main() {
println!("This example only works on Windows!");
}

#[cfg(target_os = "windows")]
const ID_FILE_NEW: u16 = 9001;
#[cfg(target_os = "windows")]
const ID_QUIT_APPLICATION: u16 = 9002;

#[cfg(target_os = "windows")]
fn main() {
use winit::os::windows::WindowBuilderExt;
let mut events_loop = winit::EventsLoop::new();

let _window = winit::WindowBuilder::new()
.with_title("A fantastic window!")
.with_create_callback(win32_create_callback)
.build(&events_loop)
.unwrap();

events_loop.run_forever(|event| {
match event {
winit::Event::WindowEvent { event, .. } => {
match event {
winit::WindowEvent::CloseRequested => winit::ControlFlow::Break,
winit::WindowEvent::Command(command) => {
match command {
ID_FILE_NEW => {
println!("New File button clicked!");
winit::ControlFlow::Continue
},
ID_QUIT_APPLICATION => {
println!("Quit Application button clicked!");
winit::ControlFlow::Break
},
_ => winit::ControlFlow::Continue,
}
},
_ => winit::ControlFlow::Continue,
}
},
_ => winit::ControlFlow::Continue,
}
});
}

#[cfg(target_os = "windows")]
fn win32_create_callback(hwnd: winit::winapi::shared::windef::HWND) {

// Encode a Rust `&str` as a Vec<u16> compatible with the Win32 API
fn str_to_wide_vec_u16(input: &str) -> Vec<u16> {
use std::ffi::OsString;
use std::os::windows::ffi::OsStrExt;
let mut s: Vec<u16> = OsString::from(input).as_os_str().encode_wide().into_iter().collect();
s.push(0);
s
}

use winit::winapi::um::winuser::{SetMenu, CreateMenu, AppendMenuW, DestroyMenu, MF_STRING, MF_POPUP};

let menu_bar = unsafe { CreateMenu() };
if menu_bar.is_null() {
println!("CreateMenu failed!");
return;
}

let popup_menu_1 = unsafe { CreateMenu() };
if popup_menu_1.is_null() {
println!("CreateMenu failed!");
unsafe { DestroyMenu(menu_bar) };
return;
}

let application_top_level_menu_str = str_to_wide_vec_u16("&Application");
let file_new_str = str_to_wide_vec_u16("New &File");
let quit_application_str = str_to_wide_vec_u16("&Quit");

// In order to keep this example simple, we don't check for errors here...

// Append the "New File" to the popup menu
unsafe { AppendMenuW(popup_menu_1, MF_STRING, ID_FILE_NEW as usize, file_new_str.as_ptr()) };

// Append the "Quit" to the popup menu
unsafe { AppendMenuW(popup_menu_1, MF_STRING, ID_QUIT_APPLICATION as usize, quit_application_str.as_ptr()) };

// Append the popup menu to the menu bar under the text "Application"
unsafe { AppendMenuW(menu_bar, MF_POPUP, popup_menu_1 as usize, application_top_level_menu_str.as_ptr()) };

// Add the menu bar to the window
if unsafe { SetMenu(hwnd, menu_bar) } == 0 { // <- window locks up here
println!("SetMenu failed!");
unsafe { DestroyMenu(popup_menu_1) };
unsafe { DestroyMenu(menu_bar) };
return;
}

// You can store the resources in your structure here if you want to
// (ex. for pushing new items to the menu).
unsafe { DestroyMenu(popup_menu_1) };
unsafe { DestroyMenu(menu_bar) };
}
10 changes: 10 additions & 0 deletions src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ pub enum WindowEvent {
/// * A user changes the desktop scaling value (e.g. in Control Panel on Windows).
/// * A user moves the application window to a display with a different DPI.
HiDPIFactorChanged(f32),

/// Custom command (currently only emitted on Windows). Win32 allows the user to
/// register custom command IDs for app menus, context menus and so on
/// (see the `WM_COMMAND` message in the Windows API). This event allows
/// you to, for example, react to when a user has clicked an item in an application menu.
///
/// The ID contained in the `Command` has to be registered by the user of the library,
/// usually by using the `WindowBuilder::with_create_callback` function.
/// For an example of how to use it, see the `menu_bar_win32` example.
Command(u16)
}

/// Represents raw hardware events that are not associated with any particular window.
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ extern crate image;

#[cfg(target_os = "windows")]
#[macro_use]
extern crate winapi;
pub extern crate winapi;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use]
extern crate objc;
Expand Down
18 changes: 18 additions & 0 deletions src/os/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ pub trait WindowBuilderExt {

/// This sets `ICON_BIG`. A good ceiling here is 256x256.
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;

/// When the window is crated, Windows emits a `WM_CREATE` message. This is the
/// time where you can register application menus, context menus and
/// custom window message IDs. Note that all the message IDs (ex. to track which
/// context menu item was clicked) will come back to you in the form of
/// `WindowEvent::Command(your_event_id)`.
///
/// Due to multithreading-unsafety of the Win32 API, this can't be a regular
/// message in the `EventsLoop`, since the `EventsLoop` is asynchronous and
/// Windows will freeze the application if you try to add menus from another thread.
/// The callback will be called immediately after the window has been created.
fn with_create_callback(self, callback: fn(HWND) -> ()) -> WindowBuilder;
}

impl WindowBuilderExt for WindowBuilder {
Expand All @@ -51,6 +63,12 @@ impl WindowBuilderExt for WindowBuilder {
self.platform_specific.taskbar_icon = taskbar_icon;
self
}

#[inline]
fn with_create_callback(mut self, callback: fn(HWND) -> ()) -> WindowBuilder {
self.platform_specific.wm_create_callback = Some(callback);
self
}
}

/// Additional methods on `MonitorId` that are specific to Windows.
Expand Down
9 changes: 9 additions & 0 deletions src/platform/windows/events_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,15 @@ pub unsafe extern "system" fn callback(window: HWND, msg: UINT,
0
},

winuser::WM_COMMAND => {
use events::WindowEvent::Command;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: Command(LOWORD(wparam as u32))
});
0
},

_ => {
if msg == *DESTROY_MSG_ID {
winuser::DestroyWindow(window);
Expand Down
3 changes: 3 additions & 0 deletions src/platform/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub use self::window::Window;
pub struct PlatformSpecificWindowBuilderAttributes {
pub parent: Option<HWND>,
pub taskbar_icon: Option<::Icon>,
/// Allows the user to register a callback that is called when the
/// WM_CREATE is emitted, allows to register application menus and context menus.
pub wm_create_callback: Option<fn(HWND) -> ()>,
}

unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
Expand Down
10 changes: 10 additions & 0 deletions src/platform/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,16 @@ unsafe fn init(
format!("{}", io::Error::last_os_error()))));
}

if let Some(callback) = pl_attribs.wm_create_callback {
// The WM_CREATE message is immediately dispatched after the CreateWindowExW call.
// Since we don't know the HWND before CreateWindowExW returns, we need to call the callback here.
// The callback can't be executed from the user thread, otherwise Windows
// will freeze the application. The callback also can't be executed from the EventLoop itself,
// since we have to know which callback corresponds to which window and the Win32 message loop
// doesn't allow to inject custon data. So the only place to call the callback is here
callback(handle);
}

let hdc = winuser::GetDC(handle);
if hdc.is_null() {
return Err(CreationError::OsError(format!("GetDC function failed: {}",
Expand Down