Switch win32 implementation to new design

This commit is contained in:
Pierre Krieger 2017-06-26 21:21:13 +02:00
parent 05cd9f2114
commit 61ba6dce7f
6 changed files with 1203 additions and 1054 deletions

View file

@ -18,7 +18,7 @@ pub trait WindowExt {
impl WindowExt for Window { impl WindowExt for Window {
#[inline] #[inline]
fn get_hwnd(&self) -> *mut libc::c_void { fn get_hwnd(&self) -> *mut libc::c_void {
self.window.platform_window() self.window.hwnd() as *mut _
} }
} }

View file

@ -1,426 +0,0 @@
use std::mem;
use std::ptr;
use std::cell::RefCell;
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use CursorState;
use WindowEvent as Event;
use KeyboardInput;
use events::ModifiersState;
use super::event;
use super::WindowState;
use user32;
use shell32;
use winapi;
/// There's no parameters passed to the callback function, so it needs to get
/// its context (the HWND, the Sender for events, etc.) stashed in
/// a thread-local variable.
thread_local!(pub static CONTEXT_STASH: RefCell<Option<ThreadLocalData>> = RefCell::new(None));
pub struct ThreadLocalData {
pub win: winapi::HWND,
pub sender: Sender<Event>,
pub window_state: Arc<Mutex<WindowState>>,
pub mouse_in_window: bool
}
/// Equivalent to the windows api [MINMAXINFO](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632605%28v=vs.85%29.aspx)
/// struct. Used because winapi-rs doesn't have this declared.
#[repr(C)]
#[allow(dead_code)]
struct MinMaxInfo {
reserved: winapi::POINT, // Do not use/change
max_size: winapi::POINT,
max_position: winapi::POINT,
min_track: winapi::POINT,
max_track: winapi::POINT
}
/// Checks that the window is the good one, and if so send the event to it.
fn send_event(input_window: winapi::HWND, event: Event) {
CONTEXT_STASH.with(|context_stash| {
let context_stash = context_stash.borrow();
let stored = match *context_stash {
None => return,
Some(ref v) => v
};
let &ThreadLocalData { ref win, ref sender, .. } = stored;
if win != &input_window {
return;
}
sender.send(event).ok(); // ignoring if closed
});
}
/// 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
pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
wparam: winapi::WPARAM, lparam: winapi::LPARAM)
-> winapi::LRESULT
{
match msg {
winapi::WM_DESTROY => {
use events::WindowEvent::Closed;
CONTEXT_STASH.with(|context_stash| {
let context_stash = context_stash.borrow();
let stored = match *context_stash {
None => return,
Some(ref v) => v
};
let &ThreadLocalData { ref win, .. } = stored;
if win == &window {
user32::PostQuitMessage(0);
}
});
send_event(window, Closed);
0
},
winapi::WM_ERASEBKGND => {
1
},
winapi::WM_SIZE => {
use events::WindowEvent::Resized;
let w = winapi::LOWORD(lparam as winapi::DWORD) as u32;
let h = winapi::HIWORD(lparam as winapi::DWORD) as u32;
send_event(window, Resized(w, h));
0
},
winapi::WM_MOVE => {
use events::WindowEvent::Moved;
let x = winapi::LOWORD(lparam as winapi::DWORD) as i32;
let y = winapi::HIWORD(lparam as winapi::DWORD) as i32;
send_event(window, Moved(x, y));
0
},
winapi::WM_CHAR => {
use std::mem;
use events::WindowEvent::ReceivedCharacter;
let chr: char = mem::transmute(wparam as u32);
send_event(window, ReceivedCharacter(chr));
0
},
// Prevents default windows menu hotkeys playing unwanted
// "ding" sounds. Alternatively could check for WM_SYSCOMMAND
// with wparam being SC_KEYMENU, but this may prevent some
// other unwanted default hotkeys as well.
winapi::WM_SYSCHAR => {
0
}
winapi::WM_MOUSEMOVE => {
use events::WindowEvent::{MouseEntered, MouseMoved};
let mouse_outside_window = CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
if let Some(context_stash) = context_stash.as_mut() {
if !context_stash.mouse_in_window {
context_stash.mouse_in_window = true;
return true;
}
}
false
});
if mouse_outside_window {
send_event(window, MouseEntered { device_id: DEVICE_ID });
// Calling TrackMouseEvent in order to receive mouse leave events.
user32::TrackMouseEvent(&mut winapi::TRACKMOUSEEVENT {
cbSize: mem::size_of::<winapi::TRACKMOUSEEVENT>() as winapi::DWORD,
dwFlags: winapi::TME_LEAVE,
hwndTrack: window,
dwHoverTime: winapi::HOVER_DEFAULT,
});
}
let x = winapi::GET_X_LPARAM(lparam) as f64;
let y = winapi::GET_Y_LPARAM(lparam) as f64;
send_event(window, MouseMoved { device_id: DEVICE_ID, position: (x, y) });
0
},
winapi::WM_MOUSELEAVE => {
use events::WindowEvent::MouseLeft;
let mouse_in_window = CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
if let Some(context_stash) = context_stash.as_mut() {
if context_stash.mouse_in_window {
context_stash.mouse_in_window = false;
return true;
}
}
false
});
if mouse_in_window {
send_event(window, MouseLeft { device_id: DEVICE_ID });
}
0
},
winapi::WM_MOUSEWHEEL => {
use events::WindowEvent::MouseWheel;
use events::MouseScrollDelta::LineDelta;
use events::TouchPhase;
let value = (wparam >> 16) as i16;
let value = value as i32;
let value = value as f32 / winapi::WHEEL_DELTA as f32;
send_event(window, MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved });
0
},
winapi::WM_KEYDOWN | winapi::WM_SYSKEYDOWN => {
use events::ElementState::Pressed;
if msg == winapi::WM_SYSKEYDOWN && wparam as i32 == winapi::VK_F4 {
user32::DefWindowProcW(window, msg, wparam, lparam)
} else {
let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam);
send_event(window, Event::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: Pressed,
scancode: scancode,
virtual_keycode: vkey,
modifiers: event::get_key_mods(),
}
});
0
}
},
winapi::WM_KEYUP | winapi::WM_SYSKEYUP => {
use events::ElementState::Released;
let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam);
send_event(window, Event::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: Released,
scancode: scancode,
virtual_keycode: vkey,
modifiers: event::get_key_mods(),
},
});
0
},
winapi::WM_LBUTTONDOWN => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Left;
use events::ElementState::Pressed;
send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left });
0
},
winapi::WM_LBUTTONUP => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Left;
use events::ElementState::Released;
send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Left });
0
},
winapi::WM_RBUTTONDOWN => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Right;
use events::ElementState::Pressed;
send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right });
0
},
winapi::WM_RBUTTONUP => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Right;
use events::ElementState::Released;
send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Right });
0
},
winapi::WM_MBUTTONDOWN => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Middle;
use events::ElementState::Pressed;
send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle });
0
},
winapi::WM_MBUTTONUP => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Middle;
use events::ElementState::Released;
send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Middle });
0
},
winapi::WM_XBUTTONDOWN => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Other;
use events::ElementState::Pressed;
let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; // waiting on PR for winapi to add GET_XBUTTON_WPARAM
send_event(window, MouseInput { device_id: DEVICE_ID, state: Pressed, button: Other(xbutton as u8) });
0
},
winapi::WM_XBUTTONUP => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Other;
use events::ElementState::Released;
let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int;
send_event(window, MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8) });
0
},
winapi::WM_INPUT => {
let mut data: winapi::RAWINPUT = mem::uninitialized();
let mut data_size = mem::size_of::<winapi::RAWINPUT>() as winapi::UINT;
user32::GetRawInputData(mem::transmute(lparam), winapi::RID_INPUT,
mem::transmute(&mut data), &mut data_size,
mem::size_of::<winapi::RAWINPUTHEADER>() as winapi::UINT);
if data.header.dwType == winapi::RIM_TYPEMOUSE {
let _x = data.mouse.lLastX; // FIXME: this is not always the relative movement
let _y = data.mouse.lLastY;
// TODO:
//send_event(window, Event::MouseRawMovement { x: x, y: y });
0
} else {
user32::DefWindowProcW(window, msg, wparam, lparam)
}
},
winapi::WM_SETFOCUS => {
use events::WindowEvent::Focused;
send_event(window, Focused(true));
0
},
winapi::WM_KILLFOCUS => {
use events::WindowEvent::Focused;
send_event(window, Focused(false));
0
},
winapi::WM_SETCURSOR => {
let call_def_window_proc = CONTEXT_STASH.with(|context_stash| {
let cstash = context_stash.borrow();
let mut call_def_window_proc = false;
if let Some(cstash) = cstash.as_ref() {
if let Ok(window_state) = cstash.window_state.lock() {
if cstash.mouse_in_window {
match window_state.cursor_state {
CursorState::Normal => {
user32::SetCursor(user32::LoadCursorW(
ptr::null_mut(),
window_state.cursor));
},
CursorState::Grab | CursorState::Hide => {
user32::SetCursor(ptr::null_mut());
}
}
} else {
call_def_window_proc = true;
}
}
}
call_def_window_proc
});
if call_def_window_proc {
user32::DefWindowProcW(window, msg, wparam, lparam)
} else {
0
}
},
winapi::WM_DROPFILES => {
use events::WindowEvent::DroppedFile;
let hdrop = wparam as winapi::HDROP;
let mut pathbuf: [u16; winapi::MAX_PATH] = mem::uninitialized();
let num_drops = shell32::DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
for i in 0..num_drops {
let nch = shell32::DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(),
winapi::MAX_PATH as u32) as usize;
if nch > 0 {
send_event(window, DroppedFile(OsString::from_wide(&pathbuf[0..nch]).into()));
}
}
shell32::DragFinish(hdrop);
0
},
winapi::WM_GETMINMAXINFO => {
let mmi = lparam as *mut MinMaxInfo;
//(*mmi).max_position = winapi::POINT { x: -8, y: -8 }; // The upper left corner of the window if it were maximized on the primary monitor.
//(*mmi).max_size = winapi::POINT { x: .., y: .. }; // The dimensions of the primary monitor.
CONTEXT_STASH.with(|context_stash| {
match context_stash.borrow().as_ref() {
Some(cstash) => {
let window_state = cstash.window_state.lock().unwrap();
match window_state.attributes.min_dimensions {
Some((width, height)) => {
(*mmi).min_track = winapi::POINT { x: width as i32, y: height as i32 };
},
None => { }
}
match window_state.attributes.max_dimensions {
Some((width, height)) => {
(*mmi).max_track = winapi::POINT { x: width as i32, y: height as i32 };
},
None => { }
}
},
None => { }
}
});
0
},
x if x == *super::WAKEUP_MSG_ID => {
// TODO: `Awakened` has been moved from the `WindowEvent` enum to the `Event` enum.
// This code needs to be updated to reflect this change.
//send_event(window, ::Event::Awakened);
0
},
_ => {
user32::DefWindowProcW(window, msg, wparam, lparam)
}
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: ::DeviceId = ::DeviceId(super::DeviceId);

View file

@ -0,0 +1,700 @@
//! An events loop on Win32 is a background thread.
//!
//! Creating an events loop spawns a thread and blocks it in a permanent Win32 events loop.
//! Destroying the events loop stops the thread.
//!
//! You can use the `execute_in_thread` method to execute some code in the background thread.
//! Since Win32 requires you to create a window in the right thread, you must use this method
//! to create a window.
//!
//! If you create a window whose class is set to `callback`, the window's events will be
//! propagated with `run_forever` and `poll_events`.
//! The closure passed to the `execute_in_thread` method takes an `Inserter` that you can use to
//! add a `WindowState` entry to a list of window to be used by the callback.
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::OsString;
use std::mem;
use std::os::windows::ffi::OsStringExt;
use std::os::windows::io::AsRawHandle;
use std::ptr;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use kernel32;
use shell32;
use user32;
use winapi;
use platform::platform::event;
use platform::platform::Cursor;
use platform::platform::WindowId;
use platform::platform::DEVICE_ID;
use ControlFlow;
use CursorState;
use Event;
use EventsLoopClosed;
use KeyboardInput;
use WindowAttributes;
use WindowEvent;
use WindowId as SuperWindowId;
/// Contains information about states and the window that the callback is going to use.
#[derive(Clone)]
pub struct WindowState {
/// Cursor to set at the next `WM_SETCURSOR` event received.
pub cursor: Cursor,
/// Cursor state to set at the next `WM_SETCURSOR` event received.
pub cursor_state: CursorState,
/// Used by `WM_GETMINMAXINFO`.
pub attributes: WindowAttributes,
/// Will contain `true` if the mouse is hovering the window.
pub mouse_in_window: bool,
}
/// Dummy object that allows inserting a window's state.
// We store a pointer in order to !impl Send and Sync.
pub struct Inserter(*mut u8);
impl Inserter {
/// Inserts a window's state for the callback to use. The state is removed automatically if the
/// callback receives a `WM_CLOSE` message for the window.
pub fn insert(&self, window: winapi::HWND, state: Arc<Mutex<WindowState>>) {
CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
let was_in = context_stash.as_mut().unwrap().windows.insert(window, state);
assert!(was_in.is_none());
});
}
}
pub struct EventsLoop {
// Id of the background thread from the Win32 API.
thread_id: winapi::DWORD,
// Receiver for the events. The sender is in the background thread.
receiver: mpsc::Receiver<Event>,
}
impl EventsLoop {
pub fn new() -> EventsLoop {
// The main events transfer channel.
let (tx, rx) = mpsc::channel();
// Local channel in order to block the `new()` function until the background thread has
// an events queue.
let (local_block_tx, local_block_rx) = mpsc::channel();
let thread = thread::spawn(move || {
CONTEXT_STASH.with(|context_stash| {
*context_stash.borrow_mut() = Some(ThreadLocalData {
sender: tx,
windows: HashMap::with_capacity(4),
});
});
unsafe {
let mut msg = mem::uninitialized();
// Calling `PostThreadMessageA` on a thread that does not have an events queue yet
// will fail. In order to avoid this situation, we call `PeekMessage` to initialize
// it.
user32::PeekMessageA(&mut msg, ptr::null_mut(), 0, 0, 0);
// Then only we unblock the `new()` function. We are sure that we don't call
// `PostThreadMessageA()` before `new()` returns.
local_block_tx.send(()).unwrap();
loop {
if user32::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
// Only happens if the message is `WM_QUIT`.
debug_assert_eq!(msg.message, winapi::WM_QUIT);
break;
}
match msg.message {
x if x == *EXEC_MSG_ID => {
let mut function: Box<Box<FnMut(Inserter)>> = Box::from_raw(msg.wParam as usize as *mut _);
function(Inserter(ptr::null_mut()));
},
x if x == *WAKEUP_MSG_ID => {
send_event(Event::Awakened);
},
_ => {
// Calls `callback` below.
user32::TranslateMessage(&msg);
user32::DispatchMessageW(&msg);
}
}
}
}
});
// Blocks this function until the background thread has an events loop. See other comments.
local_block_rx.recv().unwrap();
EventsLoop {
thread_id: unsafe { kernel32::GetThreadId(thread.as_raw_handle()) },
receiver: rx,
}
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(Event)
{
loop {
let event = match self.receiver.try_recv() {
Ok(e) => e,
Err(_) => return
};
callback(event);
}
}
pub fn run_forever<F>(&mut self, mut callback: F)
where F: FnMut(Event) -> ControlFlow
{
loop {
let event = match self.receiver.recv() {
Ok(e) => e,
Err(_) => return
};
let flow = callback(event);
match flow {
ControlFlow::Continue => continue,
ControlFlow::Break => break,
}
}
}
pub fn create_proxy(&self) -> EventsLoopProxy {
EventsLoopProxy {
thread_id: self.thread_id,
}
}
/// Executes a function in the background thread.
///
/// Note that we use a FnMut instead of a FnOnce because we're too lazy to create an equivalent
/// to the unstable FnBox.
///
/// The `Inserted` can be used to inject a `WindowState` for the callback to use. The state is
/// removed automatically if the callback receives a `WM_CLOSE` message for the window.
pub(super) fn execute_in_thread<F>(&self, function: F)
where F: FnMut(Inserter) + Send + 'static
{
unsafe {
let boxed = Box::new(function) as Box<FnMut(_)>;
let boxed2 = Box::new(boxed);
let raw = Box::into_raw(boxed2);
let res = user32::PostThreadMessageA(self.thread_id, *EXEC_MSG_ID,
raw as *mut () as usize as winapi::WPARAM, 0);
// PostThreadMessage can only fail if the thread ID is invalid (which shouldn't happen
// as the events loop is still alive) or if the queue is full.
assert!(res != 0, "PostThreadMessage failed ; is the messages queue full?");
}
}
}
impl Drop for EventsLoop {
fn drop(&mut self) {
unsafe {
// Posting `WM_QUIT` will cause `GetMessage` to stop.
user32::PostThreadMessageA(self.thread_id, winapi::WM_QUIT, 0, 0);
}
}
}
pub struct EventsLoopProxy {
thread_id: winapi::DWORD,
}
impl EventsLoopProxy {
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
unsafe {
if user32::PostThreadMessageA(self.thread_id, *WAKEUP_MSG_ID, 0, 0) != 0 {
Ok(())
} else {
// https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms644946(v=vs.85).aspx
// > If the function fails, the return value is zero. To get extended error
// > information, call GetLastError. GetLastError returns ERROR_INVALID_THREAD_ID
// > if idThread is not a valid thread identifier, or if the thread specified by
// > idThread does not have a message queue. GetLastError returns
// > ERROR_NOT_ENOUGH_QUOTA when the message limit is hit.
// TODO: handle ERROR_NOT_ENOUGH_QUOTA
Err(EventsLoopClosed)
}
}
}
}
lazy_static! {
// Message sent by the `EventsLoopProxy` when we want to wake up the thread.
// WPARAM and LPARAM are unused.
static ref WAKEUP_MSG_ID: u32 = {
unsafe {
user32::RegisterWindowMessageA("Winit::WakeupMsg".as_ptr() as *const i8)
}
};
// Message sent when we want to execute a closure in the thread.
// WPARAM contains a Box<Box<FnMut()>> that must be retreived with `Box::from_raw`,
// and LPARAM is unused.
static ref EXEC_MSG_ID: u32 = {
unsafe {
user32::RegisterWindowMessageA("Winit::ExecMsg".as_ptr() as *const i8)
}
};
}
// There's no parameters passed to the callback function, so it needs to get its context stashed
// in a thread-local variable.
thread_local!(static CONTEXT_STASH: RefCell<Option<ThreadLocalData>> = RefCell::new(None));
struct ThreadLocalData {
sender: mpsc::Sender<Event>,
windows: HashMap<winapi::HWND, Arc<Mutex<WindowState>>>,
}
// Utility function that dispatches an event on the current thread.
fn send_event(event: Event) {
CONTEXT_STASH.with(|context_stash| {
let context_stash = context_stash.borrow();
let _ = context_stash.as_ref().unwrap().sender.send(event); // Ignoring if closed
});
}
/// 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
pub unsafe extern "system" fn callback(window: winapi::HWND, msg: winapi::UINT,
wparam: winapi::WPARAM, lparam: winapi::LPARAM)
-> winapi::LRESULT
{
match msg {
winapi::WM_DESTROY => {
use events::WindowEvent::Closed;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: Closed
});
CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
context_stash.as_mut().unwrap().windows.remove(&window);
});
0
},
winapi::WM_ERASEBKGND => {
1
},
winapi::WM_SIZE => {
use events::WindowEvent::Resized;
let w = winapi::LOWORD(lparam as winapi::DWORD) as u32;
let h = winapi::HIWORD(lparam as winapi::DWORD) as u32;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: Resized(w, h),
});
0
},
winapi::WM_MOVE => {
use events::WindowEvent::Moved;
let x = winapi::LOWORD(lparam as winapi::DWORD) as i32;
let y = winapi::HIWORD(lparam as winapi::DWORD) as i32;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: Moved(x, y),
});
0
},
winapi::WM_CHAR => {
use std::mem;
use events::WindowEvent::ReceivedCharacter;
let chr: char = mem::transmute(wparam as u32);
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: ReceivedCharacter(chr),
});
0
},
// Prevents default windows menu hotkeys playing unwanted
// "ding" sounds. Alternatively could check for WM_SYSCOMMAND
// with wparam being SC_KEYMENU, but this may prevent some
// other unwanted default hotkeys as well.
winapi::WM_SYSCHAR => {
0
}
winapi::WM_MOUSEMOVE => {
use events::WindowEvent::{MouseEntered, MouseMoved};
let mouse_outside_window = CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
if let Some(context_stash) = context_stash.as_mut() {
if let Some(w) = context_stash.windows.get_mut(&window) {
let mut w = w.lock().unwrap();
if !w.mouse_in_window {
w.mouse_in_window = true;
return true;
}
}
}
false
});
if mouse_outside_window {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseEntered { device_id: DEVICE_ID },
});
// Calling TrackMouseEvent in order to receive mouse leave events.
user32::TrackMouseEvent(&mut winapi::TRACKMOUSEEVENT {
cbSize: mem::size_of::<winapi::TRACKMOUSEEVENT>() as winapi::DWORD,
dwFlags: winapi::TME_LEAVE,
hwndTrack: window,
dwHoverTime: winapi::HOVER_DEFAULT,
});
}
let x = winapi::GET_X_LPARAM(lparam) as f64;
let y = winapi::GET_Y_LPARAM(lparam) as f64;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseMoved { device_id: DEVICE_ID, position: (x, y) },
});
0
},
winapi::WM_MOUSELEAVE => {
use events::WindowEvent::MouseLeft;
let mouse_in_window = CONTEXT_STASH.with(|context_stash| {
let mut context_stash = context_stash.borrow_mut();
if let Some(context_stash) = context_stash.as_mut() {
if let Some(w) = context_stash.windows.get_mut(&window) {
let mut w = w.lock().unwrap();
if w.mouse_in_window {
w.mouse_in_window = false;
return true;
}
}
}
false
});
if mouse_in_window {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseLeft { device_id: DEVICE_ID }
});
}
0
},
winapi::WM_MOUSEWHEEL => {
use events::WindowEvent::MouseWheel;
use events::MouseScrollDelta::LineDelta;
use events::TouchPhase;
let value = (wparam >> 16) as i16;
let value = value as i32;
let value = value as f32 / winapi::WHEEL_DELTA as f32;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseWheel { device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved },
});
0
},
winapi::WM_KEYDOWN | winapi::WM_SYSKEYDOWN => {
use events::ElementState::Pressed;
if msg == winapi::WM_SYSKEYDOWN && wparam as i32 == winapi::VK_F4 {
user32::DefWindowProcW(window, msg, wparam, lparam)
} else {
let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam);
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: Pressed,
scancode: scancode,
virtual_keycode: vkey,
modifiers: event::get_key_mods(),
}
}
});
0
}
},
winapi::WM_KEYUP | winapi::WM_SYSKEYUP => {
use events::ElementState::Released;
let (scancode, vkey) = event::vkeycode_to_element(wparam, lparam);
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
input: KeyboardInput {
state: Released,
scancode: scancode,
virtual_keycode: vkey,
modifiers: event::get_key_mods(),
},
}
});
0
},
winapi::WM_LBUTTONDOWN => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Left;
use events::ElementState::Pressed;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Left }
});
0
},
winapi::WM_LBUTTONUP => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Left;
use events::ElementState::Released;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Left }
});
0
},
winapi::WM_RBUTTONDOWN => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Right;
use events::ElementState::Pressed;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Right }
});
0
},
winapi::WM_RBUTTONUP => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Right;
use events::ElementState::Released;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Right }
});
0
},
winapi::WM_MBUTTONDOWN => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Middle;
use events::ElementState::Pressed;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Middle }
});
0
},
winapi::WM_MBUTTONUP => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Middle;
use events::ElementState::Released;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Middle }
});
0
},
winapi::WM_XBUTTONDOWN => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Other;
use events::ElementState::Pressed;
let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int; // waiting on PR for winapi to add GET_XBUTTON_WPARAM
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Pressed, button: Other(xbutton as u8) }
});
0
},
winapi::WM_XBUTTONUP => {
use events::WindowEvent::MouseInput;
use events::MouseButton::Other;
use events::ElementState::Released;
let xbutton = winapi::HIWORD(wparam as winapi::DWORD) as winapi::c_int;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: MouseInput { device_id: DEVICE_ID, state: Released, button: Other(xbutton as u8) }
});
0
},
winapi::WM_INPUT => {
let mut data: winapi::RAWINPUT = mem::uninitialized();
let mut data_size = mem::size_of::<winapi::RAWINPUT>() as winapi::UINT;
user32::GetRawInputData(mem::transmute(lparam), winapi::RID_INPUT,
mem::transmute(&mut data), &mut data_size,
mem::size_of::<winapi::RAWINPUTHEADER>() as winapi::UINT);
if data.header.dwType == winapi::RIM_TYPEMOUSE {
let _x = data.mouse.lLastX; // FIXME: this is not always the relative movement
let _y = data.mouse.lLastY;
// TODO:
//send_event(window, Event::MouseRawMovement { x: x, y: y });
0
} else {
user32::DefWindowProcW(window, msg, wparam, lparam)
}
},
winapi::WM_SETFOCUS => {
use events::WindowEvent::Focused;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: Focused(true)
});
0
},
winapi::WM_KILLFOCUS => {
use events::WindowEvent::Focused;
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: Focused(false)
});
0
},
winapi::WM_SETCURSOR => {
let call_def_window_proc = CONTEXT_STASH.with(|context_stash| {
let cstash = context_stash.borrow();
let mut call_def_window_proc = false;
if let Some(cstash) = cstash.as_ref() {
if let Some(w_stash) = cstash.windows.get(&window) {
if let Ok(window_state) = w_stash.lock() {
if window_state.mouse_in_window {
match window_state.cursor_state {
CursorState::Normal => {
user32::SetCursor(user32::LoadCursorW(
ptr::null_mut(),
window_state.cursor));
},
CursorState::Grab | CursorState::Hide => {
user32::SetCursor(ptr::null_mut());
}
}
} else {
call_def_window_proc = true;
}
}
}
}
call_def_window_proc
});
if call_def_window_proc {
user32::DefWindowProcW(window, msg, wparam, lparam)
} else {
0
}
},
winapi::WM_DROPFILES => {
use events::WindowEvent::DroppedFile;
let hdrop = wparam as winapi::HDROP;
let mut pathbuf: [u16; winapi::MAX_PATH] = mem::uninitialized();
let num_drops = shell32::DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
for i in 0..num_drops {
let nch = shell32::DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(),
winapi::MAX_PATH as u32) as usize;
if nch > 0 {
send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(window)),
event: DroppedFile(OsString::from_wide(&pathbuf[0..nch]).into())
});
}
}
shell32::DragFinish(hdrop);
0
},
winapi::WM_GETMINMAXINFO => {
// Equivalent to the windows api [MINMAXINFO](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632605%28v=vs.85%29.aspx)
// struct. Used because winapi-rs doesn't have this declared.
// TODO: replace with struct from winapi-rs
#[repr(C)]
#[allow(dead_code)]
struct MinMaxInfo {
reserved: winapi::POINT, // Do not use/change
max_size: winapi::POINT,
max_position: winapi::POINT,
min_track: winapi::POINT,
max_track: winapi::POINT
}
let mmi = lparam as *mut MinMaxInfo;
//(*mmi).max_position = winapi::POINT { x: -8, y: -8 }; // The upper left corner of the window if it were maximized on the primary monitor.
//(*mmi).max_size = winapi::POINT { x: .., y: .. }; // The dimensions of the primary monitor.
CONTEXT_STASH.with(|context_stash| {
if let Some(cstash) = context_stash.borrow().as_ref() {
if let Some(wstash) = cstash.windows.get(&window) {
let window_state = wstash.lock().unwrap();
match window_state.attributes.min_dimensions {
Some((width, height)) => {
(*mmi).min_track = winapi::POINT { x: width as i32, y: height as i32 };
},
None => { }
}
match window_state.attributes.max_dimensions {
Some((width, height)) => {
(*mmi).max_track = winapi::POINT { x: width as i32, y: height as i32 };
},
None => { }
}
}
}
});
0
},
_ => {
user32::DefWindowProcW(window, msg, wparam, lparam)
}
}
}

View file

@ -1,259 +0,0 @@
use std::sync::{Arc, Mutex};
use std::io;
use std::ptr;
use std::mem;
use std::thread;
use super::callback;
use super::WindowState;
use super::Window;
use super::MonitorId;
use super::WindowWrapper;
use super::PlatformSpecificWindowBuilderAttributes;
use CreationError;
use CreationError::OsError;
use CursorState;
use WindowAttributes;
use std::ffi::{OsStr};
use std::os::windows::ffi::OsStrExt;
use std::sync::mpsc::channel;
use winapi;
use kernel32;
use dwmapi;
use user32;
pub fn new_window(window: &WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes) -> Result<Window, CreationError> {
let window = window.clone();
let attribs = pl_attribs.clone();
// initializing variables to be sent to the task
let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter())
.collect::<Vec<_>>();
let (tx, rx) = channel();
// `GetMessage` must be called in the same thread as CreateWindow, so we create a new thread
// dedicated to this window.
thread::spawn(move || {
unsafe {
// creating and sending the `Window`
match init(title, &window, attribs) {
Ok(w) => tx.send(Ok(w)).ok(),
Err(e) => {
tx.send(Err(e)).ok();
return;
}
};
// now that the `Window` struct is initialized, the main `Window::new()` function will
// return and this events loop will run in parallel
loop {
let mut msg = mem::uninitialized();
if user32::GetMessageW(&mut msg, ptr::null_mut(), 0, 0) == 0 {
break;
}
user32::TranslateMessage(&msg);
user32::DispatchMessageW(&msg); // calls `callback` (see the callback module)
}
}
});
rx.recv().unwrap()
}
unsafe fn init(title: Vec<u16>, window: &WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result<Window, CreationError> {
// registering the window class
let class_name = register_window_class();
// building a RECT object with coordinates
let mut rect = winapi::RECT {
left: 0, right: window.dimensions.unwrap_or((1024, 768)).0 as winapi::LONG,
top: 0, bottom: window.dimensions.unwrap_or((1024, 768)).1 as winapi::LONG,
};
// switching to fullscreen if necessary
// this means adjusting the window's position so that it overlaps the right monitor,
// and change the monitor's resolution if necessary
if window.monitor.is_some() {
let monitor = window.monitor.as_ref().unwrap();
try!(switch_to_fullscreen(&mut rect, monitor));
}
// computing the style and extended style of the window
let (ex_style, style) = if window.monitor.is_some() || !window.decorations {
(winapi::WS_EX_APPWINDOW,
//winapi::WS_POPUP is incompatible with winapi::WS_CHILD
if pl_attribs.parent.is_some() {
winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN
}
else {
winapi::WS_POPUP | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN
}
)
} else {
(winapi::WS_EX_APPWINDOW | winapi::WS_EX_WINDOWEDGE,
winapi::WS_OVERLAPPEDWINDOW | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN)
};
// adjusting the window coordinates using the style
user32::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
// creating the real window this time, by using the functions in `extra_functions`
let real_window = {
let (width, height) = if window.monitor.is_some() || window.dimensions.is_some() {
(Some(rect.right - rect.left), Some(rect.bottom - rect.top))
} else {
(None, None)
};
let (x, y) = if window.monitor.is_some() {
(Some(rect.left), Some(rect.top))
} else {
(None, None)
};
let mut style = if !window.visible {
style
} else {
style | winapi::WS_VISIBLE
};
if pl_attribs.parent.is_some() {
style |= winapi::WS_CHILD;
}
let handle = user32::CreateWindowExW(ex_style | winapi::WS_EX_ACCEPTFILES,
class_name.as_ptr(),
title.as_ptr() as winapi::LPCWSTR,
style | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN,
x.unwrap_or(winapi::CW_USEDEFAULT), y.unwrap_or(winapi::CW_USEDEFAULT),
width.unwrap_or(winapi::CW_USEDEFAULT), height.unwrap_or(winapi::CW_USEDEFAULT),
pl_attribs.parent.unwrap_or(ptr::null_mut()),
ptr::null_mut(), kernel32::GetModuleHandleW(ptr::null()),
ptr::null_mut());
if handle.is_null() {
return Err(OsError(format!("CreateWindowEx function failed: {}",
format!("{}", io::Error::last_os_error()))));
}
let hdc = user32::GetDC(handle);
if hdc.is_null() {
return Err(OsError(format!("GetDC function failed: {}",
format!("{}", io::Error::last_os_error()))));
}
WindowWrapper(handle, hdc)
};
// making the window transparent
if window.transparent {
let bb = winapi::DWM_BLURBEHIND {
dwFlags: 0x1, // FIXME: DWM_BB_ENABLE;
fEnable: 1,
hRgnBlur: ptr::null_mut(),
fTransitionOnMaximized: 0,
};
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
}
// calling SetForegroundWindow if fullscreen
if window.monitor.is_some() {
user32::SetForegroundWindow(real_window.0);
}
// Creating a mutex to track the current window state
let window_state = Arc::new(Mutex::new(WindowState {
cursor: winapi::IDC_ARROW, // use arrow by default
cursor_state: CursorState::Normal,
attributes: window.clone()
}));
// filling the CONTEXT_STASH task-local storage so that we can start receiving events
let events_receiver = {
let (tx, rx) = channel();
let mut tx = Some(tx);
callback::CONTEXT_STASH.with(|context_stash| {
let data = callback::ThreadLocalData {
win: real_window.0,
sender: tx.take().unwrap(),
window_state: window_state.clone(),
mouse_in_window: false
};
(*context_stash.borrow_mut()) = Some(data);
});
rx
};
// building the struct
Ok(Window {
window: real_window,
events_receiver: events_receiver,
window_state: window_state,
})
}
unsafe fn register_window_class() -> Vec<u16> {
let class_name = OsStr::new("Window Class").encode_wide().chain(Some(0).into_iter())
.collect::<Vec<_>>();
let class = winapi::WNDCLASSEXW {
cbSize: mem::size_of::<winapi::WNDCLASSEXW>() as winapi::UINT,
style: winapi::CS_HREDRAW | winapi::CS_VREDRAW | winapi::CS_OWNDC,
lpfnWndProc: Some(callback::callback),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: kernel32::GetModuleHandleW(ptr::null()),
hIcon: ptr::null_mut(),
hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly
hbrBackground: ptr::null_mut(),
lpszMenuName: ptr::null(),
lpszClassName: class_name.as_ptr(),
hIconSm: ptr::null_mut(),
};
// We ignore errors because registering the same window class twice would trigger
// an error, and because errors here are detected during CreateWindowEx anyway.
// Also since there is no weird element in the struct, there is no reason for this
// call to fail.
user32::RegisterClassExW(&class);
class_name
}
unsafe fn switch_to_fullscreen(rect: &mut winapi::RECT, monitor: &MonitorId)
-> Result<(), CreationError>
{
// adjusting the rect
{
let pos = monitor.get_position();
rect.left += pos.0 as winapi::LONG;
rect.right += pos.0 as winapi::LONG;
rect.top += pos.1 as winapi::LONG;
rect.bottom += pos.1 as winapi::LONG;
}
// changing device settings
let mut screen_settings: winapi::DEVMODEW = mem::zeroed();
screen_settings.dmSize = mem::size_of::<winapi::DEVMODEW>() as winapi::WORD;
screen_settings.dmPelsWidth = (rect.right - rect.left) as winapi::DWORD;
screen_settings.dmPelsHeight = (rect.bottom - rect.top) as winapi::DWORD;
screen_settings.dmBitsPerPel = 32; // TODO: ?
screen_settings.dmFields = winapi::DM_BITSPERPEL | winapi::DM_PELSWIDTH | winapi::DM_PELSHEIGHT;
let result = user32::ChangeDisplaySettingsExW(monitor.get_adapter_name().as_ptr(),
&mut screen_settings, ptr::null_mut(),
winapi::CDS_FULLSCREEN, ptr::null_mut());
if result != winapi::DISP_CHANGE_SUCCESSFUL {
return Err(OsError(format!("ChangeDisplaySettings failed: {}", result)));
}
Ok(())
}

View file

@ -1,21 +1,10 @@
#![cfg(target_os = "windows")] #![cfg(target_os = "windows")]
use std::mem; use winapi;
use std::ptr;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::os::raw::c_int;
use std::sync::{
Arc,
Mutex
};
use std::sync::mpsc::Receiver;
use {CreationError, WindowEvent as Event, MouseCursor};
use CursorState;
use WindowAttributes; pub use self::events_loop::{EventsLoop, EventsLoopProxy};
pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor};
gen_api_transition!(); pub use self::window::Window;
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes { pub struct PlatformSpecificWindowBuilderAttributes {
@ -25,361 +14,23 @@ pub struct PlatformSpecificWindowBuilderAttributes {
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {} unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {} unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
#[derive(Clone, Default)] // TODO: document what this means
pub struct PlatformSpecificHeadlessBuilderAttributes;
pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor};
use winapi;
use user32;
use kernel32;
mod callback;
mod event;
mod init;
mod monitor;
lazy_static! {
static ref WAKEUP_MSG_ID: u32 = unsafe { user32::RegisterWindowMessageA("Glutin::EventID".as_ptr() as *const i8) };
}
/// Cursor
pub type Cursor = *const winapi::wchar_t; pub type Cursor = *const winapi::wchar_t;
/// Contains information about states and the window for the callback. // TODO: remove
#[derive(Clone)] pub type Window2 = Window;
pub struct WindowState {
pub cursor: Cursor,
pub cursor_state: CursorState,
pub attributes: WindowAttributes
}
/// The Win32 implementation of the main `Window` object. // Constant device ID, to be removed when this backend is updated to report real device IDs.
pub struct Window { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// Main handle for the window. pub struct DeviceId;
window: WindowWrapper, const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
/// Receiver for the events dispatched by the window callback. #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
events_receiver: Receiver<Event>, pub struct WindowId(winapi::HWND);
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
/// The current window state. mod event;
window_state: Arc<Mutex<WindowState>>, mod events_loop;
} mod monitor;
mod window;
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
/// A simple wrapper that destroys the window when it is destroyed.
#[doc(hidden)]
pub struct WindowWrapper(winapi::HWND, winapi::HDC);
impl Drop for WindowWrapper {
#[inline]
fn drop(&mut self) {
unsafe {
user32::DestroyWindow(self.0);
}
}
}
#[derive(Clone)]
pub struct WindowProxy {
hwnd: winapi::HWND,
}
unsafe impl Send for WindowProxy {}
unsafe impl Sync for WindowProxy {}
impl WindowProxy {
#[inline]
pub fn wakeup_event_loop(&self) {
unsafe {
user32::PostMessageA(self.hwnd, *WAKEUP_MSG_ID, 0, 0);
}
}
}
impl Window {
/// See the docs in the crate root file.
pub fn new(window: &WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes)
-> Result<Window, CreationError>
{
init::new_window(window, pl_attribs)
}
/// See the docs in the crate root file.
///
/// Calls SetWindowText on the HWND.
pub fn set_title(&self, text: &str) {
let text = OsStr::new(text).encode_wide().chain(Some(0).into_iter())
.collect::<Vec<_>>();
unsafe {
user32::SetWindowTextW(self.window.0, text.as_ptr() as winapi::LPCWSTR);
}
}
#[inline]
pub fn show(&self) {
unsafe {
user32::ShowWindow(self.window.0, winapi::SW_SHOW);
}
}
#[inline]
pub fn hide(&self) {
unsafe {
user32::ShowWindow(self.window.0, winapi::SW_HIDE);
}
}
/// See the docs in the crate root file.
pub fn get_position(&self) -> Option<(i32, i32)> {
use std::mem;
let mut placement: winapi::WINDOWPLACEMENT = unsafe { mem::zeroed() };
placement.length = mem::size_of::<winapi::WINDOWPLACEMENT>() as winapi::UINT;
if unsafe { user32::GetWindowPlacement(self.window.0, &mut placement) } == 0 {
return None
}
let ref rect = placement.rcNormalPosition;
Some((rect.left as i32, rect.top as i32))
}
/// See the docs in the crate root file.
pub fn set_position(&self, x: i32, y: i32) {
unsafe {
user32::SetWindowPos(self.window.0, ptr::null_mut(), x as c_int, y as c_int,
0, 0, winapi::SWP_NOZORDER | winapi::SWP_NOSIZE);
user32::UpdateWindow(self.window.0);
}
}
/// See the docs in the crate root file.
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
let mut rect: winapi::RECT = unsafe { mem::uninitialized() };
if unsafe { user32::GetClientRect(self.window.0, &mut rect) } == 0 {
return None
}
Some((
(rect.right - rect.left) as u32,
(rect.bottom - rect.top) as u32
))
}
/// See the docs in the crate root file.
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
let mut rect: winapi::RECT = unsafe { mem::uninitialized() };
if unsafe { user32::GetWindowRect(self.window.0, &mut rect) } == 0 {
return None
}
Some((
(rect.right - rect.left) as u32,
(rect.bottom - rect.top) as u32
))
}
/// See the docs in the crate root file.
pub fn set_inner_size(&self, x: u32, y: u32) {
unsafe {
// Calculate the outer size based upon the specified inner size
let mut rect = winapi::RECT { top: 0, left: 0, bottom: y as winapi::LONG, right: x as winapi::LONG };
let dw_style = user32::GetWindowLongA(self.window.0, winapi::GWL_STYLE) as winapi::DWORD;
let b_menu = !user32::GetMenu(self.window.0).is_null() as winapi::BOOL;
let dw_style_ex = user32::GetWindowLongA(self.window.0, winapi::GWL_EXSTYLE) as winapi::DWORD;
user32::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex);
let outer_x = (rect.right - rect.left).abs() as c_int;
let outer_y = (rect.top - rect.bottom).abs() as c_int;
user32::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y,
winapi::SWP_NOZORDER | winapi::SWP_NOREPOSITION | winapi::SWP_NOMOVE);
user32::UpdateWindow(self.window.0);
}
}
#[inline]
pub fn create_window_proxy(&self) -> WindowProxy {
WindowProxy { hwnd: self.window.0 }
}
/// See the docs in the crate root file.
#[inline]
pub fn poll_events(&self) -> PollEventsIterator {
PollEventsIterator {
window: self,
}
}
/// See the docs in the crate root file.
#[inline]
pub fn wait_events(&self) -> WaitEventsIterator {
WaitEventsIterator {
window: self,
}
}
#[inline]
pub fn platform_display(&self) -> *mut ::libc::c_void {
// What should this return on win32?
// It could be GetDC(NULL), but that requires a ReleaseDC()
// to avoid leaking the DC.
ptr::null_mut()
}
#[inline]
pub fn platform_window(&self) -> *mut ::libc::c_void {
self.window.0 as *mut ::libc::c_void
}
#[inline]
pub fn set_window_resize_callback(&mut self, _: Option<fn(u32, u32)>) {
}
#[inline]
pub fn set_cursor(&self, _cursor: MouseCursor) {
let cursor_id = match _cursor {
MouseCursor::Arrow | MouseCursor::Default => winapi::IDC_ARROW,
MouseCursor::Hand => winapi::IDC_HAND,
MouseCursor::Crosshair => winapi::IDC_CROSS,
MouseCursor::Text | MouseCursor::VerticalText => winapi::IDC_IBEAM,
MouseCursor::NotAllowed | MouseCursor::NoDrop => winapi::IDC_NO,
MouseCursor::EResize => winapi::IDC_SIZEWE,
MouseCursor::NResize => winapi::IDC_SIZENS,
MouseCursor::WResize => winapi::IDC_SIZEWE,
MouseCursor::SResize => winapi::IDC_SIZENS,
MouseCursor::EwResize | MouseCursor::ColResize => winapi::IDC_SIZEWE,
MouseCursor::NsResize | MouseCursor::RowResize => winapi::IDC_SIZENS,
MouseCursor::Wait | MouseCursor::Progress => winapi::IDC_WAIT,
MouseCursor::Help => winapi::IDC_HELP,
_ => winapi::IDC_ARROW, // use arrow for the missing cases.
};
let mut cur = self.window_state.lock().unwrap();
cur.cursor = cursor_id;
}
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
let mut current_state = self.window_state.lock().unwrap();
let foreground_thread_id = unsafe { user32::GetWindowThreadProcessId(self.window.0, ptr::null_mut()) };
let current_thread_id = unsafe { kernel32::GetCurrentThreadId() };
unsafe { user32::AttachThreadInput(foreground_thread_id, current_thread_id, 1) };
let res = match (state, current_state.cursor_state) {
(CursorState::Normal, CursorState::Normal) => Ok(()),
(CursorState::Hide, CursorState::Hide) => Ok(()),
(CursorState::Grab, CursorState::Grab) => Ok(()),
(CursorState::Hide, CursorState::Normal) => {
current_state.cursor_state = CursorState::Hide;
Ok(())
},
(CursorState::Normal, CursorState::Hide) => {
current_state.cursor_state = CursorState::Normal;
Ok(())
},
(CursorState::Grab, CursorState::Normal) | (CursorState::Grab, CursorState::Hide) => {
unsafe {
let mut rect = mem::uninitialized();
if user32::GetClientRect(self.window.0, &mut rect) == 0 {
return Err(format!("GetWindowRect failed"));
}
user32::ClientToScreen(self.window.0, mem::transmute(&mut rect.left));
user32::ClientToScreen(self.window.0, mem::transmute(&mut rect.right));
if user32::ClipCursor(&rect) == 0 {
return Err(format!("ClipCursor failed"));
}
current_state.cursor_state = CursorState::Grab;
Ok(())
}
},
(CursorState::Normal, CursorState::Grab) => {
unsafe {
if user32::ClipCursor(ptr::null()) == 0 {
return Err(format!("ClipCursor failed"));
}
current_state.cursor_state = CursorState::Normal;
Ok(())
}
},
_ => unimplemented!(),
};
unsafe { user32::AttachThreadInput(foreground_thread_id, current_thread_id, 0) };
res
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
1.0
}
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
let mut point = winapi::POINT {
x: x,
y: y,
};
unsafe {
if user32::ClientToScreen(self.window.0, &mut point) == 0 {
return Err(());
}
if user32::SetCursorPos(point.x, point.y) == 0 {
return Err(());
}
}
Ok(())
}
}
impl Drop for Window {
#[inline]
fn drop(&mut self) {
unsafe {
user32::PostMessageW(self.window.0, winapi::WM_DESTROY, 0, 0);
}
}
}
pub struct PollEventsIterator<'a> {
window: &'a Window,
}
impl<'a> Iterator for PollEventsIterator<'a> {
type Item = Event;
#[inline]
fn next(&mut self) -> Option<Event> {
self.window.events_receiver.try_recv().ok()
}
}
pub struct WaitEventsIterator<'a> {
window: &'a Window,
}
impl<'a> Iterator for WaitEventsIterator<'a> {
type Item = Event;
#[inline]
fn next(&mut self) -> Option<Event> {
self.window.events_receiver.recv().ok()
}
}

View file

@ -0,0 +1,483 @@
#![cfg(target_os = "windows")]
use std::ffi::OsStr;
use std::io;
use std::mem;
use std::os::raw;
use std::os::windows::ffi::OsStrExt;
use std::ptr;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::mpsc::channel;
use platform::platform::events_loop;
use platform::platform::EventsLoop;
use platform::platform::PlatformSpecificWindowBuilderAttributes;
use platform::platform::MonitorId;
use platform::platform::WindowId;
use CreationError;
use CursorState;
use MouseCursor;
use WindowAttributes;
use dwmapi;
use kernel32;
use user32;
use winapi;
/// The Win32 implementation of the main `Window` object.
pub struct Window {
/// Main handle for the window.
window: WindowWrapper,
/// The current window state.
window_state: Arc<Mutex<events_loop::WindowState>>,
}
unsafe impl Send for Window {}
unsafe impl Sync for Window {}
impl Window {
pub fn new(events_loop: &EventsLoop, w_attr: &WindowAttributes,
pl_attr: &PlatformSpecificWindowBuilderAttributes) -> Result<Window, CreationError>
{
let mut w_attr = Some(w_attr.clone());
let mut pl_attr = Some(pl_attr.clone());
let (tx, rx) = channel();
events_loop.execute_in_thread(move |inserter| {
// We dispatch an `init` function because of code style.
let win = unsafe { init(w_attr.take().unwrap(), pl_attr.take().unwrap(), inserter) };
let _ = tx.send(win);
});
rx.recv().unwrap()
}
pub fn set_title(&self, text: &str) {
unsafe {
let text = OsStr::new(text).encode_wide().chain(Some(0).into_iter())
.collect::<Vec<_>>();
user32::SetWindowTextW(self.window.0, text.as_ptr() as winapi::LPCWSTR);
}
}
#[inline]
pub fn show(&self) {
unsafe {
user32::ShowWindow(self.window.0, winapi::SW_SHOW);
}
}
#[inline]
pub fn hide(&self) {
unsafe {
user32::ShowWindow(self.window.0, winapi::SW_HIDE);
}
}
/// See the docs in the crate root file.
pub fn get_position(&self) -> Option<(i32, i32)> {
use std::mem;
let mut placement: winapi::WINDOWPLACEMENT = unsafe { mem::zeroed() };
placement.length = mem::size_of::<winapi::WINDOWPLACEMENT>() as winapi::UINT;
if unsafe { user32::GetWindowPlacement(self.window.0, &mut placement) } == 0 {
return None
}
let ref rect = placement.rcNormalPosition;
Some((rect.left as i32, rect.top as i32))
}
/// See the docs in the crate root file.
pub fn set_position(&self, x: i32, y: i32) {
unsafe {
user32::SetWindowPos(self.window.0, ptr::null_mut(), x as raw::c_int, y as raw::c_int,
0, 0, winapi::SWP_NOZORDER | winapi::SWP_NOSIZE);
user32::UpdateWindow(self.window.0);
}
}
/// See the docs in the crate root file.
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
let mut rect: winapi::RECT = unsafe { mem::uninitialized() };
if unsafe { user32::GetClientRect(self.window.0, &mut rect) } == 0 {
return None
}
Some((
(rect.right - rect.left) as u32,
(rect.bottom - rect.top) as u32
))
}
/// See the docs in the crate root file.
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
let mut rect: winapi::RECT = unsafe { mem::uninitialized() };
if unsafe { user32::GetWindowRect(self.window.0, &mut rect) } == 0 {
return None
}
Some((
(rect.right - rect.left) as u32,
(rect.bottom - rect.top) as u32
))
}
/// See the docs in the crate root file.
pub fn set_inner_size(&self, x: u32, y: u32) {
unsafe {
// Calculate the outer size based upon the specified inner size
let mut rect = winapi::RECT { top: 0, left: 0, bottom: y as winapi::LONG, right: x as winapi::LONG };
let dw_style = user32::GetWindowLongA(self.window.0, winapi::GWL_STYLE) as winapi::DWORD;
let b_menu = !user32::GetMenu(self.window.0).is_null() as winapi::BOOL;
let dw_style_ex = user32::GetWindowLongA(self.window.0, winapi::GWL_EXSTYLE) as winapi::DWORD;
user32::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex);
let outer_x = (rect.right - rect.left).abs() as raw::c_int;
let outer_y = (rect.top - rect.bottom).abs() as raw::c_int;
user32::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y,
winapi::SWP_NOZORDER | winapi::SWP_NOREPOSITION | winapi::SWP_NOMOVE);
user32::UpdateWindow(self.window.0);
}
}
// TODO: remove
pub fn platform_display(&self) -> *mut ::libc::c_void {
panic!() // Deprecated function ; we don't care anymore
}
// TODO: remove
pub fn platform_window(&self) -> *mut ::libc::c_void {
panic!() // Deprecated function ; we don't care anymore
}
/// Returns the `hwnd` of this window.
#[inline]
pub fn hwnd(&self) -> winapi::HWND {
self.window.0
}
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
let cursor_id = match cursor {
MouseCursor::Arrow | MouseCursor::Default => winapi::IDC_ARROW,
MouseCursor::Hand => winapi::IDC_HAND,
MouseCursor::Crosshair => winapi::IDC_CROSS,
MouseCursor::Text | MouseCursor::VerticalText => winapi::IDC_IBEAM,
MouseCursor::NotAllowed | MouseCursor::NoDrop => winapi::IDC_NO,
MouseCursor::EResize => winapi::IDC_SIZEWE,
MouseCursor::NResize => winapi::IDC_SIZENS,
MouseCursor::WResize => winapi::IDC_SIZEWE,
MouseCursor::SResize => winapi::IDC_SIZENS,
MouseCursor::EwResize | MouseCursor::ColResize => winapi::IDC_SIZEWE,
MouseCursor::NsResize | MouseCursor::RowResize => winapi::IDC_SIZENS,
MouseCursor::Wait | MouseCursor::Progress => winapi::IDC_WAIT,
MouseCursor::Help => winapi::IDC_HELP,
_ => winapi::IDC_ARROW, // use arrow for the missing cases.
};
let mut cur = self.window_state.lock().unwrap();
cur.cursor = cursor_id;
}
// TODO: it should be possible to rework this function by using the `execute_in_thread` method
// of the events loop.
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
let mut current_state = self.window_state.lock().unwrap();
let foreground_thread_id = unsafe { user32::GetWindowThreadProcessId(self.window.0, ptr::null_mut()) };
let current_thread_id = unsafe { kernel32::GetCurrentThreadId() };
unsafe { user32::AttachThreadInput(foreground_thread_id, current_thread_id, 1) };
let res = match (state, current_state.cursor_state) {
(CursorState::Normal, CursorState::Normal) => Ok(()),
(CursorState::Hide, CursorState::Hide) => Ok(()),
(CursorState::Grab, CursorState::Grab) => Ok(()),
(CursorState::Hide, CursorState::Normal) => {
current_state.cursor_state = CursorState::Hide;
Ok(())
},
(CursorState::Normal, CursorState::Hide) => {
current_state.cursor_state = CursorState::Normal;
Ok(())
},
(CursorState::Grab, CursorState::Normal) | (CursorState::Grab, CursorState::Hide) => {
unsafe {
let mut rect = mem::uninitialized();
if user32::GetClientRect(self.window.0, &mut rect) == 0 {
return Err(format!("GetWindowRect failed"));
}
user32::ClientToScreen(self.window.0, mem::transmute(&mut rect.left));
user32::ClientToScreen(self.window.0, mem::transmute(&mut rect.right));
if user32::ClipCursor(&rect) == 0 {
return Err(format!("ClipCursor failed"));
}
current_state.cursor_state = CursorState::Grab;
Ok(())
}
},
(CursorState::Normal, CursorState::Grab) => {
unsafe {
if user32::ClipCursor(ptr::null()) == 0 {
return Err(format!("ClipCursor failed"));
}
current_state.cursor_state = CursorState::Normal;
Ok(())
}
},
_ => unimplemented!(),
};
unsafe { user32::AttachThreadInput(foreground_thread_id, current_thread_id, 0) };
res
}
#[inline]
pub fn hidpi_factor(&self) -> f32 {
1.0
}
pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> {
let mut point = winapi::POINT {
x: x,
y: y,
};
unsafe {
if user32::ClientToScreen(self.window.0, &mut point) == 0 {
return Err(());
}
if user32::SetCursorPos(point.x, point.y) == 0 {
return Err(());
}
}
Ok(())
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.window.0)
}
}
impl Drop for Window {
#[inline]
fn drop(&mut self) {
unsafe {
user32::PostMessageW(self.window.0, winapi::WM_DESTROY, 0, 0);
}
}
}
/// A simple wrapper that destroys the window when it is destroyed.
#[doc(hidden)]
pub struct WindowWrapper(winapi::HWND, winapi::HDC);
impl Drop for WindowWrapper {
#[inline]
fn drop(&mut self) {
unsafe {
user32::DestroyWindow(self.0);
}
}
}
unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes,
inserter: events_loop::Inserter) -> Result<Window, CreationError> {
let title = OsStr::new(&window.title).encode_wide().chain(Some(0).into_iter())
.collect::<Vec<_>>();
// registering the window class
let class_name = register_window_class();
// building a RECT object with coordinates
let mut rect = winapi::RECT {
left: 0, right: window.dimensions.unwrap_or((1024, 768)).0 as winapi::LONG,
top: 0, bottom: window.dimensions.unwrap_or((1024, 768)).1 as winapi::LONG,
};
// switching to fullscreen if necessary
// this means adjusting the window's position so that it overlaps the right monitor,
// and change the monitor's resolution if necessary
if window.monitor.is_some() {
let monitor = window.monitor.as_ref().unwrap();
try!(switch_to_fullscreen(&mut rect, monitor));
}
// computing the style and extended style of the window
let (ex_style, style) = if window.monitor.is_some() || !window.decorations {
(winapi::WS_EX_APPWINDOW,
//winapi::WS_POPUP is incompatible with winapi::WS_CHILD
if pl_attribs.parent.is_some() {
winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN
}
else {
winapi::WS_POPUP | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN
}
)
} else {
(winapi::WS_EX_APPWINDOW | winapi::WS_EX_WINDOWEDGE,
winapi::WS_OVERLAPPEDWINDOW | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN)
};
// adjusting the window coordinates using the style
user32::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
// creating the real window this time, by using the functions in `extra_functions`
let real_window = {
let (width, height) = if window.monitor.is_some() || window.dimensions.is_some() {
(Some(rect.right - rect.left), Some(rect.bottom - rect.top))
} else {
(None, None)
};
let (x, y) = if window.monitor.is_some() {
(Some(rect.left), Some(rect.top))
} else {
(None, None)
};
let mut style = if !window.visible {
style
} else {
style | winapi::WS_VISIBLE
};
if pl_attribs.parent.is_some() {
style |= winapi::WS_CHILD;
}
let handle = user32::CreateWindowExW(ex_style | winapi::WS_EX_ACCEPTFILES,
class_name.as_ptr(),
title.as_ptr() as winapi::LPCWSTR,
style | winapi::WS_CLIPSIBLINGS | winapi::WS_CLIPCHILDREN,
x.unwrap_or(winapi::CW_USEDEFAULT), y.unwrap_or(winapi::CW_USEDEFAULT),
width.unwrap_or(winapi::CW_USEDEFAULT), height.unwrap_or(winapi::CW_USEDEFAULT),
pl_attribs.parent.unwrap_or(ptr::null_mut()),
ptr::null_mut(), kernel32::GetModuleHandleW(ptr::null()),
ptr::null_mut());
if handle.is_null() {
return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}",
format!("{}", io::Error::last_os_error()))));
}
let hdc = user32::GetDC(handle);
if hdc.is_null() {
return Err(CreationError::OsError(format!("GetDC function failed: {}",
format!("{}", io::Error::last_os_error()))));
}
WindowWrapper(handle, hdc)
};
// Creating a mutex to track the current window state
let window_state = Arc::new(Mutex::new(events_loop::WindowState {
cursor: winapi::IDC_ARROW, // use arrow by default
cursor_state: CursorState::Normal,
attributes: window.clone(),
mouse_in_window: false,
}));
inserter.insert(real_window.0, window_state.clone());
// making the window transparent
if window.transparent {
let bb = winapi::DWM_BLURBEHIND {
dwFlags: 0x1, // FIXME: DWM_BB_ENABLE;
fEnable: 1,
hRgnBlur: ptr::null_mut(),
fTransitionOnMaximized: 0,
};
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
}
// calling SetForegroundWindow if fullscreen
if window.monitor.is_some() {
user32::SetForegroundWindow(real_window.0);
}
// Building the struct.
Ok(Window {
window: real_window,
window_state: window_state,
})
}
unsafe fn register_window_class() -> Vec<u16> {
let class_name = OsStr::new("Window Class").encode_wide().chain(Some(0).into_iter())
.collect::<Vec<_>>();
let class = winapi::WNDCLASSEXW {
cbSize: mem::size_of::<winapi::WNDCLASSEXW>() as winapi::UINT,
style: winapi::CS_HREDRAW | winapi::CS_VREDRAW | winapi::CS_OWNDC,
lpfnWndProc: Some(events_loop::callback),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: kernel32::GetModuleHandleW(ptr::null()),
hIcon: ptr::null_mut(),
hCursor: ptr::null_mut(), // must be null in order for cursor state to work properly
hbrBackground: ptr::null_mut(),
lpszMenuName: ptr::null(),
lpszClassName: class_name.as_ptr(),
hIconSm: ptr::null_mut(),
};
// We ignore errors because registering the same window class twice would trigger
// an error, and because errors here are detected during CreateWindowEx anyway.
// Also since there is no weird element in the struct, there is no reason for this
// call to fail.
user32::RegisterClassExW(&class);
class_name
}
unsafe fn switch_to_fullscreen(rect: &mut winapi::RECT, monitor: &MonitorId)
-> Result<(), CreationError>
{
// adjusting the rect
{
let pos = monitor.get_position();
rect.left += pos.0 as winapi::LONG;
rect.right += pos.0 as winapi::LONG;
rect.top += pos.1 as winapi::LONG;
rect.bottom += pos.1 as winapi::LONG;
}
// changing device settings
let mut screen_settings: winapi::DEVMODEW = mem::zeroed();
screen_settings.dmSize = mem::size_of::<winapi::DEVMODEW>() as winapi::WORD;
screen_settings.dmPelsWidth = (rect.right - rect.left) as winapi::DWORD;
screen_settings.dmPelsHeight = (rect.bottom - rect.top) as winapi::DWORD;
screen_settings.dmBitsPerPel = 32; // TODO: ?
screen_settings.dmFields = winapi::DM_BITSPERPEL | winapi::DM_PELSWIDTH | winapi::DM_PELSHEIGHT;
let result = user32::ChangeDisplaySettingsExW(monitor.get_adapter_name().as_ptr(),
&mut screen_settings, ptr::null_mut(),
winapi::CDS_FULLSCREEN, ptr::null_mut());
if result != winapi::DISP_CHANGE_SUCCESSFUL {
return Err(CreationError::OsError(format!("ChangeDisplaySettings failed: {}", result)));
}
Ok(())
}