Event Loop 2.0 API and Windows implementation (#638)

* Rename EventsLoop and associated types to EventLoop

* Rename WindowEvent::Refresh to WindowEvent::Redraw

* Remove second thread from win32 backend

* Update run_forever to hijack thread

* Replace windows Mutex with parking_lot Mutex

* Implement new ControlFlow and associated events

* Add StartCause::Init support, timer example

* Add ability to send custom user events

* Fully invert windows control flow so win32 calls into winit's callback

* Add request_redraw

* Rename platform to platform_impl

* Rename os to platform, add Ext trait postfixes

* Add platform::desktop module with EventLoopExt::run_return

* Re-organize into module structure

* Improve documentation

* Small changes to examples

* Improve docs for run and run_return

* Change instances of "events_loop" to "event_loop"

* Rename MonitorId to MonitorHandle

* Add CHANGELOG entry

* Improve WaitUntil timer precision

* When SendEvent is called during event closure, buffer events

* Fix resize lag when waiting in some situations

* Update send test and errors that broke some examples/APIs

* Improve clarity/fix typos in docs

* Fix unreachable panic after setting ControlFlow to Poll during some RedrawRequested events.

* Fix crash when running in release mode

* Remove crossbeam dependency and make drop events work again

* Remove serde implementations from ControlFlow

* Fix 1.24.1 build

* Fix freeze when setting decorations

* Replace &EventLoop in callback with &EventLoopWindowTarget

* Document and implement Debug for EventLoopWindowTarget

* Fix some deadlocks that could occur when changing window state

* Fix thread executor not executing closure when called from non-loop thread

* Fix buffered events not getting dispatched

* Fix crash with runner refcell not getting dropped

* Address review feedback

* Fix CHANGELOG typo

* Catch panics in user callback
This commit is contained in:
Osspial 2019-02-05 10:30:33 -05:00 committed by GitHub
parent 7be1d16263
commit 9602716ed2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
92 changed files with 3467 additions and 1260 deletions

View file

@ -0,0 +1,189 @@
#![allow(non_snake_case, unused_unsafe)]
use std::mem;
use std::os::raw::c_void;
use std::sync::{Once, ONCE_INIT};
use winapi::shared::minwindef::{BOOL, UINT, FALSE};
use winapi::shared::windef::{
DPI_AWARENESS_CONTEXT,
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
HMONITOR,
HWND,
};
use winapi::shared::winerror::S_OK;
use winapi::um::libloaderapi::{GetProcAddress, LoadLibraryA};
use winapi::um::shellscalingapi::{
MDT_EFFECTIVE_DPI,
MONITOR_DPI_TYPE,
PROCESS_DPI_AWARENESS,
PROCESS_PER_MONITOR_DPI_AWARE,
};
use winapi::um::wingdi::{GetDeviceCaps, LOGPIXELSX};
use winapi::um::winnt::{HRESULT, LPCSTR};
use winapi::um::winuser::{self, MONITOR_DEFAULTTONEAREST};
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2: DPI_AWARENESS_CONTEXT = -4isize as _;
type SetProcessDPIAware = unsafe extern "system" fn () -> BOOL;
type SetProcessDpiAwareness = unsafe extern "system" fn (
value: PROCESS_DPI_AWARENESS,
) -> HRESULT;
type SetProcessDpiAwarenessContext = unsafe extern "system" fn (
value: DPI_AWARENESS_CONTEXT,
) -> BOOL;
type GetDpiForWindow = unsafe extern "system" fn (hwnd: HWND) -> UINT;
type GetDpiForMonitor = unsafe extern "system" fn (
hmonitor: HMONITOR,
dpi_type: MONITOR_DPI_TYPE,
dpi_x: *mut UINT,
dpi_y: *mut UINT,
) -> HRESULT;
type EnableNonClientDpiScaling = unsafe extern "system" fn (hwnd: HWND) -> BOOL;
// Helper function to dynamically load function pointer.
// `library` and `function` must be zero-terminated.
fn get_function_impl(library: &str, function: &str) -> Option<*const c_void> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
let module = unsafe { LoadLibraryA(library.as_ptr() as LPCSTR) };
if module.is_null() {
return None;
}
let function_ptr = unsafe { GetProcAddress(module, function.as_ptr() as LPCSTR) };
if function_ptr.is_null() {
return None;
}
Some(function_ptr as _)
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
.map(|f| unsafe { mem::transmute::<*const _, $func>(f) })
}
}
lazy_static! {
static ref GET_DPI_FOR_WINDOW: Option<GetDpiForWindow> = get_function!(
"user32.dll",
GetDpiForWindow
);
static ref GET_DPI_FOR_MONITOR: Option<GetDpiForMonitor> = get_function!(
"shcore.dll",
GetDpiForMonitor
);
static ref ENABLE_NON_CLIENT_DPI_SCALING: Option<EnableNonClientDpiScaling> = get_function!(
"user32.dll",
EnableNonClientDpiScaling
);
}
pub fn become_dpi_aware(enable: bool) {
if !enable { return; }
static ENABLE_DPI_AWARENESS: Once = ONCE_INIT;
ENABLE_DPI_AWARENESS.call_once(|| { unsafe {
if let Some(SetProcessDpiAwarenessContext) = get_function!(
"user32.dll",
SetProcessDpiAwarenessContext
) {
// We are on Windows 10 Anniversary Update (1607) or later.
if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
== FALSE {
// V2 only works with Windows 10 Creators Update (1703). Try using the older
// V1 if we can't set V2.
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
}
} else if let Some(SetProcessDpiAwareness) = get_function!(
"shcore.dll",
SetProcessDpiAwareness
) {
// We are on Windows 8.1 or later.
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
} else if let Some(SetProcessDPIAware) = get_function!(
"user32.dll",
SetProcessDPIAware
) {
// We are on Vista or later.
SetProcessDPIAware();
}
} });
}
pub fn enable_non_client_dpi_scaling(hwnd: HWND) {
unsafe {
if let Some(EnableNonClientDpiScaling) = *ENABLE_NON_CLIENT_DPI_SCALING {
EnableNonClientDpiScaling(hwnd);
}
}
}
pub fn get_monitor_dpi(hmonitor: HMONITOR) -> Option<u32> {
unsafe {
if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let mut dpi_x = 0;
let mut dpi_y = 0;
if GetDpiForMonitor(hmonitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
// MSDN says that "the values of *dpiX and *dpiY are identical. You only need to
// record one of the values to determine the DPI and respond appropriately".
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn280510(v=vs.85).aspx
return Some(dpi_x as u32)
}
}
}
None
}
pub const BASE_DPI: u32 = 96;
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
dpi as f64 / BASE_DPI as f64
}
pub unsafe fn get_hwnd_dpi(hwnd: HWND) -> u32 {
let hdc = winuser::GetDC(hwnd);
if hdc.is_null() {
panic!("[winit] `GetDC` returned null!");
}
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
// We are on Windows 10 Anniversary Update (1607) or later.
match GetDpiForWindow(hwnd) {
0 => BASE_DPI, // 0 is returned if hwnd is invalid
dpi => dpi as u32,
}
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
// We are on Windows 8.1 or later.
let monitor = winuser::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if monitor.is_null() {
return BASE_DPI;
}
let mut dpi_x = 0;
let mut dpi_y = 0;
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
dpi_x as u32
} else {
BASE_DPI
}
} else {
// We are on Vista or later.
if winuser::IsProcessDPIAware() != FALSE {
// If the process is DPI aware, then scaling must be handled by the application using
// this DPI value.
GetDeviceCaps(hdc, LOGPIXELSX) as u32
} else {
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
// application and the WM.
BASE_DPI
}
}
}
pub fn get_hwnd_scale_factor(hwnd: HWND) -> f64 {
dpi_to_scale_factor(unsafe { get_hwnd_dpi(hwnd) })
}

View file

@ -0,0 +1,234 @@
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{mem, ptr};
use winapi::ctypes::c_void;
use winapi::shared::guiddef::REFIID;
use winapi::shared::minwindef::{DWORD, MAX_PATH, UINT, ULONG};
use winapi::shared::windef::{HWND, POINTL};
use winapi::shared::winerror::S_OK;
use winapi::um::objidl::IDataObject;
use winapi::um::oleidl::{DROPEFFECT_COPY, DROPEFFECT_NONE, IDropTarget, IDropTargetVtbl};
use winapi::um::winnt::HRESULT;
use winapi::um::{shellapi, unknwnbase};
use platform_impl::platform::WindowId;
use event::Event;
use window::WindowId as SuperWindowId;
#[repr(C)]
pub struct FileDropHandlerData {
pub interface: IDropTarget,
refcount: AtomicUsize,
window: HWND,
send_event: Box<Fn(Event<()>)>,
cursor_effect: DWORD,
hovered_is_valid: bool, // If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted
}
pub struct FileDropHandler {
pub data: *mut FileDropHandlerData,
}
#[allow(non_snake_case)]
impl FileDropHandler {
pub fn new(window: HWND, send_event: Box<Fn(Event<()>)>) -> FileDropHandler {
let data = Box::new(FileDropHandlerData {
interface: IDropTarget {
lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl,
},
refcount: AtomicUsize::new(1),
window,
send_event,
cursor_effect: DROPEFFECT_NONE,
hovered_is_valid: false,
});
FileDropHandler {
data: Box::into_raw(data),
}
}
// Implement IUnknown
pub unsafe extern "system" fn QueryInterface(
_this: *mut unknwnbase::IUnknown,
_riid: REFIID,
_ppvObject: *mut *mut c_void,
) -> HRESULT {
// This function doesn't appear to be required for an `IDropTarget`.
// An implementation would be nice however.
unimplemented!();
}
pub unsafe extern "system" fn AddRef(this: *mut unknwnbase::IUnknown) -> ULONG {
let drop_handler_data = Self::from_interface(this);
let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1;
count as ULONG
}
pub unsafe extern "system" fn Release(this: *mut unknwnbase::IUnknown) -> ULONG {
let drop_handler = Self::from_interface(this);
let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1;
if count == 0 {
// Destroy the underlying data
Box::from_raw(drop_handler as *mut FileDropHandlerData);
}
count as ULONG
}
pub unsafe extern "system" fn DragEnter(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: DWORD,
_pt: *const POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT {
use event::WindowEvent::HoveredFile;
let drop_handler = Self::from_interface(this);
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
drop_handler.send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: HoveredFile(filename),
});
});
drop_handler.hovered_is_valid = hdrop.is_some();
drop_handler.cursor_effect = if drop_handler.hovered_is_valid {
DROPEFFECT_COPY
} else {
DROPEFFECT_NONE
};
*pdwEffect = drop_handler.cursor_effect;
S_OK
}
pub unsafe extern "system" fn DragOver(
this: *mut IDropTarget,
_grfKeyState: DWORD,
_pt: *const POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT {
let drop_handler = Self::from_interface(this);
*pdwEffect = drop_handler.cursor_effect;
S_OK
}
pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT {
use event::WindowEvent::HoveredFileCancelled;
let drop_handler = Self::from_interface(this);
if drop_handler.hovered_is_valid {
drop_handler.send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: HoveredFileCancelled,
});
}
S_OK
}
pub unsafe extern "system" fn Drop(
this: *mut IDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: DWORD,
_pt: *const POINTL,
_pdwEffect: *mut DWORD,
) -> HRESULT {
use event::WindowEvent::DroppedFile;
let drop_handler = Self::from_interface(this);
let hdrop = Self::iterate_filenames(pDataObj, |filename| {
drop_handler.send_event(Event::WindowEvent {
window_id: SuperWindowId(WindowId(drop_handler.window)),
event: DroppedFile(filename),
});
});
if let Some(hdrop) = hdrop {
shellapi::DragFinish(hdrop);
}
S_OK
}
unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData {
&mut *(this as *mut _)
}
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, callback: F) -> Option<shellapi::HDROP>
where
F: Fn(PathBuf),
{
use winapi::ctypes::wchar_t;
use winapi::shared::winerror::{SUCCEEDED, DV_E_FORMATETC};
use winapi::shared::wtypes::{CLIPFORMAT, DVASPECT_CONTENT};
use winapi::um::objidl::{FORMATETC, TYMED_HGLOBAL};
use winapi::um::shellapi::DragQueryFileW;
use winapi::um::winuser::CF_HDROP;
let mut drop_format = FORMATETC {
cfFormat: CF_HDROP as CLIPFORMAT,
ptd: ptr::null(),
dwAspect: DVASPECT_CONTENT,
lindex: -1,
tymed: TYMED_HGLOBAL,
};
let mut medium = mem::uninitialized();
let get_data_result = (*data_obj).GetData(&mut drop_format, &mut medium);
if SUCCEEDED(get_data_result) {
let hglobal = (*medium.u).hGlobal();
let hdrop = (*hglobal) as shellapi::HDROP;
// The second parameter (0xFFFFFFFF) instructs the function to return the item count
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
let mut pathbuf: [wchar_t; MAX_PATH] = mem::uninitialized();
for i in 0..item_count {
let character_count =
DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), MAX_PATH as UINT) as usize;
if character_count > 0 {
callback(OsString::from_wide(&pathbuf[0..character_count]).into());
}
}
return Some(hdrop);
} else if get_data_result == DV_E_FORMATETC {
// If the dropped item is not a file this error will occur.
// In this case it is OK to return without taking further action.
debug!("Error occured while processing dropped/hovered item: item is not a file.");
return None;
} else {
debug!("Unexpected error occured while processing dropped/hovered item.");
return None;
}
}
}
impl FileDropHandlerData {
fn send_event(&self, event: Event<()>) {
(self.send_event)(event);
}
}
impl Drop for FileDropHandler {
fn drop(&mut self) {
unsafe {
FileDropHandler::Release(self.data as *mut unknwnbase::IUnknown);
}
}
}
static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
parent: unknwnbase::IUnknownVtbl {
QueryInterface: FileDropHandler::QueryInterface,
AddRef: FileDropHandler::AddRef,
Release: FileDropHandler::Release,
},
DragEnter: FileDropHandler::DragEnter,
DragOver: FileDropHandler::DragOver,
DragLeave: FileDropHandler::DragLeave,
Drop: FileDropHandler::Drop,
};

View file

@ -0,0 +1,264 @@
use std::char;
use std::os::raw::c_int;
use event::{ScanCode, ModifiersState, VirtualKeyCode};
use winapi::shared::minwindef::{WPARAM, LPARAM, UINT};
use winapi::um::winuser;
pub fn get_key_mods() -> ModifiersState {
let mut mods = ModifiersState::default();
unsafe {
if winuser::GetKeyState(winuser::VK_SHIFT) & (1 << 15) == (1 << 15) {
mods.shift = true;
}
if winuser::GetKeyState(winuser::VK_CONTROL) & (1 << 15) == (1 << 15) {
mods.ctrl = true;
}
if winuser::GetKeyState(winuser::VK_MENU) & (1 << 15) == (1 << 15) {
mods.alt = true;
}
if (winuser::GetKeyState(winuser::VK_LWIN) | winuser::GetKeyState(winuser::VK_RWIN)) & (1 << 15) == (1 << 15) {
mods.logo = true;
}
}
mods
}
pub fn vkey_to_winit_vkey(vkey: c_int) -> Option<VirtualKeyCode> {
// VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
match vkey {
//winuser::VK_LBUTTON => Some(VirtualKeyCode::Lbutton),
//winuser::VK_RBUTTON => Some(VirtualKeyCode::Rbutton),
//winuser::VK_CANCEL => Some(VirtualKeyCode::Cancel),
//winuser::VK_MBUTTON => Some(VirtualKeyCode::Mbutton),
//winuser::VK_XBUTTON1 => Some(VirtualKeyCode::Xbutton1),
//winuser::VK_XBUTTON2 => Some(VirtualKeyCode::Xbutton2),
winuser::VK_BACK => Some(VirtualKeyCode::Back),
winuser::VK_TAB => Some(VirtualKeyCode::Tab),
//winuser::VK_CLEAR => Some(VirtualKeyCode::Clear),
winuser::VK_RETURN => Some(VirtualKeyCode::Return),
winuser::VK_LSHIFT => Some(VirtualKeyCode::LShift),
winuser::VK_RSHIFT => Some(VirtualKeyCode::RShift),
winuser::VK_LCONTROL => Some(VirtualKeyCode::LControl),
winuser::VK_RCONTROL => Some(VirtualKeyCode::RControl),
winuser::VK_LMENU => Some(VirtualKeyCode::LAlt),
winuser::VK_RMENU => Some(VirtualKeyCode::RAlt),
winuser::VK_PAUSE => Some(VirtualKeyCode::Pause),
winuser::VK_CAPITAL => Some(VirtualKeyCode::Capital),
winuser::VK_KANA => Some(VirtualKeyCode::Kana),
//winuser::VK_HANGUEL => Some(VirtualKeyCode::Hanguel),
//winuser::VK_HANGUL => Some(VirtualKeyCode::Hangul),
//winuser::VK_JUNJA => Some(VirtualKeyCode::Junja),
//winuser::VK_FINAL => Some(VirtualKeyCode::Final),
//winuser::VK_HANJA => Some(VirtualKeyCode::Hanja),
winuser::VK_KANJI => Some(VirtualKeyCode::Kanji),
winuser::VK_ESCAPE => Some(VirtualKeyCode::Escape),
winuser::VK_CONVERT => Some(VirtualKeyCode::Convert),
winuser::VK_NONCONVERT => Some(VirtualKeyCode::NoConvert),
//winuser::VK_ACCEPT => Some(VirtualKeyCode::Accept),
//winuser::VK_MODECHANGE => Some(VirtualKeyCode::Modechange),
winuser::VK_SPACE => Some(VirtualKeyCode::Space),
winuser::VK_PRIOR => Some(VirtualKeyCode::PageUp),
winuser::VK_NEXT => Some(VirtualKeyCode::PageDown),
winuser::VK_END => Some(VirtualKeyCode::End),
winuser::VK_HOME => Some(VirtualKeyCode::Home),
winuser::VK_LEFT => Some(VirtualKeyCode::Left),
winuser::VK_UP => Some(VirtualKeyCode::Up),
winuser::VK_RIGHT => Some(VirtualKeyCode::Right),
winuser::VK_DOWN => Some(VirtualKeyCode::Down),
//winuser::VK_SELECT => Some(VirtualKeyCode::Select),
//winuser::VK_PRINT => Some(VirtualKeyCode::Print),
//winuser::VK_EXECUTE => Some(VirtualKeyCode::Execute),
winuser::VK_SNAPSHOT => Some(VirtualKeyCode::Snapshot),
winuser::VK_INSERT => Some(VirtualKeyCode::Insert),
winuser::VK_DELETE => Some(VirtualKeyCode::Delete),
//winuser::VK_HELP => Some(VirtualKeyCode::Help),
0x30 => Some(VirtualKeyCode::Key0),
0x31 => Some(VirtualKeyCode::Key1),
0x32 => Some(VirtualKeyCode::Key2),
0x33 => Some(VirtualKeyCode::Key3),
0x34 => Some(VirtualKeyCode::Key4),
0x35 => Some(VirtualKeyCode::Key5),
0x36 => Some(VirtualKeyCode::Key6),
0x37 => Some(VirtualKeyCode::Key7),
0x38 => Some(VirtualKeyCode::Key8),
0x39 => Some(VirtualKeyCode::Key9),
0x41 => Some(VirtualKeyCode::A),
0x42 => Some(VirtualKeyCode::B),
0x43 => Some(VirtualKeyCode::C),
0x44 => Some(VirtualKeyCode::D),
0x45 => Some(VirtualKeyCode::E),
0x46 => Some(VirtualKeyCode::F),
0x47 => Some(VirtualKeyCode::G),
0x48 => Some(VirtualKeyCode::H),
0x49 => Some(VirtualKeyCode::I),
0x4A => Some(VirtualKeyCode::J),
0x4B => Some(VirtualKeyCode::K),
0x4C => Some(VirtualKeyCode::L),
0x4D => Some(VirtualKeyCode::M),
0x4E => Some(VirtualKeyCode::N),
0x4F => Some(VirtualKeyCode::O),
0x50 => Some(VirtualKeyCode::P),
0x51 => Some(VirtualKeyCode::Q),
0x52 => Some(VirtualKeyCode::R),
0x53 => Some(VirtualKeyCode::S),
0x54 => Some(VirtualKeyCode::T),
0x55 => Some(VirtualKeyCode::U),
0x56 => Some(VirtualKeyCode::V),
0x57 => Some(VirtualKeyCode::W),
0x58 => Some(VirtualKeyCode::X),
0x59 => Some(VirtualKeyCode::Y),
0x5A => Some(VirtualKeyCode::Z),
//winuser::VK_LWIN => Some(VirtualKeyCode::Lwin),
//winuser::VK_RWIN => Some(VirtualKeyCode::Rwin),
winuser::VK_APPS => Some(VirtualKeyCode::Apps),
winuser::VK_SLEEP => Some(VirtualKeyCode::Sleep),
winuser::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0),
winuser::VK_NUMPAD1 => Some(VirtualKeyCode::Numpad1),
winuser::VK_NUMPAD2 => Some(VirtualKeyCode::Numpad2),
winuser::VK_NUMPAD3 => Some(VirtualKeyCode::Numpad3),
winuser::VK_NUMPAD4 => Some(VirtualKeyCode::Numpad4),
winuser::VK_NUMPAD5 => Some(VirtualKeyCode::Numpad5),
winuser::VK_NUMPAD6 => Some(VirtualKeyCode::Numpad6),
winuser::VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7),
winuser::VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8),
winuser::VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9),
winuser::VK_MULTIPLY => Some(VirtualKeyCode::Multiply),
winuser::VK_ADD => Some(VirtualKeyCode::Add),
//winuser::VK_SEPARATOR => Some(VirtualKeyCode::Separator),
winuser::VK_SUBTRACT => Some(VirtualKeyCode::Subtract),
winuser::VK_DECIMAL => Some(VirtualKeyCode::Decimal),
winuser::VK_DIVIDE => Some(VirtualKeyCode::Divide),
winuser::VK_F1 => Some(VirtualKeyCode::F1),
winuser::VK_F2 => Some(VirtualKeyCode::F2),
winuser::VK_F3 => Some(VirtualKeyCode::F3),
winuser::VK_F4 => Some(VirtualKeyCode::F4),
winuser::VK_F5 => Some(VirtualKeyCode::F5),
winuser::VK_F6 => Some(VirtualKeyCode::F6),
winuser::VK_F7 => Some(VirtualKeyCode::F7),
winuser::VK_F8 => Some(VirtualKeyCode::F8),
winuser::VK_F9 => Some(VirtualKeyCode::F9),
winuser::VK_F10 => Some(VirtualKeyCode::F10),
winuser::VK_F11 => Some(VirtualKeyCode::F11),
winuser::VK_F12 => Some(VirtualKeyCode::F12),
winuser::VK_F13 => Some(VirtualKeyCode::F13),
winuser::VK_F14 => Some(VirtualKeyCode::F14),
winuser::VK_F15 => Some(VirtualKeyCode::F15),
winuser::VK_F16 => Some(VirtualKeyCode::F16),
winuser::VK_F17 => Some(VirtualKeyCode::F17),
winuser::VK_F18 => Some(VirtualKeyCode::F18),
winuser::VK_F19 => Some(VirtualKeyCode::F19),
winuser::VK_F20 => Some(VirtualKeyCode::F20),
winuser::VK_F21 => Some(VirtualKeyCode::F21),
winuser::VK_F22 => Some(VirtualKeyCode::F22),
winuser::VK_F23 => Some(VirtualKeyCode::F23),
winuser::VK_F24 => Some(VirtualKeyCode::F24),
winuser::VK_NUMLOCK => Some(VirtualKeyCode::Numlock),
winuser::VK_SCROLL => Some(VirtualKeyCode::Scroll),
winuser::VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward),
winuser::VK_BROWSER_FORWARD => Some(VirtualKeyCode::NavigateForward),
winuser::VK_BROWSER_REFRESH => Some(VirtualKeyCode::WebRefresh),
winuser::VK_BROWSER_STOP => Some(VirtualKeyCode::WebStop),
winuser::VK_BROWSER_SEARCH => Some(VirtualKeyCode::WebSearch),
winuser::VK_BROWSER_FAVORITES => Some(VirtualKeyCode::WebFavorites),
winuser::VK_BROWSER_HOME => Some(VirtualKeyCode::WebHome),
winuser::VK_VOLUME_MUTE => Some(VirtualKeyCode::Mute),
winuser::VK_VOLUME_DOWN => Some(VirtualKeyCode::VolumeDown),
winuser::VK_VOLUME_UP => Some(VirtualKeyCode::VolumeUp),
winuser::VK_MEDIA_NEXT_TRACK => Some(VirtualKeyCode::NextTrack),
winuser::VK_MEDIA_PREV_TRACK => Some(VirtualKeyCode::PrevTrack),
winuser::VK_MEDIA_STOP => Some(VirtualKeyCode::MediaStop),
winuser::VK_MEDIA_PLAY_PAUSE => Some(VirtualKeyCode::PlayPause),
winuser::VK_LAUNCH_MAIL => Some(VirtualKeyCode::Mail),
winuser::VK_LAUNCH_MEDIA_SELECT => Some(VirtualKeyCode::MediaSelect),
/*winuser::VK_LAUNCH_APP1 => Some(VirtualKeyCode::Launch_app1),
winuser::VK_LAUNCH_APP2 => Some(VirtualKeyCode::Launch_app2),*/
winuser::VK_OEM_PLUS => Some(VirtualKeyCode::Equals),
winuser::VK_OEM_COMMA => Some(VirtualKeyCode::Comma),
winuser::VK_OEM_MINUS => Some(VirtualKeyCode::Minus),
winuser::VK_OEM_PERIOD => Some(VirtualKeyCode::Period),
winuser::VK_OEM_1 => map_text_keys(vkey),
winuser::VK_OEM_2 => map_text_keys(vkey),
winuser::VK_OEM_3 => map_text_keys(vkey),
winuser::VK_OEM_4 => map_text_keys(vkey),
winuser::VK_OEM_5 => map_text_keys(vkey),
winuser::VK_OEM_6 => map_text_keys(vkey),
winuser::VK_OEM_7 => map_text_keys(vkey),
/*winuser::VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */
winuser::VK_OEM_102 => Some(VirtualKeyCode::OEM102),
/*winuser::VK_PROCESSKEY => Some(VirtualKeyCode::Processkey),
winuser::VK_PACKET => Some(VirtualKeyCode::Packet),
winuser::VK_ATTN => Some(VirtualKeyCode::Attn),
winuser::VK_CRSEL => Some(VirtualKeyCode::Crsel),
winuser::VK_EXSEL => Some(VirtualKeyCode::Exsel),
winuser::VK_EREOF => Some(VirtualKeyCode::Ereof),
winuser::VK_PLAY => Some(VirtualKeyCode::Play),
winuser::VK_ZOOM => Some(VirtualKeyCode::Zoom),
winuser::VK_NONAME => Some(VirtualKeyCode::Noname),
winuser::VK_PA1 => Some(VirtualKeyCode::Pa1),
winuser::VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/
_ => None
}
}
pub fn handle_extended_keys(vkey: c_int, mut scancode: UINT, extended: bool) -> Option<(c_int, UINT)> {
// Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/
let vkey = match vkey {
winuser::VK_SHIFT => unsafe { winuser::MapVirtualKeyA(
scancode,
winuser::MAPVK_VSC_TO_VK_EX,
) as _ },
winuser::VK_CONTROL => if extended {
winuser::VK_RCONTROL
} else {
winuser::VK_LCONTROL
},
winuser::VK_MENU => if extended {
winuser::VK_RMENU
} else {
winuser::VK_LMENU
},
_ => match scancode {
// This is only triggered when using raw input. Without this check, we get two events whenever VK_PAUSE is
// pressed, the first one having scancode 0x1D but vkey VK_PAUSE...
0x1D if vkey == winuser::VK_PAUSE => return None,
// ...and the second having scancode 0x45 but an unmatched vkey!
0x45 => winuser::VK_PAUSE,
// VK_PAUSE and VK_SCROLL have the same scancode when using modifiers, alongside incorrect vkey values.
0x46 => {
if extended {
scancode = 0x45;
winuser::VK_PAUSE
} else {
winuser::VK_SCROLL
}
},
_ => vkey,
},
};
Some((vkey, scancode))
}
pub fn process_key_params(wparam: WPARAM, lparam: LPARAM) -> Option<(ScanCode, Option<VirtualKeyCode>)> {
let scancode = ((lparam >> 16) & 0xff) as UINT;
let extended = (lparam & 0x01000000) != 0;
handle_extended_keys(wparam as _, scancode, extended)
.map(|(vkey, scancode)| (scancode, vkey_to_winit_vkey(vkey)))
}
// This is needed as windows doesn't properly distinguish
// some virtual key codes for different keyboard layouts
fn map_text_keys(win_virtual_key: i32) -> Option<VirtualKeyCode> {
let char_key = unsafe { winuser::MapVirtualKeyA(win_virtual_key as u32, winuser::MAPVK_VK_TO_CHAR) } & 0x7FFF;
match char::from_u32(char_key) {
Some(';') => Some(VirtualKeyCode::Semicolon),
Some('/') => Some(VirtualKeyCode::Slash),
Some('`') => Some(VirtualKeyCode::Grave),
Some('[') => Some(VirtualKeyCode::LBracket),
Some(']') => Some(VirtualKeyCode::RBracket),
Some('\'') => Some(VirtualKeyCode::Apostrophe),
Some('\\') => Some(VirtualKeyCode::Backslash),
_ => None
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,114 @@
use std::{self, mem, ptr};
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use winapi::ctypes::{c_int, wchar_t};
use winapi::shared::minwindef::{BYTE, LPARAM, WPARAM};
use winapi::shared::windef::{HICON, HWND};
use winapi::um::winuser;
use icon::{Pixel, PIXEL_SIZE, Icon};
use platform_impl::platform::util;
impl Pixel {
fn to_bgra(&mut self) {
mem::swap(&mut self.r, &mut self.b);
}
}
#[derive(Debug)]
pub enum IconType {
Small = winuser::ICON_SMALL as isize,
Big = winuser::ICON_BIG as isize,
}
#[derive(Clone, Debug)]
pub struct WinIcon {
pub handle: HICON,
}
unsafe impl Send for WinIcon {}
impl WinIcon {
#[allow(dead_code)]
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, util::WinError> {
let wide_path: Vec<u16> = path.as_ref().as_os_str().encode_wide().collect();
let handle = unsafe {
winuser::LoadImageW(
ptr::null_mut(),
wide_path.as_ptr() as *const wchar_t,
winuser::IMAGE_ICON,
0, // 0 indicates that we want to use the actual width
0, // and height
winuser::LR_LOADFROMFILE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon { handle })
} else {
Err(util::WinError::from_last_error())
}
}
pub fn from_icon(icon: Icon) -> Result<Self, util::WinError> {
Self::from_rgba(icon.rgba, icon.width, icon.height)
}
pub fn from_rgba(mut rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, util::WinError> {
assert_eq!(rgba.len() % PIXEL_SIZE, 0);
let pixel_count = rgba.len() / PIXEL_SIZE;
assert_eq!(pixel_count, (width * height) as usize);
let mut and_mask = Vec::with_capacity(pixel_count);
let pixels = rgba.as_mut_ptr() as *mut Pixel; // how not to write idiomatic Rust
for pixel_index in 0..pixel_count {
let pixel = unsafe { &mut *pixels.offset(pixel_index as isize) };
and_mask.push(pixel.a.wrapping_sub(std::u8::MAX)); // invert alpha channel
pixel.to_bgra();
}
assert_eq!(and_mask.len(), pixel_count);
let handle = unsafe {
winuser::CreateIcon(
ptr::null_mut(),
width as c_int,
height as c_int,
1,
(PIXEL_SIZE * 8) as BYTE,
and_mask.as_ptr() as *const BYTE,
rgba.as_ptr() as *const BYTE,
) as HICON
};
if !handle.is_null() {
Ok(WinIcon { handle })
} else {
Err(util::WinError::from_last_error())
}
}
pub fn set_for_window(&self, hwnd: HWND, icon_type: IconType) {
unsafe {
winuser::SendMessageW(
hwnd,
winuser::WM_SETICON,
icon_type as WPARAM,
self.handle as LPARAM,
);
}
}
}
impl Drop for WinIcon {
fn drop(&mut self) {
unsafe { winuser::DestroyIcon(self.handle) };
}
}
pub fn unset_for_window(hwnd: HWND, icon_type: IconType) {
unsafe {
winuser::SendMessageW(
hwnd,
winuser::WM_SETICON,
icon_type as WPARAM,
0 as LPARAM,
);
}
}

View file

@ -0,0 +1,77 @@
#![cfg(target_os = "windows")]
use winapi;
use winapi::shared::windef::HWND;
pub use self::event_loop::{EventLoop, EventLoopWindowTarget, EventLoopProxy};
pub use self::monitor::MonitorHandle;
pub use self::window::Window;
use window::Icon;
use event::DeviceId as RootDeviceId;
#[derive(Clone, Default)]
pub struct PlatformSpecificWindowBuilderAttributes {
pub parent: Option<HWND>,
pub taskbar_icon: Option<Icon>,
pub no_redirection_bitmap: bool,
}
unsafe impl Send for PlatformSpecificWindowBuilderAttributes {}
unsafe impl Sync for PlatformSpecificWindowBuilderAttributes {}
// Cursor name in UTF-16. Used to set cursor in `WM_SETCURSOR`.
#[derive(Debug, Clone, Copy)]
pub struct Cursor(pub *const winapi::ctypes::wchar_t);
unsafe impl Send for Cursor {}
unsafe impl Sync for Cursor {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(u32);
impl DeviceId {
pub unsafe fn dummy() -> Self {
DeviceId(0)
}
}
impl DeviceId {
pub fn get_persistent_identifier(&self) -> Option<String> {
if self.0 != 0 {
raw_input::get_raw_input_device_name(self.0 as _)
} else {
None
}
}
}
// Constant device ID, to be removed when this backend is updated to report real device IDs.
const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId(0));
fn wrap_device_id(id: u32) -> RootDeviceId {
RootDeviceId(DeviceId(id))
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(HWND);
unsafe impl Send for WindowId {}
unsafe impl Sync for WindowId {}
impl WindowId {
pub unsafe fn dummy() -> Self {
use std::ptr::null_mut;
WindowId(null_mut())
}
}
mod dpi;
mod drop_handler;
mod event;
mod event_loop;
mod icon;
mod monitor;
mod raw_input;
mod util;
mod window;
mod window_state;

View file

@ -0,0 +1,173 @@
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE};
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
use winapi::um::winnt::LONG;
use winapi::um::winuser;
use std::{mem, ptr};
use std::collections::VecDeque;
use super::{EventLoop, util};
use dpi::{PhysicalPosition, PhysicalSize};
use platform_impl::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
use platform_impl::platform::window::Window;
/// Win32 implementation of the main `MonitorHandle` object.
#[derive(Debug, Clone)]
pub struct MonitorHandle {
/// Monitor handle.
hmonitor: HMonitor,
/// The system name of the monitor.
monitor_name: String,
/// True if this is the primary monitor.
primary: bool,
/// The position of the monitor in pixels on the desktop.
///
/// A window that is positioned at these coordinates will overlap the monitor.
position: (i32, i32),
/// The current resolution in pixels on the monitor.
dimensions: (u32, u32),
/// DPI scale factor.
hidpi_factor: f64,
}
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
// For more info see:
// https://github.com/retep998/winapi-rs/issues/360
// https://github.com/retep998/winapi-rs/issues/396
#[derive(Debug, Clone)]
struct HMonitor(HMONITOR);
unsafe impl Send for HMonitor {}
unsafe extern "system" fn monitor_enum_proc(
hmonitor: HMONITOR,
_hdc: HDC,
_place: LPRECT,
data: LPARAM,
) -> BOOL {
let monitors = data as *mut VecDeque<MonitorHandle>;
(*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor));
TRUE // continue enumeration
}
pub fn get_available_monitors() -> VecDeque<MonitorHandle> {
let mut monitors: VecDeque<MonitorHandle> = VecDeque::new();
unsafe {
winuser::EnumDisplayMonitors(
ptr::null_mut(),
ptr::null_mut(),
Some(monitor_enum_proc),
&mut monitors as *mut _ as LPARAM,
);
}
monitors
}
pub fn get_primary_monitor() -> MonitorHandle {
const ORIGIN: POINT = POINT { x: 0, y: 0 };
let hmonitor = unsafe {
winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY)
};
MonitorHandle::from_hmonitor(hmonitor)
}
pub fn get_current_monitor(hwnd: HWND) -> MonitorHandle {
let hmonitor = unsafe {
winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST)
};
MonitorHandle::from_hmonitor(hmonitor)
}
impl<T> EventLoop<T> {
// TODO: Investigate opportunities for caching
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
get_available_monitors()
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
get_primary_monitor()
}
}
impl Window {
pub fn get_available_monitors(&self) -> VecDeque<MonitorHandle> {
get_available_monitors()
}
pub fn get_primary_monitor(&self) -> MonitorHandle {
get_primary_monitor()
}
}
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINFOEXW, util::WinError> {
let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() };
monitor_info.cbSize = mem::size_of::<winuser::MONITORINFOEXW>() as DWORD;
let status = unsafe {
winuser::GetMonitorInfoW(
hmonitor,
&mut monitor_info as *mut winuser::MONITORINFOEXW as *mut winuser::MONITORINFO,
)
};
if status == 0 {
Err(util::WinError::from_last_error())
} else {
Ok(monitor_info)
}
}
impl MonitorHandle {
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
let place = monitor_info.rcMonitor;
let dimensions = (
(place.right - place.left) as u32,
(place.bottom - place.top) as u32,
);
MonitorHandle {
hmonitor: HMonitor(hmonitor),
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
position: (place.left as i32, place.top as i32),
dimensions,
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
}
}
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
let left = self.position.0 as LONG;
let right = left + self.dimensions.0 as LONG;
let top = self.position.1 as LONG;
let bottom = top + self.dimensions.1 as LONG;
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
}
#[inline]
pub fn get_name(&self) -> Option<String> {
Some(self.monitor_name.clone())
}
#[inline]
pub fn get_native_identifier(&self) -> String {
self.monitor_name.clone()
}
#[inline]
pub fn get_hmonitor(&self) -> HMONITOR {
self.hmonitor.0
}
#[inline]
pub fn get_dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}
#[inline]
pub fn get_position(&self) -> PhysicalPosition {
self.position.into()
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
}

View file

@ -0,0 +1,235 @@
use std::mem::{self, size_of};
use std::ptr;
use winapi::ctypes::wchar_t;
use winapi::shared::minwindef::{UINT, USHORT, TRUE};
use winapi::shared::hidusage::{
HID_USAGE_PAGE_GENERIC,
HID_USAGE_GENERIC_MOUSE,
HID_USAGE_GENERIC_KEYBOARD,
};
use winapi::shared::windef::HWND;
use winapi::um::winnt::HANDLE;
use winapi::um::winuser::{
self,
RAWINPUTDEVICELIST,
RID_DEVICE_INFO,
RID_DEVICE_INFO_MOUSE,
RID_DEVICE_INFO_KEYBOARD,
RID_DEVICE_INFO_HID,
RIM_TYPEMOUSE,
RIM_TYPEKEYBOARD,
RIM_TYPEHID,
RIDI_DEVICEINFO,
RIDI_DEVICENAME,
RAWINPUTDEVICE,
RIDEV_DEVNOTIFY,
RIDEV_INPUTSINK,
HRAWINPUT,
RAWINPUT,
RAWINPUTHEADER,
RID_INPUT,
};
use platform_impl::platform::util;
use event::ElementState;
#[allow(dead_code)]
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
let list_size = size_of::<RAWINPUTDEVICELIST>() as UINT;
let mut num_devices = 0;
let status = unsafe { winuser::GetRawInputDeviceList(
ptr::null_mut(),
&mut num_devices,
list_size,
) };
if status == UINT::max_value() {
return None;
}
let mut buffer = Vec::with_capacity(num_devices as _);
let num_stored = unsafe { winuser::GetRawInputDeviceList(
buffer.as_ptr() as _,
&mut num_devices,
list_size,
) };
if num_stored == UINT::max_value() {
return None;
}
debug_assert_eq!(num_devices, num_stored);
unsafe { buffer.set_len(num_devices as _) };
Some(buffer)
}
#[allow(dead_code)]
pub enum RawDeviceInfo {
Mouse(RID_DEVICE_INFO_MOUSE),
Keyboard(RID_DEVICE_INFO_KEYBOARD),
Hid(RID_DEVICE_INFO_HID),
}
impl From<RID_DEVICE_INFO> for RawDeviceInfo {
fn from(info: RID_DEVICE_INFO) -> Self {
unsafe {
match info.dwType {
RIM_TYPEMOUSE => RawDeviceInfo::Mouse(*info.u.mouse()),
RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(*info.u.keyboard()),
RIM_TYPEHID => RawDeviceInfo::Hid(*info.u.hid()),
_ => unreachable!(),
}
}
}
}
#[allow(dead_code)]
pub fn get_raw_input_device_info(handle: HANDLE) -> Option<RawDeviceInfo> {
let mut info: RID_DEVICE_INFO = unsafe { mem::uninitialized() };
let info_size = size_of::<RID_DEVICE_INFO>() as UINT;
info.cbSize = info_size;
let mut minimum_size = 0;
let status = unsafe { winuser::GetRawInputDeviceInfoW(
handle,
RIDI_DEVICEINFO,
&mut info as *mut _ as _,
&mut minimum_size,
) };
if status == UINT::max_value() || status == 0 {
return None;
}
debug_assert_eq!(info_size, status);
Some(info.into())
}
pub fn get_raw_input_device_name(handle: HANDLE) -> Option<String> {
let mut minimum_size = 0;
let status = unsafe { winuser::GetRawInputDeviceInfoW(
handle,
RIDI_DEVICENAME,
ptr::null_mut(),
&mut minimum_size,
) };
if status != 0 {
return None;
}
let mut name: Vec<wchar_t> = Vec::with_capacity(minimum_size as _);
let status = unsafe { winuser::GetRawInputDeviceInfoW(
handle,
RIDI_DEVICENAME,
name.as_ptr() as _,
&mut minimum_size,
) };
if status == UINT::max_value() || status == 0 {
return None;
}
debug_assert_eq!(minimum_size, status);
unsafe { name.set_len(minimum_size as _) };
Some(util::wchar_to_string(&name))
}
pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
let device_size = size_of::<RAWINPUTDEVICE>() as UINT;
let success = unsafe { winuser::RegisterRawInputDevices(
devices.as_ptr() as _,
devices.len() as _,
device_size,
) };
success == TRUE
}
pub fn register_all_mice_and_keyboards_for_raw_input(window_handle: HWND) -> bool {
// RIDEV_DEVNOTIFY: receive hotplug events
// RIDEV_INPUTSINK: receive events even if we're not in the foreground
let flags = RIDEV_DEVNOTIFY | RIDEV_INPUTSINK;
let devices: [RAWINPUTDEVICE; 2] = [
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_MOUSE,
dwFlags: flags,
hwndTarget: window_handle,
},
RAWINPUTDEVICE {
usUsagePage: HID_USAGE_PAGE_GENERIC,
usUsage: HID_USAGE_GENERIC_KEYBOARD,
dwFlags: flags,
hwndTarget: window_handle,
},
];
register_raw_input_devices(&devices)
}
pub fn get_raw_input_data(handle: HRAWINPUT) -> Option<RAWINPUT> {
let mut data: RAWINPUT = unsafe { mem::uninitialized() };
let mut data_size = size_of::<RAWINPUT>() as UINT;
let header_size = size_of::<RAWINPUTHEADER>() as UINT;
let status = unsafe { winuser::GetRawInputData(
handle,
RID_INPUT,
&mut data as *mut _ as _,
&mut data_size,
header_size,
) };
if status == UINT::max_value() || status == 0 {
return None;
}
Some(data)
}
fn button_flags_to_element_state(button_flags: USHORT, down_flag: USHORT, up_flag: USHORT)
-> Option<ElementState>
{
// We assume the same button won't be simultaneously pressed and released.
if util::has_flag(button_flags, down_flag) {
Some(ElementState::Pressed)
} else if util::has_flag(button_flags, up_flag) {
Some(ElementState::Released)
} else {
None
}
}
pub fn get_raw_mouse_button_state(button_flags: USHORT) -> [Option<ElementState>; 3] {
[
button_flags_to_element_state(
button_flags,
winuser::RI_MOUSE_LEFT_BUTTON_DOWN,
winuser::RI_MOUSE_LEFT_BUTTON_UP,
),
button_flags_to_element_state(
button_flags,
winuser::RI_MOUSE_MIDDLE_BUTTON_DOWN,
winuser::RI_MOUSE_MIDDLE_BUTTON_UP,
),
button_flags_to_element_state(
button_flags,
winuser::RI_MOUSE_RIGHT_BUTTON_DOWN,
winuser::RI_MOUSE_RIGHT_BUTTON_UP,
),
]
}

View file

@ -0,0 +1,183 @@
use std::{self, mem, ptr, slice, io};
use std::ops::BitAnd;
use std::sync::atomic::{AtomicBool, Ordering};
use window::MouseCursor;
use winapi::ctypes::wchar_t;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::winbase::{
FormatMessageW,
FORMAT_MESSAGE_ALLOCATE_BUFFER,
FORMAT_MESSAGE_FROM_SYSTEM,
FORMAT_MESSAGE_IGNORE_INSERTS,
lstrlenW,
LocalFree,
};
use winapi::um::winnt::{
LPCWSTR,
MAKELANGID,
LANG_NEUTRAL,
SUBLANG_DEFAULT,
};
use winapi::um::winuser;
pub fn has_flag<T>(bitset: T, flag: T) -> bool
where T:
Copy + PartialEq + BitAnd<T, Output = T>
{
bitset & flag == flag
}
pub fn wchar_to_string(wchar: &[wchar_t]) -> String {
String::from_utf16_lossy(wchar).to_string()
}
pub fn wchar_ptr_to_string(wchar: *const wchar_t) -> String {
let len = unsafe { lstrlenW(wchar) } as usize;
let wchar_slice = unsafe { slice::from_raw_parts(wchar, len) };
wchar_to_string(wchar_slice)
}
pub unsafe fn status_map<T, F: FnMut(&mut T) -> BOOL>(mut fun: F) -> Option<T> {
let mut data: T = mem::uninitialized();
if fun(&mut data) != 0 {
Some(data)
} else {
None
}
}
fn win_to_err<F: FnOnce() -> BOOL>(f: F) -> Result<(), io::Error> {
if f() != 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub fn get_cursor_pos() -> Option<POINT> {
unsafe { status_map(|cursor_pos| winuser::GetCursorPos(cursor_pos)) }
}
pub fn get_window_rect(hwnd: HWND) -> Option<RECT> {
unsafe { status_map(|rect| winuser::GetWindowRect(hwnd, rect)) }
}
pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> {
unsafe {
let mut rect = mem::uninitialized();
let mut top_left = mem::zeroed();
win_to_err(|| winuser::ClientToScreen(hwnd, &mut top_left))?;
win_to_err(|| winuser::GetClientRect(hwnd, &mut rect))?;
rect.left += top_left.x;
rect.top += top_left.y;
rect.right += top_left.x;
rect.bottom += top_left.y;
Ok(rect)
}
}
pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
unsafe {
let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE);
let style_ex = winuser::GetWindowLongW(hwnd, winuser::GWL_EXSTYLE);
adjust_window_rect_with_styles(hwnd, style as _, style_ex as _, rect)
}
}
pub fn adjust_window_rect_with_styles(hwnd: HWND, style: DWORD, style_ex: DWORD, rect: RECT) -> Option<RECT> {
unsafe { status_map(|r| {
*r = rect;
let b_menu = !winuser::GetMenu(hwnd).is_null() as BOOL;
winuser::AdjustWindowRectEx(r, style as _ , b_menu, style_ex as _)
}) }
}
pub fn set_cursor_hidden(hidden: bool) {
static HIDDEN: AtomicBool = AtomicBool::new(false);
let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden;
if changed {
unsafe{ winuser::ShowCursor(!hidden as BOOL) };
}
}
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
unsafe {
let rect_ptr = rect.as_ref().map(|r| r as *const RECT).unwrap_or(ptr::null());
win_to_err(|| winuser::ClipCursor(rect_ptr))
}
}
pub fn is_focused(window: HWND) -> bool {
window == unsafe{ winuser::GetActiveWindow() }
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct WinError(Option<String>);
impl WinError {
pub fn from_last_error() -> Self {
WinError(unsafe { get_last_error() })
}
}
pub unsafe fn get_last_error() -> Option<String> {
let err = GetLastError();
if err != 0 {
let buf_addr: LPCWSTR = {
let mut buf_addr: LPCWSTR = mem::uninitialized();
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
ptr::null(),
err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT) as DWORD,
// This is a pointer to a pointer
&mut buf_addr as *mut LPCWSTR as *mut _,
0,
ptr::null_mut(),
);
buf_addr
};
if !buf_addr.is_null() {
let buf_len = lstrlenW(buf_addr) as usize;
let buf_slice = std::slice::from_raw_parts(buf_addr, buf_len);
let string = wchar_to_string(buf_slice);
LocalFree(buf_addr as *mut _);
return Some(string);
}
}
None
}
impl MouseCursor {
pub(crate) fn to_windows_cursor(self) -> *const wchar_t {
match self {
MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW,
MouseCursor::Hand => winuser::IDC_HAND,
MouseCursor::Crosshair => winuser::IDC_CROSS,
MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM,
MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO,
MouseCursor::Grab | MouseCursor::Grabbing |
MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL,
MouseCursor::EResize | MouseCursor::WResize |
MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE,
MouseCursor::NResize | MouseCursor::SResize |
MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS,
MouseCursor::NeResize | MouseCursor::SwResize |
MouseCursor::NeswResize => winuser::IDC_SIZENESW,
MouseCursor::NwResize | MouseCursor::SeResize |
MouseCursor::NwseResize => winuser::IDC_SIZENWSE,
MouseCursor::Wait => winuser::IDC_WAIT,
MouseCursor::Progress => winuser::IDC_APPSTARTING,
MouseCursor::Help => winuser::IDC_HELP,
_ => winuser::IDC_ARROW, // use arrow for the missing cases.
}
}
}

View file

@ -0,0 +1,898 @@
#![cfg(target_os = "windows")]
use std::{io, mem, ptr};
use std::cell::Cell;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::sync::Arc;
use std::sync::mpsc::channel;
use parking_lot::Mutex;
use winapi::ctypes::c_int;
use winapi::shared::minwindef::{DWORD, LPARAM, UINT, WORD, WPARAM};
use winapi::shared::windef::{HWND, POINT, RECT};
use winapi::um::{combaseapi, dwmapi, libloaderapi, ole2, winuser};
use winapi::um::objbase::COINIT_APARTMENTTHREADED;
use winapi::um::shobjidl_core::{CLSID_TaskbarList, ITaskbarList2};
use winapi::um::wingdi::{CreateRectRgn, DeleteObject};
use winapi::um::oleidl::LPDROPTARGET;
use winapi::um::winnt::{LONG, LPCWSTR};
use window::{CreationError, Icon, MouseCursor, WindowAttributes};
use dpi::{LogicalPosition, LogicalSize, PhysicalSize};
use monitor::MonitorHandle as RootMonitorHandle;
use platform_impl::platform::{
{PlatformSpecificWindowBuilderAttributes, WindowId},
dpi::{dpi_to_scale_factor, get_hwnd_dpi},
drop_handler::FileDropHandler,
event_loop::{self, EventLoopWindowTarget, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID, REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID},
icon::{self, IconType, WinIcon},
monitor,
raw_input::register_all_mice_and_keyboards_for_raw_input,
util,
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
};
/// 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<WindowState>>,
// The events loop proxy.
thread_executor: event_loop::EventLoopThreadExecutor,
}
impl Window {
pub fn new<T: 'static>(
event_loop: &EventLoopWindowTarget<T>,
w_attr: WindowAttributes,
pl_attr: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, CreationError> {
// We dispatch an `init` function because of code style.
// First person to remove the need for cloning here gets a cookie!
//
// done. you owe me -- ossi
unsafe {
init(w_attr, pl_attr, event_loop).map(|win| {
let file_drop_handler = {
use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE, S_OK};
let ole_init_result = ole2::OleInitialize(ptr::null_mut());
// It is ok if the initialize result is `S_FALSE` because it might happen that
// multiple windows are created on the same thread.
if ole_init_result == OLE_E_WRONGCOMPOBJ {
panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`");
} else if ole_init_result == RPC_E_CHANGED_MODE {
panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`");
}
let file_drop_runner = event_loop.runner_shared.clone();
let file_drop_handler = FileDropHandler::new(
win.window.0,
Box::new(move |event| if let Ok(e) = event.map_nonuser_event() {file_drop_runner.send_event(e)})
);
let handler_interface_ptr = &mut (*file_drop_handler.data).interface as LPDROPTARGET;
assert_eq!(ole2::RegisterDragDrop(win.window.0, handler_interface_ptr), S_OK);
file_drop_handler
};
let subclass_input = event_loop::SubclassInput {
window_state: win.window_state.clone(),
event_loop_runner: event_loop.runner_shared.clone(),
file_drop_handler,
};
event_loop::subclass_window(win.window.0, subclass_input);
win
})
}
}
pub fn set_title(&self, text: &str) {
let text = OsStr::new(text)
.encode_wide()
.chain(Some(0).into_iter())
.collect::<Vec<_>>();
unsafe {
winuser::SetWindowTextW(self.window.0, text.as_ptr() as LPCWSTR);
}
}
#[inline]
pub fn show(&self) {
unsafe {
winuser::ShowWindow(self.window.0, winuser::SW_SHOW);
}
}
#[inline]
pub fn hide(&self) {
unsafe {
winuser::ShowWindow(self.window.0, winuser::SW_HIDE);
}
}
#[inline]
pub fn request_redraw(&self) {
unsafe {
if self.thread_executor.trigger_newevents_on_redraw() {
winuser::RedrawWindow(
self.window.0,
ptr::null(),
ptr::null_mut(),
winuser::RDW_INTERNALPAINT
);
} else {
winuser::PostMessageW(self.window.0, *REQUEST_REDRAW_NO_NEWEVENTS_MSG_ID, 0, 0);
}
}
}
pub(crate) fn get_position_physical(&self) -> Option<(i32, i32)> {
util::get_window_rect(self.window.0)
.map(|rect| (rect.left as i32, rect.top as i32))
}
#[inline]
pub fn get_position(&self) -> Option<LogicalPosition> {
self.get_position_physical()
.map(|physical_position| {
let dpi_factor = self.get_hidpi_factor();
LogicalPosition::from_physical(physical_position, dpi_factor)
})
}
pub(crate) fn get_inner_position_physical(&self) -> Option<(i32, i32)> {
let mut position: POINT = unsafe { mem::zeroed() };
if unsafe { winuser::ClientToScreen(self.window.0, &mut position) } == 0 {
return None;
}
Some((position.x, position.y))
}
#[inline]
pub fn get_inner_position(&self) -> Option<LogicalPosition> {
self.get_inner_position_physical()
.map(|physical_position| {
let dpi_factor = self.get_hidpi_factor();
LogicalPosition::from_physical(physical_position, dpi_factor)
})
}
pub(crate) fn set_position_physical(&self, x: i32, y: i32) {
unsafe {
winuser::SetWindowPos(
self.window.0,
ptr::null_mut(),
x as c_int,
y as c_int,
0,
0,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOSIZE,
);
winuser::UpdateWindow(self.window.0);
}
}
#[inline]
pub fn set_position(&self, logical_position: LogicalPosition) {
let dpi_factor = self.get_hidpi_factor();
let (x, y) = logical_position.to_physical(dpi_factor).into();
self.set_position_physical(x, y);
}
pub(crate) fn get_inner_size_physical(&self) -> Option<(u32, u32)> {
let mut rect: RECT = unsafe { mem::uninitialized() };
if unsafe { winuser::GetClientRect(self.window.0, &mut rect) } == 0 {
return None;
}
Some((
(rect.right - rect.left) as u32,
(rect.bottom - rect.top) as u32,
))
}
#[inline]
pub fn get_inner_size(&self) -> Option<LogicalSize> {
self.get_inner_size_physical()
.map(|physical_size| {
let dpi_factor = self.get_hidpi_factor();
LogicalSize::from_physical(physical_size, dpi_factor)
})
}
pub(crate) fn get_outer_size_physical(&self) -> Option<(u32, u32)> {
util::get_window_rect(self.window.0)
.map(|rect| (
(rect.right - rect.left) as u32,
(rect.bottom - rect.top) as u32,
))
}
#[inline]
pub fn get_outer_size(&self) -> Option<LogicalSize> {
self.get_outer_size_physical()
.map(|physical_size| {
let dpi_factor = self.get_hidpi_factor();
LogicalSize::from_physical(physical_size, dpi_factor)
})
}
pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) {
unsafe {
let rect = util::adjust_window_rect(
self.window.0,
RECT {
top: 0,
left: 0,
bottom: y as LONG,
right: x as LONG,
}
).expect("adjust_window_rect failed");
let outer_x = (rect.right - rect.left).abs() as c_int;
let outer_y = (rect.top - rect.bottom).abs() as c_int;
winuser::SetWindowPos(
self.window.0,
ptr::null_mut(),
0,
0,
outer_x,
outer_y,
winuser::SWP_ASYNCWINDOWPOS
| winuser::SWP_NOZORDER
| winuser::SWP_NOREPOSITION
| winuser::SWP_NOMOVE,
);
winuser::UpdateWindow(self.window.0);
}
}
#[inline]
pub fn set_inner_size(&self, logical_size: LogicalSize) {
let dpi_factor = self.get_hidpi_factor();
let (width, height) = logical_size.to_physical(dpi_factor).into();
self.set_inner_size_physical(width, height);
}
pub(crate) fn set_min_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
self.window_state.lock().min_size = dimensions.map(Into::into);
// Make windows re-check the window size bounds.
self.get_inner_size_physical()
.map(|(width, height)| self.set_inner_size_physical(width, height));
}
#[inline]
pub fn set_min_dimensions(&self, logical_size: Option<LogicalSize>) {
let physical_size = logical_size.map(|logical_size| {
let dpi_factor = self.get_hidpi_factor();
logical_size.to_physical(dpi_factor).into()
});
self.set_min_dimensions_physical(physical_size);
}
pub fn set_max_dimensions_physical(&self, dimensions: Option<(u32, u32)>) {
self.window_state.lock().max_size = dimensions.map(Into::into);
// Make windows re-check the window size bounds.
self.get_inner_size_physical()
.map(|(width, height)| self.set_inner_size_physical(width, height));
}
#[inline]
pub fn set_max_dimensions(&self, logical_size: Option<LogicalSize>) {
let physical_size = logical_size.map(|logical_size| {
let dpi_factor = self.get_hidpi_factor();
logical_size.to_physical(dpi_factor).into()
});
self.set_max_dimensions_physical(physical_size);
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(
window_state.lock(),
window.0,
None,
|f| f.set(WindowFlags::RESIZABLE, resizable),
);
});
}
/// Returns the `hwnd` of this window.
#[inline]
pub fn hwnd(&self) -> HWND {
self.window.0
}
#[inline]
pub fn set_cursor(&self, cursor: MouseCursor) {
self.window_state.lock().mouse.cursor = cursor;
self.thread_executor.execute_in_thread(move || unsafe {
let cursor = winuser::LoadCursorW(
ptr::null_mut(),
cursor.to_windows_cursor(),
);
winuser::SetCursor(cursor);
});
}
#[inline]
pub fn grab_cursor(&self, grab: bool) -> Result<(), String> {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let (tx, rx) = channel();
self.thread_executor.execute_in_thread(move || {
let result = window_state.lock().mouse
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, grab))
.map_err(|e| e.to_string());
let _ = tx.send(result);
});
rx.recv().unwrap()
}
#[inline]
pub fn hide_cursor(&self, hide: bool) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let (tx, rx) = channel();
self.thread_executor.execute_in_thread(move || {
let result = window_state.lock().mouse
.set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, hide))
.map_err(|e| e.to_string());
let _ = tx.send(result);
});
rx.recv().unwrap().ok();
}
#[inline]
pub fn get_hidpi_factor(&self) -> f64 {
self.window_state.lock().dpi_factor
}
fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), String> {
let mut point = POINT { x, y };
unsafe {
if winuser::ClientToScreen(self.window.0, &mut point) == 0 {
return Err("`ClientToScreen` failed".to_owned());
}
if winuser::SetCursorPos(point.x, point.y) == 0 {
return Err("`SetCursorPos` failed".to_owned());
}
}
Ok(())
}
#[inline]
pub fn set_cursor_position(&self, logical_position: LogicalPosition) -> Result<(), String> {
let dpi_factor = self.get_hidpi_factor();
let (x, y) = logical_position.to_physical(dpi_factor).into();
self.set_cursor_position_physical(x, y)
}
#[inline]
pub fn id(&self) -> WindowId {
WindowId(self.window.0)
}
#[inline]
pub fn set_maximized(&self, maximized: bool) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(
window_state.lock(),
window.0,
None,
|f| f.set(WindowFlags::MAXIMIZED, maximized),
);
});
}
#[inline]
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
unsafe {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
match &monitor {
&Some(RootMonitorHandle { ref inner }) => {
let (x, y): (i32, i32) = inner.get_position().into();
let (width, height): (u32, u32) = inner.get_dimensions().into();
let mut monitor = monitor.clone();
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
window_state_lock.saved_window = Some(SavedWindow {
client_rect,
dpi_factor: window_state_lock.dpi_factor
});
window_state_lock.fullscreen = monitor.take();
WindowState::refresh_window_state(
window_state_lock,
window.0,
Some(RECT {
left: x,
top: y,
right: x + width as c_int,
bottom: y + height as c_int,
})
);
mark_fullscreen(window.0, true);
});
}
&None => {
self.thread_executor.execute_in_thread(move || {
let mut window_state_lock = window_state.lock();
window_state_lock.fullscreen = None;
if let Some(SavedWindow{client_rect, dpi_factor}) = window_state_lock.saved_window {
window_state_lock.dpi_factor = dpi_factor;
window_state_lock.saved_window = None;
WindowState::refresh_window_state(
window_state_lock,
window.0,
Some(client_rect)
);
}
mark_fullscreen(window.0, false);
});
}
}
}
}
#[inline]
pub fn set_decorations(&self, decorations: bool) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
WindowState::set_window_flags(
window_state.lock(),
window.0,
Some(client_rect),
|f| f.set(WindowFlags::DECORATIONS, decorations),
);
});
}
#[inline]
pub fn set_always_on_top(&self, always_on_top: bool) {
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
self.thread_executor.execute_in_thread(move || {
WindowState::set_window_flags(
window_state.lock(),
window.0,
None,
|f| f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top),
);
});
}
#[inline]
pub fn get_current_monitor(&self) -> RootMonitorHandle {
RootMonitorHandle {
inner: monitor::get_current_monitor(self.window.0),
}
}
#[inline]
pub fn set_window_icon(&self, mut window_icon: Option<Icon>) {
let window_icon = window_icon
.take()
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_SMALL`"));
if let Some(ref window_icon) = window_icon {
window_icon.set_for_window(self.window.0, IconType::Small);
} else {
icon::unset_for_window(self.window.0, IconType::Small);
}
self.window_state.lock().window_icon = window_icon;
}
#[inline]
pub fn set_taskbar_icon(&self, mut taskbar_icon: Option<Icon>) {
let taskbar_icon = taskbar_icon
.take()
.map(|icon| WinIcon::from_icon(icon).expect("Failed to create `ICON_BIG`"));
if let Some(ref taskbar_icon) = taskbar_icon {
taskbar_icon.set_for_window(self.window.0, IconType::Big);
} else {
icon::unset_for_window(self.window.0, IconType::Big);
}
self.window_state.lock().taskbar_icon = taskbar_icon;
}
#[inline]
pub fn set_ime_spot(&self, _logical_spot: LogicalPosition) {
unimplemented!();
}
}
impl Drop for Window {
#[inline]
fn drop(&mut self) {
unsafe {
// The window must be destroyed from the same thread that created it, so we send a
// custom message to be handled by our callback to do the actual work.
winuser::PostMessageW(self.window.0, *DESTROY_MSG_ID, 0, 0);
}
}
}
/// A simple non-owning wrapper around a window.
#[doc(hidden)]
#[derive(Clone)]
pub struct WindowWrapper(HWND);
// Send and Sync are not implemented for HWND and HDC, we have to wrap it and implement them manually.
// For more info see:
// https://github.com/retep998/winapi-rs/issues/360
// https://github.com/retep998/winapi-rs/issues/396
unsafe impl Sync for WindowWrapper {}
unsafe impl Send for WindowWrapper {}
pub unsafe fn adjust_size(
physical_size: PhysicalSize,
style: DWORD,
ex_style: DWORD,
) -> (LONG, LONG) {
let (width, height): (u32, u32) = physical_size.into();
let mut rect = RECT {
left: 0,
right: width as LONG,
top: 0,
bottom: height as LONG,
};
winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style);
(rect.right - rect.left, rect.bottom - rect.top)
}
unsafe fn init<T: 'static>(
mut attributes: WindowAttributes,
mut pl_attribs: PlatformSpecificWindowBuilderAttributes,
event_loop: &EventLoopWindowTarget<T>,
) -> Result<Window, CreationError> {
let title = OsStr::new(&attributes.title)
.encode_wide()
.chain(Some(0).into_iter())
.collect::<Vec<_>>();
let window_icon = {
let icon = attributes.window_icon
.take()
.map(WinIcon::from_icon);
if icon.is_some() {
Some(icon.unwrap().map_err(|err| {
CreationError::OsError(format!("Failed to create `ICON_SMALL`: {:?}", err))
})?)
} else {
None
}
};
let taskbar_icon = {
let icon = pl_attribs.taskbar_icon
.take()
.map(WinIcon::from_icon);
if icon.is_some() {
Some(icon.unwrap().map_err(|err| {
CreationError::OsError(format!("Failed to create `ICON_BIG`: {:?}", err))
})?)
} else {
None
}
};
// registering the window class
let class_name = register_window_class(&window_icon, &taskbar_icon);
let guessed_dpi_factor = {
let monitors = monitor::get_available_monitors();
let dpi_factor = if !monitors.is_empty() {
let mut dpi_factor = Some(monitors[0].get_hidpi_factor());
for monitor in &monitors {
if Some(monitor.get_hidpi_factor()) != dpi_factor {
dpi_factor = None;
}
}
dpi_factor
} else {
return Err(CreationError::OsError(format!("No monitors were detected.")));
};
dpi_factor.unwrap_or_else(|| {
util::get_cursor_pos()
.and_then(|cursor_pos| {
let mut dpi_factor = None;
for monitor in &monitors {
if monitor.contains_point(&cursor_pos) {
dpi_factor = Some(monitor.get_hidpi_factor());
break;
}
}
dpi_factor
})
.unwrap_or(1.0)
})
};
info!("Guessed window DPI factor: {}", guessed_dpi_factor);
let dimensions = attributes.dimensions.unwrap_or_else(|| (1024, 768).into());
let mut window_flags = WindowFlags::empty();
window_flags.set(WindowFlags::DECORATIONS, attributes.decorations);
window_flags.set(WindowFlags::ALWAYS_ON_TOP, attributes.always_on_top);
window_flags.set(WindowFlags::NO_BACK_BUFFER, pl_attribs.no_redirection_bitmap);
window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent);
// WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured.
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable);
window_flags.set(WindowFlags::CHILD, pl_attribs.parent.is_some());
window_flags.set(WindowFlags::ON_TASKBAR, true);
// creating the real window this time, by using the functions in `extra_functions`
let real_window = {
let (style, ex_style) = window_flags.to_window_styles();
let handle = winuser::CreateWindowExW(
ex_style,
class_name.as_ptr(),
title.as_ptr() as LPCWSTR,
style,
winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT,
winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT,
pl_attribs.parent.unwrap_or(ptr::null_mut()),
ptr::null_mut(),
libloaderapi::GetModuleHandleW(ptr::null()),
ptr::null_mut(),
);
if handle.is_null() {
return Err(CreationError::OsError(format!("CreateWindowEx function failed: {}",
format!("{}", io::Error::last_os_error()))));
}
winuser::SetWindowLongW(handle, winuser::GWL_STYLE, 0);
winuser::SetWindowLongW(handle, winuser::GWL_EXSTYLE, 0);
WindowWrapper(handle)
};
// Set up raw input
register_all_mice_and_keyboards_for_raw_input(real_window.0);
// Register for touch events if applicable
{
let digitizer = winuser::GetSystemMetrics( winuser::SM_DIGITIZER ) as u32;
if digitizer & winuser::NID_READY != 0 {
winuser::RegisterTouchWindow( real_window.0, winuser::TWF_WANTPALM );
}
}
let dpi = get_hwnd_dpi(real_window.0);
let dpi_factor = dpi_to_scale_factor(dpi);
if dpi_factor != guessed_dpi_factor {
let (width, height): (u32, u32) = dimensions.into();
let mut packed_dimensions = 0;
// MAKELPARAM isn't provided by winapi yet.
let ptr = &mut packed_dimensions as *mut LPARAM as *mut WORD;
*ptr.offset(0) = width as WORD;
*ptr.offset(1) = height as WORD;
winuser::PostMessageW(
real_window.0,
*INITIAL_DPI_MSG_ID,
dpi as WPARAM,
packed_dimensions,
);
}
// making the window transparent
if attributes.transparent && !pl_attribs.no_redirection_bitmap {
let region = CreateRectRgn(0, 0, -1, -1); // makes the window transparent
let bb = dwmapi::DWM_BLURBEHIND {
dwFlags: dwmapi::DWM_BB_ENABLE | dwmapi::DWM_BB_BLURREGION,
fEnable: 1,
hRgnBlur: region,
fTransitionOnMaximized: 0,
};
dwmapi::DwmEnableBlurBehindWindow(real_window.0, &bb);
DeleteObject(region as _);
if attributes.decorations {
// HACK: When opaque (opacity 255), there is a trail whenever
// the transparent window is moved. By reducing it to 254,
// the window is rendered properly.
let opacity = 254;
// The color key can be any value except for black (0x0).
let color_key = 0x0030c100;
winuser::SetLayeredWindowAttributes(real_window.0, color_key, opacity, winuser::LWA_ALPHA);
}
}
window_flags.set(WindowFlags::VISIBLE, attributes.visible);
window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized);
let window_state = {
let mut window_state = WindowState::new(
&attributes,
window_icon,
taskbar_icon,
dpi_factor,
);
let window_state = Arc::new(Mutex::new(window_state));
WindowState::set_window_flags(
window_state.lock(),
real_window.0,
None,
|f| *f = window_flags,
);
window_state
};
let win = Window {
window: real_window,
window_state,
thread_executor: event_loop.create_thread_executor(),
};
if let Some(_) = attributes.fullscreen {
win.set_fullscreen(attributes.fullscreen);
force_window_active(win.window.0);
}
if let Some(dimensions) = attributes.dimensions {
win.set_inner_size(dimensions);
}
Ok(win)
}
unsafe fn register_window_class(
window_icon: &Option<WinIcon>,
taskbar_icon: &Option<WinIcon>,
) -> Vec<u16> {
let class_name: Vec<_> = OsStr::new("Window Class")
.encode_wide()
.chain(Some(0).into_iter())
.collect();
let h_icon = taskbar_icon
.as_ref()
.map(|icon| icon.handle)
.unwrap_or(ptr::null_mut());
let h_icon_small = window_icon
.as_ref()
.map(|icon| icon.handle)
.unwrap_or(ptr::null_mut());
let class = winuser::WNDCLASSEXW {
cbSize: mem::size_of::<winuser::WNDCLASSEXW>() as UINT,
style: winuser::CS_HREDRAW | winuser::CS_VREDRAW | winuser::CS_OWNDC,
lpfnWndProc: Some(winuser::DefWindowProcW),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: libloaderapi::GetModuleHandleW(ptr::null()),
hIcon: h_icon,
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: h_icon_small,
};
// 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.
winuser::RegisterClassExW(&class);
class_name
}
struct ComInitialized(*mut ());
impl Drop for ComInitialized {
fn drop(&mut self) {
unsafe { combaseapi::CoUninitialize() };
}
}
thread_local!{
static COM_INITIALIZED: ComInitialized = {
unsafe {
combaseapi::CoInitializeEx(ptr::null_mut(), COINIT_APARTMENTTHREADED);
ComInitialized(ptr::null_mut())
}
};
static TASKBAR_LIST: Cell<*mut ITaskbarList2> = Cell::new(ptr::null_mut());
}
pub fn com_initialized() {
COM_INITIALIZED.with(|_| {});
}
// Reference Implementation:
// https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc
//
// As per MSDN marking the window as fullscreen should ensure that the
// taskbar is moved to the bottom of the Z-order when the fullscreen window
// is activated. If the window is not fullscreen, the Shell falls back to
// heuristics to determine how the window should be treated, which means
// that it could still consider the window as fullscreen. :(
unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) {
com_initialized();
TASKBAR_LIST.with(|task_bar_list_ptr| {
let mut task_bar_list = task_bar_list_ptr.get();
if task_bar_list == ptr::null_mut() {
use winapi::shared::winerror::S_OK;
use winapi::Interface;
let hr = combaseapi::CoCreateInstance(
&CLSID_TaskbarList,
ptr::null_mut(),
combaseapi::CLSCTX_ALL,
&ITaskbarList2::uuidof(),
&mut task_bar_list as *mut _ as *mut _,
);
if hr != S_OK || (*task_bar_list).HrInit() != S_OK {
// In some old windows, the taskbar object could not be created, we just ignore it
return;
}
task_bar_list_ptr.set(task_bar_list)
}
task_bar_list = task_bar_list_ptr.get();
(*task_bar_list).MarkFullscreenWindow(handle, if fullscreen { 1 } else { 0 });
})
}
unsafe fn force_window_active(handle: HWND) {
// In some situation, calling SetForegroundWindow could not bring up the window,
// This is a little hack which can "steal" the foreground window permission
// We only call this function in the window creation, so it should be fine.
// See : https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open
let alt_sc = winuser::MapVirtualKeyW(winuser::VK_MENU as _, winuser::MAPVK_VK_TO_VSC);
let mut inputs: [winuser::INPUT; 2] = mem::zeroed();
inputs[0].type_ = winuser::INPUT_KEYBOARD;
inputs[0].u.ki_mut().wVk = winuser::VK_LMENU as _;
inputs[0].u.ki_mut().wScan = alt_sc as _;
inputs[0].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY;
inputs[1].type_ = winuser::INPUT_KEYBOARD;
inputs[1].u.ki_mut().wVk = winuser::VK_LMENU as _;
inputs[1].u.ki_mut().wScan = alt_sc as _;
inputs[1].u.ki_mut().dwFlags = winuser::KEYEVENTF_EXTENDEDKEY | winuser::KEYEVENTF_KEYUP;
// Simulate a key press and release
winuser::SendInput(
inputs.len() as _,
inputs.as_mut_ptr(),
mem::size_of::<winuser::INPUT>() as _,
);
winuser::SetForegroundWindow(handle);
}

View file

@ -0,0 +1,332 @@
use monitor::MonitorHandle;
use window::{MouseCursor, WindowAttributes};
use std::{io, ptr};
use parking_lot::MutexGuard;
use dpi::LogicalSize;
use platform_impl::platform::{util, event_loop};
use platform_impl::platform::icon::WinIcon;
use winapi::shared::windef::{RECT, HWND};
use winapi::shared::minwindef::DWORD;
use winapi::um::winuser;
/// Contains information about states and the window that the callback is going to use.
#[derive(Clone)]
pub struct WindowState {
pub mouse: MouseProperties,
/// Used by `WM_GETMINMAXINFO`.
pub min_size: Option<LogicalSize>,
pub max_size: Option<LogicalSize>,
pub window_icon: Option<WinIcon>,
pub taskbar_icon: Option<WinIcon>,
pub saved_window: Option<SavedWindow>,
pub dpi_factor: f64,
pub fullscreen: Option<MonitorHandle>,
window_flags: WindowFlags,
}
#[derive(Clone)]
pub struct SavedWindow {
pub client_rect: RECT,
pub dpi_factor: f64,
}
#[derive(Clone)]
pub struct MouseProperties {
pub cursor: MouseCursor,
pub buttons_down: u32,
cursor_flags: CursorFlags,
}
bitflags! {
pub struct CursorFlags: u8 {
const GRABBED = 1 << 0;
const HIDDEN = 1 << 1;
const IN_WINDOW = 1 << 2;
}
}
bitflags! {
pub struct WindowFlags: u32 {
const RESIZABLE = 1 << 0;
const DECORATIONS = 1 << 1;
const VISIBLE = 1 << 2;
const ON_TASKBAR = 1 << 3;
const ALWAYS_ON_TOP = 1 << 4;
const NO_BACK_BUFFER = 1 << 5;
const TRANSPARENT = 1 << 6;
const CHILD = 1 << 7;
const MAXIMIZED = 1 << 8;
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
/// included here to make masking easier.
const MARKER_FULLSCREEN = 1 << 9;
/// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`.
/// In most cases, it's okay to let those parameters change the state. However, when we're
/// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to
/// effect our stored state, because the purpose of `apply_diff` is to update the actual
/// window's state to match our stored state. This controls whether to accept those changes.
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10;
const FULLSCREEN_AND_MASK = !(
WindowFlags::DECORATIONS.bits |
WindowFlags::RESIZABLE.bits |
WindowFlags::MAXIMIZED.bits
);
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
}
}
impl WindowState {
pub fn new(
attributes: &WindowAttributes,
window_icon: Option<WinIcon>,
taskbar_icon: Option<WinIcon>,
dpi_factor: f64
) -> WindowState {
WindowState {
mouse: MouseProperties {
cursor: MouseCursor::default(),
buttons_down: 0,
cursor_flags: CursorFlags::empty(),
},
min_size: attributes.min_dimensions,
max_size: attributes.max_dimensions,
window_icon,
taskbar_icon,
saved_window: None,
dpi_factor,
fullscreen: None,
window_flags: WindowFlags::empty()
}
}
pub fn window_flags(&self) -> WindowFlags {
self.window_flags
}
pub fn set_window_flags<F>(mut this: MutexGuard<Self>, window: HWND, set_client_rect: Option<RECT>, f: F)
where F: FnOnce(&mut WindowFlags)
{
let old_flags = this.window_flags;
f(&mut this.window_flags);
let is_fullscreen = this.fullscreen.is_some();
this.window_flags.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
let new_flags = this.window_flags;
drop(this);
old_flags.apply_diff(window, new_flags, set_client_rect);
}
pub fn refresh_window_state(this: MutexGuard<Self>, window: HWND, set_client_rect: Option<RECT>) {
Self::set_window_flags(this, window, set_client_rect, |_| ());
}
pub fn set_window_flags_in_place<F>(&mut self, f: F)
where F: FnOnce(&mut WindowFlags)
{
f(&mut self.window_flags);
}
}
impl MouseProperties {
pub fn cursor_flags(&self) -> CursorFlags {
self.cursor_flags
}
pub fn set_cursor_flags<F>(&mut self, window: HWND, f: F) -> Result<(), io::Error>
where F: FnOnce(&mut CursorFlags)
{
let old_flags = self.cursor_flags;
f(&mut self.cursor_flags);
match self.cursor_flags.refresh_os_cursor(window) {
Ok(()) => (),
Err(e) => {
self.cursor_flags = old_flags;
return Err(e);
}
}
Ok(())
}
}
impl WindowFlags {
fn mask(mut self) -> WindowFlags {
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
self &= WindowFlags::FULLSCREEN_AND_MASK;
}
if !self.contains(WindowFlags::VISIBLE) {
self &= WindowFlags::INVISIBLE_AND_MASK;
}
if !self.contains(WindowFlags::DECORATIONS) {
self &= WindowFlags::NO_DECORATIONS_AND_MASK;
}
self
}
pub fn to_window_styles(self) -> (DWORD, DWORD) {
use winapi::um::winuser::*;
let (mut style, mut style_ex) = (0, 0);
if self.contains(WindowFlags::RESIZABLE) {
style |= WS_SIZEBOX | WS_MAXIMIZEBOX;
}
if self.contains(WindowFlags::DECORATIONS) {
style |= WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER;
style_ex = WS_EX_WINDOWEDGE;
}
if self.contains(WindowFlags::VISIBLE) {
style |= WS_VISIBLE;
}
if self.contains(WindowFlags::ON_TASKBAR) {
style_ex |= WS_EX_APPWINDOW;
}
if self.contains(WindowFlags::ALWAYS_ON_TOP) {
style_ex |= WS_EX_TOPMOST;
}
if self.contains(WindowFlags::NO_BACK_BUFFER) {
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
}
if self.contains(WindowFlags::TRANSPARENT) {
// Is this necessary? The docs say that WS_EX_LAYERED requires a windows class without
// CS_OWNDC, and Winit windows have that flag set.
style_ex |= WS_EX_LAYERED;
}
if self.contains(WindowFlags::CHILD) {
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
}
if self.contains(WindowFlags::MAXIMIZED) {
style |= WS_MAXIMIZE;
}
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
style_ex |= WS_EX_ACCEPTFILES;
(style, style_ex)
}
/// Adjust the window client rectangle to the return value, if present.
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) {
self = self.mask();
new = new.mask();
let diff = self ^ new;
if diff == WindowFlags::empty() {
return;
}
if diff.contains(WindowFlags::VISIBLE) {
unsafe {
winuser::ShowWindow(
window,
match new.contains(WindowFlags::VISIBLE) {
true => winuser::SW_SHOW,
false => winuser::SW_HIDE
}
);
}
}
if diff.contains(WindowFlags::ALWAYS_ON_TOP) {
unsafe {
winuser::SetWindowPos(
window,
match new.contains(WindowFlags::ALWAYS_ON_TOP) {
true => winuser::HWND_TOPMOST,
false => winuser::HWND_NOTOPMOST,
},
0, 0, 0, 0,
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE,
);
winuser::UpdateWindow(window);
}
}
if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) {
unsafe {
winuser::ShowWindow(
window,
match new.contains(WindowFlags::MAXIMIZED) {
true => winuser::SW_MAXIMIZE,
false => winuser::SW_RESTORE
}
);
}
}
if diff != WindowFlags::empty() {
let (style, style_ex) = new.to_window_styles();
unsafe {
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0);
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
match set_client_rect.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) {
Some(client_rect) => {
let (x, y, w, h) = (
client_rect.left,
client_rect.top,
client_rect.right - client_rect.left,
client_rect.bottom - client_rect.top,
);
winuser::SetWindowPos(
window,
ptr::null_mut(),
x, y, w, h,
winuser::SWP_NOZORDER
| winuser::SWP_FRAMECHANGED,
);
},
None => {
// Refresh the window frame.
winuser::SetWindowPos(
window,
ptr::null_mut(),
0, 0, 0, 0,
winuser::SWP_NOZORDER
| winuser::SWP_NOMOVE
| winuser::SWP_NOSIZE
| winuser::SWP_FRAMECHANGED,
);
}
}
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
}
}
}
}
impl CursorFlags {
fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> {
let client_rect = util::get_client_rect(window)?;
if util::is_focused(window) {
if self.contains(CursorFlags::GRABBED) {
util::set_cursor_clip(Some(client_rect))?;
} else {
util::set_cursor_clip(None)?;
}
}
let cursor_in_client = self.contains(CursorFlags::IN_WINDOW);
if cursor_in_client {
util::set_cursor_hidden(self.contains(CursorFlags::HIDDEN));
} else {
util::set_cursor_hidden(false);
}
Ok(())
}
}