2017-06-26 21:21:13 +02:00
|
|
|
//! 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;
|
2017-07-04 19:32:59 -07:00
|
|
|
use AxisId;
|
2017-06-26 21:21:13 +02:00
|
|
|
|
|
|
|
|
/// 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 => {
|
2017-07-04 19:32:59 -07:00
|
|
|
use events::DeviceEvent::Motion;
|
2017-06-26 21:21:13 +02:00
|
|
|
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 {
|
2017-07-04 19:32:59 -07:00
|
|
|
if data.mouse.usFlags & winapi::MOUSE_MOVE_RELATIVE == winapi::MOUSE_MOVE_RELATIVE {
|
|
|
|
|
let x = data.mouse.lLastX as f64;
|
|
|
|
|
let y = data.mouse.lLastY as f64;
|
|
|
|
|
|
|
|
|
|
if x != 0.0 {
|
|
|
|
|
send_event(Event::DeviceEvent {
|
|
|
|
|
device_id: DEVICE_ID,
|
|
|
|
|
event: Motion { axis: AxisId(0), value: x }
|
|
|
|
|
});
|
|
|
|
|
}
|
2017-06-26 21:21:13 +02:00
|
|
|
|
2017-07-04 19:32:59 -07:00
|
|
|
if y != 0.0 {
|
|
|
|
|
send_event(Event::DeviceEvent {
|
|
|
|
|
device_id: DEVICE_ID,
|
|
|
|
|
event: Motion { axis: AxisId(1), value: y }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-06-26 21:21:13 +02:00
|
|
|
|
2017-07-04 19:32:59 -07:00
|
|
|
0
|
2017-06-26 21:21:13 +02:00
|
|
|
} 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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|