Move Windows backend to winit-win32
This commit is contained in:
parent
b1f8d778a1
commit
3b986f5583
26 changed files with 299 additions and 280 deletions
59
winit-win32/Cargo.toml
Normal file
59
winit-win32/Cargo.toml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
[package]
|
||||
description = "Winit's Win32/Windows backend"
|
||||
documentation = "https://docs.rs/winit-win32"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "winit-win32"
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version = "0.0.0"
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde", "bitflags/serde", "smol_str/serde", "dpi/serde", "winit-core/serde"]
|
||||
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
cursor-icon.workspace = true
|
||||
dpi.workspace = true
|
||||
rwh_06.workspace = true
|
||||
serde = { workspace = true, optional = true }
|
||||
smol_str.workspace = true
|
||||
tracing.workspace = true
|
||||
winit-core.workspace = true
|
||||
|
||||
# Platform-specific
|
||||
unicode-segmentation.workspace = true
|
||||
windows-sys = { workspace = true, features = [
|
||||
"Win32_Devices_HumanInterfaceDevice",
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Media",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Ole",
|
||||
"Win32_Security",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_WindowsProgramming",
|
||||
"Win32_UI_Accessibility",
|
||||
"Win32_UI_Controls",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_Ime",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Input_Pointer",
|
||||
"Win32_UI_Input_Touch",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_TextServices",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
winit.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
targets = ["x86_64-pc-windows-msvc"]
|
||||
1
winit-win32/README.md
Symbolic link
1
winit-win32/README.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../README.md
|
||||
171
winit-win32/src/dark_mode.rs
Normal file
171
winit-win32/src/dark_mode.rs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
use std::sync::LazyLock;
|
||||
/// This is a simple implementation of support for Windows Dark Mode,
|
||||
/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode
|
||||
use std::{ffi::c_void, ptr};
|
||||
|
||||
use windows_sys::core::PCSTR;
|
||||
use windows_sys::Win32::Foundation::{BOOL, HWND, NTSTATUS, S_OK};
|
||||
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
|
||||
use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;
|
||||
use windows_sys::Win32::UI::Accessibility::{HCF_HIGHCONTRASTON, HIGHCONTRASTA};
|
||||
use windows_sys::Win32::UI::Controls::SetWindowTheme;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{SystemParametersInfoA, SPI_GETHIGHCONTRAST};
|
||||
use winit_core::window::Theme;
|
||||
|
||||
use super::util;
|
||||
|
||||
static WIN10_BUILD_VERSION: LazyLock<Option<u32>> = LazyLock::new(|| {
|
||||
type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS;
|
||||
let handle = get_function!("ntdll.dll", RtlGetVersion);
|
||||
|
||||
if let Some(rtl_get_version) = handle {
|
||||
unsafe {
|
||||
let mut vi = OSVERSIONINFOW {
|
||||
dwOSVersionInfoSize: 0,
|
||||
dwMajorVersion: 0,
|
||||
dwMinorVersion: 0,
|
||||
dwBuildNumber: 0,
|
||||
dwPlatformId: 0,
|
||||
szCSDVersion: [0; 128],
|
||||
};
|
||||
|
||||
let status = (rtl_get_version)(&mut vi);
|
||||
|
||||
if status >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
|
||||
Some(vi.dwBuildNumber)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
static DARK_MODE_SUPPORTED: LazyLock<bool> = LazyLock::new(|| {
|
||||
// We won't try to do anything for windows versions < 17763
|
||||
// (Windows 10 October 2018 update)
|
||||
match *WIN10_BUILD_VERSION {
|
||||
Some(v) => v >= 17763,
|
||||
None => false,
|
||||
}
|
||||
});
|
||||
|
||||
static DARK_THEME_NAME: LazyLock<Vec<u16>> =
|
||||
LazyLock::new(|| util::encode_wide("DarkMode_Explorer"));
|
||||
static LIGHT_THEME_NAME: LazyLock<Vec<u16>> = LazyLock::new(|| util::encode_wide(""));
|
||||
|
||||
/// Attempt to set a theme on a window, if necessary.
|
||||
/// Returns the theme that was picked
|
||||
pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>) -> Theme {
|
||||
if *DARK_MODE_SUPPORTED {
|
||||
let is_dark_mode = match preferred_theme {
|
||||
Some(theme) => theme == Theme::Dark,
|
||||
None => should_use_dark_mode(),
|
||||
};
|
||||
|
||||
let theme = if is_dark_mode { Theme::Dark } else { Theme::Light };
|
||||
let theme_name = match theme {
|
||||
Theme::Dark => DARK_THEME_NAME.as_ptr(),
|
||||
Theme::Light => LIGHT_THEME_NAME.as_ptr(),
|
||||
};
|
||||
|
||||
let status = unsafe { SetWindowTheme(hwnd, theme_name, ptr::null()) };
|
||||
|
||||
if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) {
|
||||
return theme;
|
||||
}
|
||||
}
|
||||
|
||||
Theme::Light
|
||||
}
|
||||
|
||||
fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
|
||||
// Uses Windows undocumented API SetWindowCompositionAttribute,
|
||||
// as seen in win32-darkmode example linked at top of file.
|
||||
|
||||
type SetWindowCompositionAttribute =
|
||||
unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
type WINDOWCOMPOSITIONATTRIB = u32;
|
||||
const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[repr(C)]
|
||||
struct WINDOWCOMPOSITIONATTRIBDATA {
|
||||
Attrib: WINDOWCOMPOSITIONATTRIB,
|
||||
pvData: *mut c_void,
|
||||
cbData: usize,
|
||||
}
|
||||
|
||||
static SET_WINDOW_COMPOSITION_ATTRIBUTE: LazyLock<Option<SetWindowCompositionAttribute>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", SetWindowCompositionAttribute));
|
||||
|
||||
if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE {
|
||||
unsafe {
|
||||
// SetWindowCompositionAttribute needs a bigbool (i32), not bool.
|
||||
let mut is_dark_mode_bigbool = BOOL::from(is_dark_mode);
|
||||
|
||||
let mut data = WINDOWCOMPOSITIONATTRIBDATA {
|
||||
Attrib: WCA_USEDARKMODECOLORS,
|
||||
pvData: &mut is_dark_mode_bigbool as *mut _ as _,
|
||||
cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _,
|
||||
};
|
||||
|
||||
let status = set_window_composition_attribute(hwnd, &mut data);
|
||||
|
||||
status != false.into()
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_use_dark_mode() -> bool {
|
||||
should_apps_use_dark_mode() && !is_high_contrast()
|
||||
}
|
||||
|
||||
fn should_apps_use_dark_mode() -> bool {
|
||||
type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool;
|
||||
static SHOULD_APPS_USE_DARK_MODE: LazyLock<Option<ShouldAppsUseDarkMode>> =
|
||||
LazyLock::new(|| unsafe {
|
||||
const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR;
|
||||
|
||||
// We won't try to do anything for windows versions < 17763
|
||||
// (Windows 10 October 2018 update)
|
||||
if !*DARK_MODE_SUPPORTED {
|
||||
return None;
|
||||
}
|
||||
|
||||
let module = LoadLibraryA(c"uxtheme.dll".as_ptr().cast());
|
||||
|
||||
if module.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let handle = GetProcAddress(module, UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL);
|
||||
|
||||
handle.map(|handle| std::mem::transmute(handle))
|
||||
});
|
||||
|
||||
SHOULD_APPS_USE_DARK_MODE
|
||||
.map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() })
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_high_contrast() -> bool {
|
||||
let mut hc = HIGHCONTRASTA { cbSize: 0, dwFlags: 0, lpszDefaultScheme: ptr::null_mut() };
|
||||
|
||||
let ok = unsafe {
|
||||
SystemParametersInfoA(
|
||||
SPI_GETHIGHCONTRAST,
|
||||
std::mem::size_of_val(&hc) as _,
|
||||
&mut hc as *mut _ as _,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
ok != false.into() && util::has_flag(hc.dwFlags, HCF_HIGHCONTRASTON)
|
||||
}
|
||||
152
winit-win32/src/definitions.rs
Normal file
152
winit-win32/src/definitions.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use windows_sys::core::{GUID, HRESULT};
|
||||
use windows_sys::Win32::Foundation::{BOOL, HWND, POINTL};
|
||||
use windows_sys::Win32::System::Com::{FORMATETC, STGMEDIUM};
|
||||
|
||||
pub type IUnknown = *mut c_void;
|
||||
pub type IAdviseSink = *mut c_void;
|
||||
pub type IDataObject = *mut c_void;
|
||||
pub type IEnumFORMATETC = *mut c_void;
|
||||
pub type IEnumSTATDATA = *mut c_void;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IUnknownVtbl {
|
||||
pub QueryInterface: unsafe extern "system" fn(
|
||||
This: *mut IUnknown,
|
||||
riid: *const GUID,
|
||||
ppvObject: *mut *mut c_void,
|
||||
) -> HRESULT,
|
||||
pub AddRef: unsafe extern "system" fn(This: *mut IUnknown) -> u32,
|
||||
pub Release: unsafe extern "system" fn(This: *mut IUnknown) -> u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IDataObjectVtbl {
|
||||
pub parent: IUnknownVtbl,
|
||||
pub GetData: unsafe extern "system" fn(
|
||||
This: *mut IDataObject,
|
||||
pformatetcIn: *const FORMATETC,
|
||||
pmedium: *mut STGMEDIUM,
|
||||
) -> HRESULT,
|
||||
pub GetDataHere: unsafe extern "system" fn(
|
||||
This: *mut IDataObject,
|
||||
pformatetc: *const FORMATETC,
|
||||
pmedium: *mut STGMEDIUM,
|
||||
) -> HRESULT,
|
||||
QueryGetData:
|
||||
unsafe extern "system" fn(This: *mut IDataObject, pformatetc: *const FORMATETC) -> HRESULT,
|
||||
pub GetCanonicalFormatEtc: unsafe extern "system" fn(
|
||||
This: *mut IDataObject,
|
||||
pformatetcIn: *const FORMATETC,
|
||||
pformatetcOut: *mut FORMATETC,
|
||||
) -> HRESULT,
|
||||
pub SetData: unsafe extern "system" fn(
|
||||
This: *mut IDataObject,
|
||||
pformatetc: *const FORMATETC,
|
||||
pformatetcOut: *const FORMATETC,
|
||||
fRelease: BOOL,
|
||||
) -> HRESULT,
|
||||
pub EnumFormatEtc: unsafe extern "system" fn(
|
||||
This: *mut IDataObject,
|
||||
dwDirection: u32,
|
||||
ppenumFormatEtc: *mut *mut IEnumFORMATETC,
|
||||
) -> HRESULT,
|
||||
pub DAdvise: unsafe extern "system" fn(
|
||||
This: *mut IDataObject,
|
||||
pformatetc: *const FORMATETC,
|
||||
advf: u32,
|
||||
pAdvSInk: *const IAdviseSink,
|
||||
pdwConnection: *mut u32,
|
||||
) -> HRESULT,
|
||||
pub DUnadvise: unsafe extern "system" fn(This: *mut IDataObject, dwConnection: u32) -> HRESULT,
|
||||
pub EnumDAdvise: unsafe extern "system" fn(
|
||||
This: *mut IDataObject,
|
||||
ppenumAdvise: *const *const IEnumSTATDATA,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IDropTargetVtbl {
|
||||
pub parent: IUnknownVtbl,
|
||||
pub DragEnter: unsafe extern "system" fn(
|
||||
This: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
grfKeyState: u32,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT,
|
||||
pub DragOver: unsafe extern "system" fn(
|
||||
This: *mut IDropTarget,
|
||||
grfKeyState: u32,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT,
|
||||
pub DragLeave: unsafe extern "system" fn(This: *mut IDropTarget) -> HRESULT,
|
||||
pub Drop: unsafe extern "system" fn(
|
||||
This: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
grfKeyState: u32,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct IDropTarget {
|
||||
pub lpVtbl: *const IDropTargetVtbl,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ITaskbarListVtbl {
|
||||
pub parent: IUnknownVtbl,
|
||||
pub HrInit: unsafe extern "system" fn(This: *mut ITaskbarList) -> HRESULT,
|
||||
pub AddTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT,
|
||||
pub DeleteTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT,
|
||||
pub ActivateTab: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT,
|
||||
pub SetActiveAlt: unsafe extern "system" fn(This: *mut ITaskbarList, hwnd: HWND) -> HRESULT,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ITaskbarList {
|
||||
pub lpVtbl: *const ITaskbarListVtbl,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ITaskbarList2Vtbl {
|
||||
pub parent: ITaskbarListVtbl,
|
||||
pub MarkFullscreenWindow: unsafe extern "system" fn(
|
||||
This: *mut ITaskbarList2,
|
||||
hwnd: HWND,
|
||||
fFullscreen: BOOL,
|
||||
) -> HRESULT,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct ITaskbarList2 {
|
||||
pub lpVtbl: *const ITaskbarList2Vtbl,
|
||||
}
|
||||
|
||||
pub const CLSID_TaskbarList: GUID = GUID {
|
||||
data1: 0x56fdf344,
|
||||
data2: 0xfd6d,
|
||||
data3: 0x11d0,
|
||||
data4: [0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90],
|
||||
};
|
||||
|
||||
pub const IID_ITaskbarList: GUID = GUID {
|
||||
data1: 0x56fdf342,
|
||||
data2: 0xfd6d,
|
||||
data3: 0x11d0,
|
||||
data4: [0x95, 0x8a, 0x00, 0x60, 0x97, 0xc9, 0xa0, 0x90],
|
||||
};
|
||||
|
||||
pub const IID_ITaskbarList2: GUID = GUID {
|
||||
data1: 0x602d4995,
|
||||
data2: 0xb13a,
|
||||
data3: 0x429b,
|
||||
data4: [0xa6, 0x6e, 0x19, 0x35, 0xe4, 0x4f, 0x43, 0x17],
|
||||
};
|
||||
112
winit-win32/src/dpi.rs
Normal file
112
winit-win32/src/dpi.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#![allow(non_snake_case, unused_unsafe)]
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use windows_sys::Win32::Foundation::{HWND, S_OK};
|
||||
use windows_sys::Win32::Graphics::Gdi::{
|
||||
GetDC, GetDeviceCaps, MonitorFromWindow, HMONITOR, LOGPIXELSX, MONITOR_DEFAULTTONEAREST,
|
||||
};
|
||||
use windows_sys::Win32::UI::HiDpi::{
|
||||
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
||||
MDT_EFFECTIVE_DPI, PROCESS_PER_MONITOR_DPI_AWARE,
|
||||
};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::IsProcessDPIAware;
|
||||
|
||||
use crate::util::{
|
||||
ENABLE_NON_CLIENT_DPI_SCALING, GET_DPI_FOR_MONITOR, GET_DPI_FOR_WINDOW, SET_PROCESS_DPI_AWARE,
|
||||
SET_PROCESS_DPI_AWARENESS, SET_PROCESS_DPI_AWARENESS_CONTEXT,
|
||||
};
|
||||
|
||||
pub fn become_dpi_aware() {
|
||||
static ENABLE_DPI_AWARENESS: Once = Once::new();
|
||||
ENABLE_DPI_AWARENESS.call_once(|| {
|
||||
unsafe {
|
||||
if let Some(SetProcessDpiAwarenessContext) = *SET_PROCESS_DPI_AWARENESS_CONTEXT {
|
||||
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||
if SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
|
||||
== false.into()
|
||||
{
|
||||
// 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) = *SET_PROCESS_DPI_AWARENESS {
|
||||
// We are on Windows 8.1 or later.
|
||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||
} else if let Some(SetProcessDPIAware) = *SET_PROCESS_DPI_AWARE {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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 hwnd_dpi(hwnd: HWND) -> u32 {
|
||||
let hdc = unsafe { 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 unsafe { GetDpiForWindow(hwnd) } {
|
||||
0 => BASE_DPI, // 0 is returned if hwnd is invalid
|
||||
dpi => dpi,
|
||||
}
|
||||
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let monitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) };
|
||||
if monitor.is_null() {
|
||||
return BASE_DPI;
|
||||
}
|
||||
|
||||
let mut dpi_x = 0;
|
||||
let mut dpi_y = 0;
|
||||
if unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) } == S_OK {
|
||||
dpi_x
|
||||
} else {
|
||||
BASE_DPI
|
||||
}
|
||||
} else {
|
||||
// We are on Vista or later.
|
||||
if unsafe { IsProcessDPIAware() } != false.into() {
|
||||
// If the process is DPI aware, then scaling must be handled by the application using
|
||||
// this DPI value.
|
||||
unsafe { GetDeviceCaps(hdc, LOGPIXELSX as i32) 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
|
||||
}
|
||||
}
|
||||
}
|
||||
240
winit-win32/src/drop_handler.rs
Normal file
240
winit-win32/src/drop_handler.rs
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
use std::ffi::{c_void, OsString};
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use std::path::PathBuf;
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use dpi::PhysicalPosition;
|
||||
use tracing::debug;
|
||||
use windows_sys::core::{GUID, HRESULT};
|
||||
use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINT, POINTL, S_OK};
|
||||
use windows_sys::Win32::Graphics::Gdi::ScreenToClient;
|
||||
use windows_sys::Win32::System::Com::{DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL};
|
||||
use windows_sys::Win32::System::Ole::{CF_HDROP, DROPEFFECT_COPY, DROPEFFECT_NONE};
|
||||
use windows_sys::Win32::UI::Shell::{DragFinish, DragQueryFileW, HDROP};
|
||||
use winit_core::event::WindowEvent;
|
||||
|
||||
use crate::definitions::{
|
||||
IDataObject, IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknown, IUnknownVtbl,
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FileDropHandlerData {
|
||||
pub interface: IDropTarget,
|
||||
refcount: AtomicUsize,
|
||||
window: HWND,
|
||||
send_event: Box<dyn Fn(WindowEvent)>,
|
||||
cursor_effect: u32,
|
||||
valid: bool, /* If the currently hovered item is not valid there must not be any
|
||||
* `DragLeft` emitted */
|
||||
}
|
||||
|
||||
pub struct FileDropHandler {
|
||||
pub data: *mut FileDropHandlerData,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl FileDropHandler {
|
||||
pub(crate) fn new(window: HWND, send_event: Box<dyn Fn(WindowEvent)>) -> 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,
|
||||
valid: false,
|
||||
});
|
||||
FileDropHandler { data: Box::into_raw(data) }
|
||||
}
|
||||
|
||||
// Implement IUnknown
|
||||
pub unsafe extern "system" fn QueryInterface(
|
||||
_this: *mut IUnknown,
|
||||
_riid: *const GUID,
|
||||
_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 IUnknown) -> u32 {
|
||||
let drop_handler_data = unsafe { Self::from_interface(this) };
|
||||
let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1;
|
||||
count as u32
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn Release(this: *mut IUnknown) -> u32 {
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1;
|
||||
if count == 0 {
|
||||
// Destroy the underlying data
|
||||
drop(unsafe { Box::from_raw(drop_handler as *mut FileDropHandlerData) });
|
||||
}
|
||||
count as u32
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn DragEnter(
|
||||
this: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
_grfKeyState: u32,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT {
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
unsafe {
|
||||
ScreenToClient(drop_handler.window, &mut pt);
|
||||
}
|
||||
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
|
||||
let mut paths = Vec::new();
|
||||
let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) };
|
||||
drop_handler.valid = hdrop.is_some();
|
||||
if drop_handler.valid {
|
||||
(drop_handler.send_event)(WindowEvent::DragEntered { paths, position });
|
||||
}
|
||||
drop_handler.cursor_effect =
|
||||
if drop_handler.valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE };
|
||||
unsafe {
|
||||
*pdwEffect = drop_handler.cursor_effect;
|
||||
}
|
||||
|
||||
S_OK
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn DragOver(
|
||||
this: *mut IDropTarget,
|
||||
_grfKeyState: u32,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT {
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
if drop_handler.valid {
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
unsafe {
|
||||
ScreenToClient(drop_handler.window, &mut pt);
|
||||
}
|
||||
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
|
||||
(drop_handler.send_event)(WindowEvent::DragMoved { position });
|
||||
}
|
||||
unsafe {
|
||||
*pdwEffect = drop_handler.cursor_effect;
|
||||
}
|
||||
|
||||
S_OK
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT {
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
if drop_handler.valid {
|
||||
(drop_handler.send_event)(WindowEvent::DragLeft { position: None });
|
||||
}
|
||||
|
||||
S_OK
|
||||
}
|
||||
|
||||
pub unsafe extern "system" fn Drop(
|
||||
this: *mut IDropTarget,
|
||||
pDataObj: *const IDataObject,
|
||||
_grfKeyState: u32,
|
||||
pt: POINTL,
|
||||
pdwEffect: *mut u32,
|
||||
) -> HRESULT {
|
||||
let drop_handler = unsafe { Self::from_interface(this) };
|
||||
if drop_handler.valid {
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
unsafe {
|
||||
ScreenToClient(drop_handler.window, &mut pt);
|
||||
}
|
||||
let position = PhysicalPosition::new(pt.x as f64, pt.y as f64);
|
||||
let mut paths = Vec::new();
|
||||
let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) };
|
||||
(drop_handler.send_event)(WindowEvent::DragDropped { paths, position });
|
||||
if let Some(hdrop) = hdrop {
|
||||
unsafe {
|
||||
DragFinish(hdrop);
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
*pdwEffect = drop_handler.cursor_effect;
|
||||
}
|
||||
|
||||
S_OK
|
||||
}
|
||||
|
||||
unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData {
|
||||
unsafe { &mut *(this as *mut _) }
|
||||
}
|
||||
|
||||
unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, mut callback: F) -> Option<HDROP>
|
||||
where
|
||||
F: FnMut(PathBuf),
|
||||
{
|
||||
let drop_format = FORMATETC {
|
||||
cfFormat: CF_HDROP,
|
||||
ptd: ptr::null_mut(),
|
||||
dwAspect: DVASPECT_CONTENT,
|
||||
lindex: -1,
|
||||
tymed: TYMED_HGLOBAL as u32,
|
||||
};
|
||||
|
||||
let mut medium = unsafe { std::mem::zeroed() };
|
||||
let get_data_fn = unsafe { (*(*data_obj).cast::<IDataObjectVtbl>()).GetData };
|
||||
let get_data_result = unsafe { get_data_fn(data_obj as *mut _, &drop_format, &mut medium) };
|
||||
if get_data_result >= 0 {
|
||||
let hdrop = unsafe { medium.u.hGlobal as HDROP };
|
||||
|
||||
// The second parameter (0xFFFFFFFF) instructs the function to return the item count
|
||||
let item_count = unsafe { DragQueryFileW(hdrop, 0xffffffff, ptr::null_mut(), 0) };
|
||||
|
||||
for i in 0..item_count {
|
||||
// Get the length of the path string NOT including the terminating null character.
|
||||
// Previously, this was using a fixed size array of MAX_PATH length, but the
|
||||
// Windows API allows longer paths under certain circumstances.
|
||||
let character_count =
|
||||
unsafe { DragQueryFileW(hdrop, i, ptr::null_mut(), 0) as usize };
|
||||
let str_len = character_count + 1;
|
||||
|
||||
// Fill path_buf with the null-terminated file name
|
||||
let mut path_buf = Vec::with_capacity(str_len);
|
||||
unsafe {
|
||||
DragQueryFileW(hdrop, i, path_buf.as_mut_ptr(), str_len as u32);
|
||||
path_buf.set_len(str_len);
|
||||
}
|
||||
|
||||
callback(OsString::from_wide(&path_buf[0..character_count]).into());
|
||||
}
|
||||
|
||||
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 occurred while processing dropped/hovered item: item is not a file.");
|
||||
None
|
||||
} else {
|
||||
debug!("Unexpected error occurred while processing dropped/hovered item.");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FileDropHandler {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
FileDropHandler::Release(self.data as *mut IUnknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
|
||||
parent: IUnknownVtbl {
|
||||
QueryInterface: FileDropHandler::QueryInterface,
|
||||
AddRef: FileDropHandler::AddRef,
|
||||
Release: FileDropHandler::Release,
|
||||
},
|
||||
DragEnter: FileDropHandler::DragEnter,
|
||||
DragOver: FileDropHandler::DragOver,
|
||||
DragLeave: FileDropHandler::DragLeave,
|
||||
Drop: FileDropHandler::Drop,
|
||||
};
|
||||
2626
winit-win32/src/event_loop.rs
Normal file
2626
winit-win32/src/event_loop.rs
Normal file
File diff suppressed because it is too large
Load diff
446
winit-win32/src/event_loop/runner.rs
Normal file
446
winit-win32/src/event_loop/runner.rs
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
use std::any::Any;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
use std::{fmt, mem, panic};
|
||||
|
||||
use dpi::PhysicalSize;
|
||||
use windows_sys::Win32::Foundation::HWND;
|
||||
use winit_core::application::ApplicationHandler;
|
||||
use winit_core::event::{DeviceEvent, DeviceId, StartCause, SurfaceSizeWriter, WindowEvent};
|
||||
use winit_core::event_loop::ActiveEventLoop as RootActiveEventLoop;
|
||||
use winit_core::window::WindowId;
|
||||
|
||||
use super::{ActiveEventLoop, ControlFlow, EventLoopThreadExecutor};
|
||||
use crate::event_loop::{WindowData, GWL_USERDATA};
|
||||
use crate::util::get_window_long;
|
||||
|
||||
type EventHandler = Cell<Option<&'static mut (dyn ApplicationHandler + 'static)>>;
|
||||
|
||||
pub(crate) struct EventLoopRunner {
|
||||
pub(super) thread_id: u32,
|
||||
|
||||
// The event loop's win32 handles
|
||||
pub(super) thread_msg_target: HWND,
|
||||
|
||||
// Setting this will ensure pump_events will return to the external
|
||||
// loop asap. E.g. set after each RedrawRequested to ensure pump_events
|
||||
// can't stall an external loop beyond a frame
|
||||
pub(super) interrupt_msg_dispatch: Cell<bool>,
|
||||
|
||||
control_flow: Cell<ControlFlow>,
|
||||
exit: Cell<Option<i32>>,
|
||||
runner_state: Cell<RunnerState>,
|
||||
last_events_cleared: Cell<Instant>,
|
||||
event_handler: Rc<EventHandler>,
|
||||
event_buffer: RefCell<VecDeque<Event>>,
|
||||
|
||||
panic_error: Cell<Option<PanicError>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for EventLoopRunner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("EventLoopRunner")
|
||||
.field("thread_msg_target", &self.thread_msg_target)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
pub type PanicError = Box<dyn Any + Send + 'static>;
|
||||
|
||||
/// See `move_state_to` function for details on how the state loop works.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub(crate) enum RunnerState {
|
||||
/// The event loop has just been created, and an `Init` event must be sent.
|
||||
Uninitialized,
|
||||
/// The event loop is idling.
|
||||
Idle,
|
||||
/// The event loop is handling the OS's events and sending them to the user's callback.
|
||||
/// `NewEvents` has been sent, and `AboutToWait` hasn't.
|
||||
HandlingMainEvents,
|
||||
/// The event loop has been destroyed. No other events will be emitted.
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Event {
|
||||
Device { device_id: DeviceId, event: DeviceEvent },
|
||||
Window { window_id: WindowId, event: WindowEvent },
|
||||
BufferedScaleFactorChanged(HWND, f64, PhysicalSize<u32>),
|
||||
// FIXME(madsmtm): Coalesce these into a flag (or similar) instead of handling them as events.
|
||||
// https://github.com/rust-windowing/winit/pull/3687
|
||||
WakeUp,
|
||||
}
|
||||
|
||||
impl EventLoopRunner {
|
||||
pub(crate) fn new(thread_id: u32, thread_msg_target: HWND) -> Self {
|
||||
Self {
|
||||
thread_id,
|
||||
thread_msg_target,
|
||||
interrupt_msg_dispatch: Cell::new(false),
|
||||
runner_state: Cell::new(RunnerState::Uninitialized),
|
||||
control_flow: Cell::new(ControlFlow::default()),
|
||||
exit: Cell::new(None),
|
||||
panic_error: Cell::new(None),
|
||||
last_events_cleared: Cell::new(Instant::now()),
|
||||
event_handler: Rc::new(Cell::new(None)),
|
||||
event_buffer: RefCell::new(VecDeque::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate the application's event handler with the runner.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The returned type must not be leaked (as that would allow the application to be associated
|
||||
/// with the runner for too long).
|
||||
pub(crate) unsafe fn set_app<'app>(
|
||||
&self,
|
||||
app: &'app mut (dyn ApplicationHandler + 'app),
|
||||
) -> impl Drop + 'app {
|
||||
// Erase app lifetime, to allow storing on the event loop runner.
|
||||
//
|
||||
// SAFETY: Caller upholds that the lifetime of the closure is upheld, by not dropping the
|
||||
// return type which resets it.
|
||||
let f = unsafe {
|
||||
mem::transmute::<
|
||||
&'app mut (dyn ApplicationHandler + 'app),
|
||||
&'static mut (dyn ApplicationHandler + 'static),
|
||||
>(app)
|
||||
};
|
||||
|
||||
let old_event_handler = self.event_handler.replace(Some(f));
|
||||
|
||||
assert!(old_event_handler.is_none());
|
||||
|
||||
struct Resetter(Rc<EventHandler>);
|
||||
|
||||
impl Drop for Resetter {
|
||||
fn drop(&mut self) {
|
||||
self.0.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
Resetter(self.event_handler.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn reset_runner(&self) {
|
||||
let Self {
|
||||
thread_id: _,
|
||||
thread_msg_target: _,
|
||||
interrupt_msg_dispatch,
|
||||
runner_state,
|
||||
panic_error,
|
||||
control_flow: _,
|
||||
exit,
|
||||
last_events_cleared: _,
|
||||
event_handler,
|
||||
event_buffer: _,
|
||||
} = self;
|
||||
interrupt_msg_dispatch.set(false);
|
||||
runner_state.set(RunnerState::Uninitialized);
|
||||
panic_error.set(None);
|
||||
exit.set(None);
|
||||
event_handler.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
/// State retrieval functions.
|
||||
impl EventLoopRunner {
|
||||
#[allow(unused)]
|
||||
pub fn thread_msg_target(&self) -> HWND {
|
||||
self.thread_msg_target
|
||||
}
|
||||
|
||||
pub fn take_panic_error(&self) -> Result<(), PanicError> {
|
||||
match self.panic_error.take() {
|
||||
Some(err) => Err(err),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_control_flow(&self, control_flow: ControlFlow) {
|
||||
self.control_flow.set(control_flow)
|
||||
}
|
||||
|
||||
pub fn control_flow(&self) -> ControlFlow {
|
||||
self.control_flow.get()
|
||||
}
|
||||
|
||||
pub fn set_exit_code(&self, code: i32) {
|
||||
self.exit.set(Some(code))
|
||||
}
|
||||
|
||||
pub fn exit_code(&self) -> Option<i32> {
|
||||
self.exit.get()
|
||||
}
|
||||
|
||||
pub fn clear_exit(&self) {
|
||||
self.exit.set(None);
|
||||
}
|
||||
|
||||
pub fn should_buffer(&self) -> bool {
|
||||
let handler = self.event_handler.take();
|
||||
let should_buffer = handler.is_none();
|
||||
self.event_handler.set(handler);
|
||||
should_buffer
|
||||
}
|
||||
}
|
||||
|
||||
/// Misc. functions
|
||||
impl EventLoopRunner {
|
||||
pub fn catch_unwind<R>(&self, f: impl FnOnce() -> R) -> Option<R> {
|
||||
let panic_error = self.panic_error.take();
|
||||
if panic_error.is_none() {
|
||||
let result = panic::catch_unwind(panic::AssertUnwindSafe(f));
|
||||
|
||||
// Check to see if the panic error was set in a re-entrant call to catch_unwind inside
|
||||
// of `f`. If it was, that error takes priority. If it wasn't, check if our call to
|
||||
// catch_unwind caught any panics and set panic_error appropriately.
|
||||
match self.panic_error.take() {
|
||||
None => match result {
|
||||
Ok(r) => Some(r),
|
||||
Err(e) => {
|
||||
self.panic_error.set(Some(e));
|
||||
None
|
||||
},
|
||||
},
|
||||
Some(e) => {
|
||||
self.panic_error.set(Some(e));
|
||||
None
|
||||
},
|
||||
}
|
||||
} else {
|
||||
self.panic_error.set(panic_error);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn create_thread_executor(&self) -> EventLoopThreadExecutor {
|
||||
EventLoopThreadExecutor { thread_id: self.thread_id, target_window: self.thread_msg_target }
|
||||
}
|
||||
}
|
||||
|
||||
/// Event dispatch functions.
|
||||
impl EventLoopRunner {
|
||||
pub(crate) fn prepare_wait(self: &Rc<Self>) {
|
||||
self.move_state_to(RunnerState::Idle);
|
||||
}
|
||||
|
||||
pub(crate) fn wakeup(self: &Rc<Self>) {
|
||||
self.move_state_to(RunnerState::HandlingMainEvents);
|
||||
}
|
||||
|
||||
pub(crate) fn send_event(self: &Rc<Self>, event: Event) {
|
||||
if let Event::Window { event: WindowEvent::RedrawRequested, .. } = event {
|
||||
self.call_event_handler(|app, event_loop| event.dispatch_event(app, event_loop));
|
||||
// As a rule, to ensure that `pump_events` can't block an external event loop
|
||||
// for too long, we always guarantee that `pump_events` will return control to
|
||||
// the external loop asap after a `RedrawRequested` event is dispatched.
|
||||
self.interrupt_msg_dispatch.set(true);
|
||||
} else if self.should_buffer() {
|
||||
// If the runner is already borrowed, we're in the middle of an event loop invocation.
|
||||
// Add the event to a buffer to be processed later.
|
||||
self.event_buffer.borrow_mut().push_back(event.buffer_scale_factor())
|
||||
} else {
|
||||
self.call_event_handler(|app, event_loop| event.dispatch_event(app, event_loop));
|
||||
self.dispatch_buffered_events();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn loop_destroyed(self: &Rc<Self>) {
|
||||
self.move_state_to(RunnerState::Destroyed);
|
||||
}
|
||||
|
||||
fn call_event_handler(
|
||||
self: &Rc<Self>,
|
||||
closure: impl FnOnce(&mut dyn ApplicationHandler, &dyn RootActiveEventLoop),
|
||||
) {
|
||||
self.catch_unwind(|| {
|
||||
let event_handler = self.event_handler.take().expect(
|
||||
"either event handler is re-entrant (likely), or no event handler is registered \
|
||||
(very unlikely)",
|
||||
);
|
||||
|
||||
closure(event_handler, ActiveEventLoop::from_ref(self));
|
||||
|
||||
assert!(self.event_handler.replace(Some(event_handler)).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
fn dispatch_buffered_events(self: &Rc<Self>) {
|
||||
loop {
|
||||
// We do this instead of using a `while let` loop because if we use a `while let`
|
||||
// loop the reference returned `borrow_mut()` doesn't get dropped until the end
|
||||
// of the loop's body and attempts to add events to the event buffer while in
|
||||
// `process_event` will fail.
|
||||
let buffered_event_opt = self.event_buffer.borrow_mut().pop_front();
|
||||
match buffered_event_opt {
|
||||
Some(e) => {
|
||||
self.call_event_handler(|app, event_loop| e.dispatch_event(app, event_loop))
|
||||
},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch control flow events (`NewEvents`, `AboutToWait`, and
|
||||
/// `LoopExiting`) as necessary to bring the internal `RunnerState` to the
|
||||
/// new runner state.
|
||||
///
|
||||
/// The state transitions are defined as follows:
|
||||
///
|
||||
/// ```text
|
||||
/// Uninitialized
|
||||
/// |
|
||||
/// V
|
||||
/// Idle
|
||||
/// ^ |
|
||||
/// | V
|
||||
/// HandlingMainEvents
|
||||
/// |
|
||||
/// V
|
||||
/// Destroyed
|
||||
/// ```
|
||||
///
|
||||
/// Attempting to transition back to `Uninitialized` will result in a panic. Attempting to
|
||||
/// transition *from* `Destroyed` will also result in a panic. Transitioning to the current
|
||||
/// state is a no-op. Even if the `new_runner_state` isn't the immediate next state in the
|
||||
/// runner state machine (e.g. `self.runner_state == HandlingMainEvents` and
|
||||
/// `new_runner_state == Idle`), the intermediate state transitions will still be executed.
|
||||
fn move_state_to(self: &Rc<Self>, new_runner_state: RunnerState) {
|
||||
use RunnerState::{Destroyed, HandlingMainEvents, Idle, Uninitialized};
|
||||
|
||||
match (self.runner_state.replace(new_runner_state), new_runner_state) {
|
||||
(Uninitialized, Uninitialized)
|
||||
| (Idle, Idle)
|
||||
| (HandlingMainEvents, HandlingMainEvents)
|
||||
| (Destroyed, Destroyed) => (),
|
||||
|
||||
// State transitions that initialize the event loop.
|
||||
(Uninitialized, HandlingMainEvents) => {
|
||||
self.call_new_events(true);
|
||||
},
|
||||
(Uninitialized, Idle) => {
|
||||
self.call_new_events(true);
|
||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
},
|
||||
(Uninitialized, Destroyed) => {
|
||||
self.call_new_events(true);
|
||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
},
|
||||
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
|
||||
|
||||
// State transitions that start the event handling process.
|
||||
(Idle, HandlingMainEvents) => {
|
||||
self.call_new_events(false);
|
||||
},
|
||||
(Idle, Destroyed) => {},
|
||||
|
||||
(HandlingMainEvents, Idle) => {
|
||||
// This is always the last event we dispatch before waiting for new events
|
||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
},
|
||||
(HandlingMainEvents, Destroyed) => {
|
||||
self.call_event_handler(|app, event_loop| app.about_to_wait(event_loop));
|
||||
self.last_events_cleared.set(Instant::now());
|
||||
},
|
||||
|
||||
(Destroyed, _) => panic!("cannot move state from Destroyed"),
|
||||
}
|
||||
}
|
||||
|
||||
fn call_new_events(self: &Rc<Self>, init: bool) {
|
||||
let start_cause = match (init, self.control_flow(), self.exit.get()) {
|
||||
(true, ..) => StartCause::Init,
|
||||
(false, ControlFlow::Poll, None) => StartCause::Poll,
|
||||
(false, _, Some(_)) | (false, ControlFlow::Wait, None) => StartCause::WaitCancelled {
|
||||
requested_resume: None,
|
||||
start: self.last_events_cleared.get(),
|
||||
},
|
||||
(false, ControlFlow::WaitUntil(requested_resume), None) => {
|
||||
if Instant::now() < requested_resume {
|
||||
StartCause::WaitCancelled {
|
||||
requested_resume: Some(requested_resume),
|
||||
start: self.last_events_cleared.get(),
|
||||
}
|
||||
} else {
|
||||
StartCause::ResumeTimeReached {
|
||||
requested_resume,
|
||||
start: self.last_events_cleared.get(),
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
self.call_event_handler(|app, event_loop| app.new_events(event_loop, start_cause));
|
||||
// NB: For consistency all platforms must call `can_create_surfaces` even though Windows
|
||||
// applications don't themselves have a formal surface destroy/create lifecycle.
|
||||
if init {
|
||||
self.call_event_handler(|app, event_loop| app.can_create_surfaces(event_loop));
|
||||
}
|
||||
self.dispatch_buffered_events();
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Mark ScaleFactorChanged as being buffered (which forces us to re-handle when the user set a
|
||||
/// new size).
|
||||
pub fn buffer_scale_factor(self) -> Self {
|
||||
match self {
|
||||
Self::Window {
|
||||
event: WindowEvent::ScaleFactorChanged { scale_factor, surface_size_writer },
|
||||
window_id,
|
||||
} => Event::BufferedScaleFactorChanged(
|
||||
window_id.into_raw() as HWND,
|
||||
scale_factor,
|
||||
surface_size_writer.surface_size().unwrap(),
|
||||
),
|
||||
event => event,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_event(
|
||||
self,
|
||||
app: &mut dyn ApplicationHandler,
|
||||
event_loop: &dyn RootActiveEventLoop,
|
||||
) {
|
||||
match self {
|
||||
Self::Window { window_id, event } => app.window_event(event_loop, window_id, event),
|
||||
Self::Device { device_id, event } => {
|
||||
app.device_event(event_loop, Some(device_id), event)
|
||||
},
|
||||
Self::BufferedScaleFactorChanged(window, scale_factor, new_surface_size) => {
|
||||
let user_new_surface_size = Arc::new(Mutex::new(new_surface_size));
|
||||
app.window_event(
|
||||
event_loop,
|
||||
WindowId::from_raw(window as usize),
|
||||
WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
surface_size_writer: SurfaceSizeWriter::new(Arc::downgrade(
|
||||
&user_new_surface_size,
|
||||
)),
|
||||
},
|
||||
);
|
||||
let surface_size = *user_new_surface_size.lock().unwrap();
|
||||
|
||||
drop(user_new_surface_size);
|
||||
|
||||
if surface_size != new_surface_size {
|
||||
let window_flags = unsafe {
|
||||
let userdata = get_window_long(window, GWL_USERDATA) as *mut WindowData;
|
||||
(*userdata).window_state_lock().window_flags
|
||||
};
|
||||
|
||||
window_flags.set_size(window, surface_size);
|
||||
}
|
||||
},
|
||||
Self::WakeUp => app.proxy_wake_up(event_loop),
|
||||
}
|
||||
}
|
||||
}
|
||||
263
winit-win32/src/icon.rs
Normal file
263
winit-win32/src/icon.rs
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
use std::ffi::c_void;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, io, mem, ptr};
|
||||
|
||||
use cursor_icon::CursorIcon;
|
||||
use dpi::PhysicalSize;
|
||||
use windows_sys::core::PCWSTR;
|
||||
use windows_sys::Win32::Graphics::Gdi::{
|
||||
CreateBitmap, CreateCompatibleBitmap, DeleteObject, GetDC, ReleaseDC, SetBitmapBits,
|
||||
};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
CreateIcon, CreateIconIndirect, DestroyCursor, DestroyIcon, LoadImageW, HCURSOR, HICON,
|
||||
ICONINFO, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTSIZE, LR_LOADFROMFILE,
|
||||
};
|
||||
use winit_core::cursor::{CursorImage, CustomCursorProvider};
|
||||
use winit_core::error::RequestError;
|
||||
use winit_core::icon::*;
|
||||
|
||||
use super::util;
|
||||
use crate::WinIcon;
|
||||
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
unsafe impl Send for WinIcon {}
|
||||
|
||||
impl WinIcon {
|
||||
pub(crate) fn from_path_impl<P: AsRef<Path>>(
|
||||
path: P,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
|
||||
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
|
||||
|
||||
let wide_path = util::encode_wide(path.as_ref());
|
||||
|
||||
let handle = unsafe {
|
||||
LoadImageW(
|
||||
ptr::null_mut(),
|
||||
wide_path.as_ptr(),
|
||||
IMAGE_ICON,
|
||||
width,
|
||||
height,
|
||||
LR_DEFAULTSIZE | LR_LOADFROMFILE,
|
||||
)
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon::from_handle(handle as HICON))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_resource_impl(
|
||||
resource_id: u16,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
Self::from_resource_ptr(resource_id as PCWSTR, size)
|
||||
}
|
||||
|
||||
pub(crate) fn from_resource_name_impl(
|
||||
resource_name: &str,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
let wide_name = util::encode_wide(resource_name);
|
||||
Self::from_resource_ptr(wide_name.as_ptr(), size)
|
||||
}
|
||||
|
||||
fn from_resource_ptr(
|
||||
resource: PCWSTR,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
// width / height of 0 along with LR_DEFAULTSIZE tells windows to load the default icon size
|
||||
let (width, height) = size.map(Into::into).unwrap_or((0, 0));
|
||||
let handle = unsafe {
|
||||
LoadImageW(
|
||||
util::get_instance_handle(),
|
||||
resource,
|
||||
IMAGE_ICON,
|
||||
width,
|
||||
height,
|
||||
LR_DEFAULTSIZE,
|
||||
)
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon::from_handle(handle as HICON))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn as_raw_handle(&self) -> HICON {
|
||||
self.inner.handle
|
||||
}
|
||||
|
||||
pub(crate) fn from_rgba(rgba: &RgbaIcon) -> Result<Self, BadIcon> {
|
||||
let pixel_count = rgba.buffer().len() / PIXEL_SIZE;
|
||||
let mut and_mask = Vec::with_capacity(pixel_count);
|
||||
let pixels = unsafe {
|
||||
std::slice::from_raw_parts_mut(rgba.buffer().as_ptr() as *mut Pixel, pixel_count)
|
||||
};
|
||||
for pixel in pixels {
|
||||
and_mask.push(pixel.a.wrapping_sub(u8::MAX)); // invert alpha channel
|
||||
pixel.convert_to_bgra();
|
||||
}
|
||||
assert_eq!(and_mask.len(), pixel_count);
|
||||
let handle = unsafe {
|
||||
CreateIcon(
|
||||
ptr::null_mut(),
|
||||
rgba.width() as i32,
|
||||
rgba.height() as i32,
|
||||
1,
|
||||
(PIXEL_SIZE * 8) as u8,
|
||||
and_mask.as_ptr(),
|
||||
rgba.buffer().as_ptr(),
|
||||
)
|
||||
};
|
||||
if !handle.is_null() {
|
||||
Ok(WinIcon::from_handle(handle))
|
||||
} else {
|
||||
Err(BadIcon::OsError(io::Error::last_os_error()))
|
||||
}
|
||||
}
|
||||
|
||||
fn from_handle(handle: HICON) -> Self {
|
||||
Self { inner: Arc::new(RaiiIcon { handle }) }
|
||||
}
|
||||
}
|
||||
|
||||
impl IconProvider for WinIcon {}
|
||||
|
||||
impl fmt::Debug for WinIcon {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
(*self.inner).fmt(formatter)
|
||||
}
|
||||
}
|
||||
|
||||
impl Pixel {
|
||||
fn convert_to_bgra(&mut self) {
|
||||
mem::swap(&mut self.r, &mut self.b);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum IconType {
|
||||
Small = ICON_SMALL as isize,
|
||||
Big = ICON_BIG as isize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct RaiiIcon {
|
||||
handle: HICON,
|
||||
}
|
||||
|
||||
unsafe impl Send for RaiiIcon {}
|
||||
unsafe impl Sync for RaiiIcon {}
|
||||
|
||||
impl Drop for RaiiIcon {
|
||||
fn drop(&mut self) {
|
||||
unsafe { DestroyIcon(self.handle) };
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SelectedCursor {
|
||||
Named(CursorIcon),
|
||||
Custom(Arc<RaiiCursor>),
|
||||
}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
Self::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct WinCursor(pub(super) Arc<RaiiCursor>);
|
||||
|
||||
impl CustomCursorProvider for WinCursor {
|
||||
fn is_animated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl WinCursor {
|
||||
pub(crate) fn new(image: &CursorImage) -> Result<Self, RequestError> {
|
||||
let mut bgra = Vec::from(image.buffer());
|
||||
bgra.chunks_exact_mut(4).for_each(|chunk| chunk.swap(0, 2));
|
||||
|
||||
let w = image.width() as i32;
|
||||
let h = image.height() as i32;
|
||||
|
||||
unsafe {
|
||||
let hdc_screen = GetDC(ptr::null_mut());
|
||||
if hdc_screen.is_null() {
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
let hbm_color = CreateCompatibleBitmap(hdc_screen, w, h);
|
||||
ReleaseDC(ptr::null_mut(), hdc_screen);
|
||||
if hbm_color.is_null() {
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
if SetBitmapBits(hbm_color, bgra.len() as u32, bgra.as_ptr() as *const c_void) == 0 {
|
||||
DeleteObject(hbm_color);
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
};
|
||||
|
||||
// Mask created according to https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap#parameters
|
||||
let mask_bits: Vec<u8> = vec![0xff; ((((w + 15) >> 4) << 1) * h) as usize];
|
||||
let hbm_mask = CreateBitmap(w, h, 1, 1, mask_bits.as_ptr() as *const _);
|
||||
if hbm_mask.is_null() {
|
||||
DeleteObject(hbm_color);
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
|
||||
let icon_info = ICONINFO {
|
||||
fIcon: 0,
|
||||
xHotspot: image.hotspot_x() as u32,
|
||||
yHotspot: image.hotspot_y() as u32,
|
||||
hbmMask: hbm_mask,
|
||||
hbmColor: hbm_color,
|
||||
};
|
||||
|
||||
let handle = CreateIconIndirect(&icon_info as *const _);
|
||||
DeleteObject(hbm_color);
|
||||
DeleteObject(hbm_mask);
|
||||
if handle.is_null() {
|
||||
return Err(os_error!(io::Error::last_os_error()).into());
|
||||
}
|
||||
|
||||
Ok(Self(Arc::new(RaiiCursor { handle })))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Eq, PartialEq)]
|
||||
pub struct RaiiCursor {
|
||||
handle: HCURSOR,
|
||||
}
|
||||
|
||||
unsafe impl Send for RaiiCursor {}
|
||||
unsafe impl Sync for RaiiCursor {}
|
||||
|
||||
impl Drop for RaiiCursor {
|
||||
fn drop(&mut self) {
|
||||
unsafe { DestroyCursor(self.handle) };
|
||||
}
|
||||
}
|
||||
|
||||
impl RaiiCursor {
|
||||
pub fn as_raw_handle(&self) -> HICON {
|
||||
self.handle
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pixel {
|
||||
pub(crate) r: u8,
|
||||
pub(crate) g: u8,
|
||||
pub(crate) b: u8,
|
||||
pub(crate) a: u8,
|
||||
}
|
||||
161
winit-win32/src/ime.rs
Normal file
161
winit-win32/src/ime.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use std::ffi::{c_void, OsString};
|
||||
use std::os::windows::prelude::OsStringExt;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use dpi::{Position, Size};
|
||||
use windows_sys::Win32::Foundation::{POINT, RECT};
|
||||
use windows_sys::Win32::UI::Input::Ime::{
|
||||
ImmAssociateContextEx, ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext,
|
||||
ImmSetCandidateWindow, ImmSetCompositionWindow, ATTR_TARGET_CONVERTED,
|
||||
ATTR_TARGET_NOTCONVERTED, CANDIDATEFORM, CFS_EXCLUDE, CFS_POINT, COMPOSITIONFORM, GCS_COMPATTR,
|
||||
GCS_COMPSTR, GCS_CURSORPOS, GCS_RESULTSTR, HIMC, IACE_CHILDREN, IACE_DEFAULT,
|
||||
};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_IMMENABLED};
|
||||
|
||||
use crate::HWND;
|
||||
|
||||
pub struct ImeContext {
|
||||
hwnd: HWND,
|
||||
himc: HIMC,
|
||||
}
|
||||
|
||||
impl ImeContext {
|
||||
pub unsafe fn current(hwnd: HWND) -> Self {
|
||||
let himc = unsafe { ImmGetContext(hwnd) };
|
||||
ImeContext { hwnd, himc }
|
||||
}
|
||||
|
||||
pub unsafe fn get_composing_text_and_cursor(
|
||||
&self,
|
||||
) -> Option<(String, Option<usize>, Option<usize>)> {
|
||||
let text = unsafe { self.get_composition_string(GCS_COMPSTR) }?;
|
||||
let attrs = unsafe { self.get_composition_data(GCS_COMPATTR) }.unwrap_or_default();
|
||||
|
||||
let mut first = None;
|
||||
let mut last = None;
|
||||
let mut boundary_before_char = 0;
|
||||
let mut attr_idx = 0;
|
||||
|
||||
for chr in text.chars() {
|
||||
let Some(attr) = attrs.get(attr_idx).copied() else {
|
||||
break;
|
||||
};
|
||||
|
||||
let char_is_targeted =
|
||||
attr as u32 == ATTR_TARGET_CONVERTED || attr as u32 == ATTR_TARGET_NOTCONVERTED;
|
||||
|
||||
if first.is_none() && char_is_targeted {
|
||||
first = Some(boundary_before_char);
|
||||
} else if first.is_some() && last.is_none() && !char_is_targeted {
|
||||
last = Some(boundary_before_char);
|
||||
}
|
||||
|
||||
boundary_before_char += chr.len_utf8();
|
||||
attr_idx += chr.len_utf16();
|
||||
}
|
||||
|
||||
if first.is_some() && last.is_none() {
|
||||
last = Some(text.len());
|
||||
} else if first.is_none() {
|
||||
// IME haven't split words and select any clause yet, so trying to retrieve normal
|
||||
// cursor.
|
||||
let cursor = unsafe { self.get_composition_cursor(&text) };
|
||||
first = cursor;
|
||||
last = cursor;
|
||||
}
|
||||
|
||||
Some((text, first, last))
|
||||
}
|
||||
|
||||
pub unsafe fn get_composed_text(&self) -> Option<String> {
|
||||
unsafe { self.get_composition_string(GCS_RESULTSTR) }
|
||||
}
|
||||
|
||||
unsafe fn get_composition_cursor(&self, text: &str) -> Option<usize> {
|
||||
let cursor = unsafe { ImmGetCompositionStringW(self.himc, GCS_CURSORPOS, null_mut(), 0) };
|
||||
(cursor >= 0).then(|| text.chars().take(cursor as _).map(|c| c.len_utf8()).sum())
|
||||
}
|
||||
|
||||
unsafe fn get_composition_string(&self, gcs_mode: u32) -> Option<String> {
|
||||
let data = unsafe { self.get_composition_data(gcs_mode) }?;
|
||||
let (prefix, shorts, suffix) = unsafe { data.align_to::<u16>() };
|
||||
if prefix.is_empty() && suffix.is_empty() {
|
||||
OsString::from_wide(shorts).into_string().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_composition_data(&self, gcs_mode: u32) -> Option<Vec<u8>> {
|
||||
let size = match unsafe { ImmGetCompositionStringW(self.himc, gcs_mode, null_mut(), 0) } {
|
||||
0 => return Some(Vec::new()),
|
||||
size if size < 0 => return None,
|
||||
size => size,
|
||||
};
|
||||
|
||||
let mut buf = Vec::<u8>::with_capacity(size as _);
|
||||
let size = unsafe {
|
||||
ImmGetCompositionStringW(
|
||||
self.himc,
|
||||
gcs_mode,
|
||||
buf.as_mut_ptr() as *mut c_void,
|
||||
size as _,
|
||||
)
|
||||
};
|
||||
|
||||
if size < 0 {
|
||||
None
|
||||
} else {
|
||||
unsafe { buf.set_len(size as _) };
|
||||
Some(buf)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set_ime_cursor_area(&self, spot: Position, size: Size, scale_factor: f64) {
|
||||
if !unsafe { ImeContext::system_has_ime() } {
|
||||
return;
|
||||
}
|
||||
|
||||
let (x, y) = spot.to_physical::<i32>(scale_factor).into();
|
||||
let (width, height): (i32, i32) = size.to_physical::<i32>(scale_factor).into();
|
||||
let rc_area = RECT { left: x, top: y, right: x + width, bottom: y + height };
|
||||
let candidate_form = CANDIDATEFORM {
|
||||
dwIndex: 0,
|
||||
dwStyle: CFS_EXCLUDE,
|
||||
ptCurrentPos: POINT { x, y },
|
||||
rcArea: rc_area,
|
||||
};
|
||||
let composition_form = COMPOSITIONFORM {
|
||||
dwStyle: CFS_POINT,
|
||||
ptCurrentPos: POINT { x, y: y + height },
|
||||
rcArea: rc_area,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
ImmSetCompositionWindow(self.himc, &composition_form);
|
||||
ImmSetCandidateWindow(self.himc, &candidate_form);
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn set_ime_allowed(hwnd: HWND, allowed: bool) {
|
||||
if !unsafe { ImeContext::system_has_ime() } {
|
||||
return;
|
||||
}
|
||||
|
||||
if allowed {
|
||||
unsafe { ImmAssociateContextEx(hwnd, null_mut(), IACE_DEFAULT) };
|
||||
} else {
|
||||
unsafe { ImmAssociateContextEx(hwnd, null_mut(), IACE_CHILDREN) };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn system_has_ime() -> bool {
|
||||
unsafe { GetSystemMetrics(SM_IMMENABLED) != 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ImeContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ImmReleaseContext(self.hwnd, self.himc) };
|
||||
}
|
||||
}
|
||||
1268
winit-win32/src/keyboard.rs
Normal file
1268
winit-win32/src/keyboard.rs
Normal file
File diff suppressed because it is too large
Load diff
988
winit-win32/src/keyboard_layout.rs
Normal file
988
winit-win32/src/keyboard_layout.rs
Normal file
|
|
@ -0,0 +1,988 @@
|
|||
use std::collections::hash_map::Entry;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::ffi::OsString;
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use smol_str::SmolStr;
|
||||
use windows_sys::Win32::System::SystemServices::{LANG_JAPANESE, LANG_KOREAN};
|
||||
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
|
||||
GetKeyState, GetKeyboardLayout, MapVirtualKeyExW, ToUnicodeEx, HKL, MAPVK_VK_TO_VSC_EX,
|
||||
VIRTUAL_KEY, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_BACK, VK_BROWSER_BACK,
|
||||
VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME, VK_BROWSER_REFRESH,
|
||||
VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_CANCEL, VK_CAPITAL, VK_CLEAR, VK_CONTROL, VK_CONVERT,
|
||||
VK_CRSEL, VK_DECIMAL, VK_DELETE, VK_DIVIDE, VK_DOWN, VK_END, VK_EREOF, VK_ESCAPE, VK_EXECUTE,
|
||||
VK_EXSEL, VK_F1, VK_F10, VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18,
|
||||
VK_F19, VK_F2, VK_F20, VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7,
|
||||
VK_F8, VK_F9, VK_FINAL, VK_GAMEPAD_A, VK_GAMEPAD_B, VK_GAMEPAD_DPAD_DOWN, VK_GAMEPAD_DPAD_LEFT,
|
||||
VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP, VK_GAMEPAD_LEFT_SHOULDER,
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON, VK_GAMEPAD_LEFT_THUMBSTICK_DOWN,
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_LEFT, VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT,
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_UP, VK_GAMEPAD_LEFT_TRIGGER, VK_GAMEPAD_MENU,
|
||||
VK_GAMEPAD_RIGHT_SHOULDER, VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON,
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN, VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT,
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT, VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER,
|
||||
VK_GAMEPAD_VIEW, VK_GAMEPAD_X, VK_GAMEPAD_Y, VK_HANGUL, VK_HANJA, VK_HELP, VK_HOME, VK_ICO_00,
|
||||
VK_ICO_CLEAR, VK_ICO_HELP, VK_INSERT, VK_JUNJA, VK_KANA, VK_KANJI, VK_LAUNCH_APP1,
|
||||
VK_LAUNCH_APP2, VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LBUTTON, VK_LCONTROL, VK_LEFT,
|
||||
VK_LMENU, VK_LSHIFT, VK_LWIN, VK_MBUTTON, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE,
|
||||
VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MENU, VK_MODECHANGE, VK_MULTIPLY, VK_NAVIGATION_ACCEPT,
|
||||
VK_NAVIGATION_CANCEL, VK_NAVIGATION_DOWN, VK_NAVIGATION_LEFT, VK_NAVIGATION_MENU,
|
||||
VK_NAVIGATION_RIGHT, VK_NAVIGATION_UP, VK_NAVIGATION_VIEW, VK_NEXT, VK_NONAME, VK_NONCONVERT,
|
||||
VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3, VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6,
|
||||
VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_OEM_1, VK_OEM_102, VK_OEM_2, VK_OEM_3, VK_OEM_4,
|
||||
VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_ATTN, VK_OEM_AUTO, VK_OEM_AX, VK_OEM_BACKTAB,
|
||||
VK_OEM_CLEAR, VK_OEM_COMMA, VK_OEM_COPY, VK_OEM_CUSEL, VK_OEM_ENLW, VK_OEM_FINISH,
|
||||
VK_OEM_FJ_LOYA, VK_OEM_FJ_MASSHOU, VK_OEM_FJ_ROYA, VK_OEM_FJ_TOUROKU, VK_OEM_JUMP,
|
||||
VK_OEM_MINUS, VK_OEM_NEC_EQUAL, VK_OEM_PA1, VK_OEM_PA2, VK_OEM_PA3, VK_OEM_PERIOD, VK_OEM_PLUS,
|
||||
VK_OEM_RESET, VK_OEM_WSCTRL, VK_PA1, VK_PACKET, VK_PAUSE, VK_PLAY, VK_PRINT, VK_PRIOR,
|
||||
VK_PROCESSKEY, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT, VK_RMENU, VK_RSHIFT, VK_RWIN,
|
||||
VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT,
|
||||
VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM,
|
||||
};
|
||||
use winit_core::keyboard::{Key, KeyCode, ModifiersState, NamedKey, NativeKey, PhysicalKey};
|
||||
|
||||
use crate::keyboard::scancode_to_physicalkey;
|
||||
use crate::util::{loword, primarylangid};
|
||||
|
||||
pub(crate) static LAYOUT_CACHE: LazyLock<Mutex<LayoutCache>> =
|
||||
LazyLock::new(|| Mutex::new(LayoutCache::default()));
|
||||
|
||||
fn key_pressed(vkey: VIRTUAL_KEY) -> bool {
|
||||
unsafe { (GetKeyState(vkey as i32) & (1 << 15)) == (1 << 15) }
|
||||
}
|
||||
|
||||
const NUMPAD_VKEYS: [VIRTUAL_KEY; 16] = [
|
||||
VK_NUMPAD0,
|
||||
VK_NUMPAD1,
|
||||
VK_NUMPAD2,
|
||||
VK_NUMPAD3,
|
||||
VK_NUMPAD4,
|
||||
VK_NUMPAD5,
|
||||
VK_NUMPAD6,
|
||||
VK_NUMPAD7,
|
||||
VK_NUMPAD8,
|
||||
VK_NUMPAD9,
|
||||
VK_MULTIPLY,
|
||||
VK_ADD,
|
||||
VK_SEPARATOR,
|
||||
VK_SUBTRACT,
|
||||
VK_DECIMAL,
|
||||
VK_DIVIDE,
|
||||
];
|
||||
|
||||
static NUMPAD_KEYCODES: LazyLock<HashSet<KeyCode>> = LazyLock::new(|| {
|
||||
let mut keycodes = HashSet::new();
|
||||
keycodes.insert(KeyCode::Numpad0);
|
||||
keycodes.insert(KeyCode::Numpad1);
|
||||
keycodes.insert(KeyCode::Numpad2);
|
||||
keycodes.insert(KeyCode::Numpad3);
|
||||
keycodes.insert(KeyCode::Numpad4);
|
||||
keycodes.insert(KeyCode::Numpad5);
|
||||
keycodes.insert(KeyCode::Numpad6);
|
||||
keycodes.insert(KeyCode::Numpad7);
|
||||
keycodes.insert(KeyCode::Numpad8);
|
||||
keycodes.insert(KeyCode::Numpad9);
|
||||
keycodes.insert(KeyCode::NumpadMultiply);
|
||||
keycodes.insert(KeyCode::NumpadAdd);
|
||||
keycodes.insert(KeyCode::NumpadComma);
|
||||
keycodes.insert(KeyCode::NumpadSubtract);
|
||||
keycodes.insert(KeyCode::NumpadDecimal);
|
||||
keycodes.insert(KeyCode::NumpadDivide);
|
||||
keycodes
|
||||
});
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct WindowsModifiers : u8 {
|
||||
const SHIFT = 1 << 0;
|
||||
const CONTROL = 1 << 1;
|
||||
const ALT = 1 << 2;
|
||||
const CAPS_LOCK = 1 << 3;
|
||||
const FLAGS_END = 1 << 4;
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsModifiers {
|
||||
pub fn active_modifiers(key_state: &[u8; 256]) -> WindowsModifiers {
|
||||
let shift = key_state[VK_SHIFT as usize] & 0x80 != 0;
|
||||
let lshift = key_state[VK_LSHIFT as usize] & 0x80 != 0;
|
||||
let rshift = key_state[VK_RSHIFT as usize] & 0x80 != 0;
|
||||
|
||||
let control = key_state[VK_CONTROL as usize] & 0x80 != 0;
|
||||
let lcontrol = key_state[VK_LCONTROL as usize] & 0x80 != 0;
|
||||
let rcontrol = key_state[VK_RCONTROL as usize] & 0x80 != 0;
|
||||
|
||||
let alt = key_state[VK_MENU as usize] & 0x80 != 0;
|
||||
let lalt = key_state[VK_LMENU as usize] & 0x80 != 0;
|
||||
let ralt = key_state[VK_RMENU as usize] & 0x80 != 0;
|
||||
|
||||
let caps = key_state[VK_CAPITAL as usize] & 0x01 != 0;
|
||||
|
||||
let mut result = WindowsModifiers::empty();
|
||||
if shift || lshift || rshift {
|
||||
result.insert(WindowsModifiers::SHIFT);
|
||||
}
|
||||
if control || lcontrol || rcontrol {
|
||||
result.insert(WindowsModifiers::CONTROL);
|
||||
}
|
||||
if alt || lalt || ralt {
|
||||
result.insert(WindowsModifiers::ALT);
|
||||
}
|
||||
if caps {
|
||||
result.insert(WindowsModifiers::CAPS_LOCK);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn apply_to_kbd_state(self, key_state: &mut [u8; 256]) {
|
||||
if self.intersects(Self::SHIFT) {
|
||||
key_state[VK_SHIFT as usize] |= 0x80;
|
||||
} else {
|
||||
key_state[VK_SHIFT as usize] &= !0x80;
|
||||
key_state[VK_LSHIFT as usize] &= !0x80;
|
||||
key_state[VK_RSHIFT as usize] &= !0x80;
|
||||
}
|
||||
if self.intersects(Self::CONTROL) {
|
||||
key_state[VK_CONTROL as usize] |= 0x80;
|
||||
} else {
|
||||
key_state[VK_CONTROL as usize] &= !0x80;
|
||||
key_state[VK_LCONTROL as usize] &= !0x80;
|
||||
key_state[VK_RCONTROL as usize] &= !0x80;
|
||||
}
|
||||
if self.intersects(Self::ALT) {
|
||||
key_state[VK_MENU as usize] |= 0x80;
|
||||
} else {
|
||||
key_state[VK_MENU as usize] &= !0x80;
|
||||
key_state[VK_LMENU as usize] &= !0x80;
|
||||
key_state[VK_RMENU as usize] &= !0x80;
|
||||
}
|
||||
if self.intersects(Self::CAPS_LOCK) {
|
||||
key_state[VK_CAPITAL as usize] |= 0x01;
|
||||
} else {
|
||||
key_state[VK_CAPITAL as usize] &= !0x01;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the control modifier if the alt modifier is not present.
|
||||
/// This is useful because on Windows: (Control + Alt) == AltGr
|
||||
/// but we don't want to interfere with the AltGr state.
|
||||
pub fn remove_only_ctrl(mut self) -> WindowsModifiers {
|
||||
if !self.contains(WindowsModifiers::ALT) {
|
||||
self.remove(WindowsModifiers::CONTROL);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Layout {
|
||||
pub hkl: u64,
|
||||
|
||||
/// Maps numpad keys from Windows virtual key to a `Key`.
|
||||
///
|
||||
/// This is useful because some numpad keys generate different characters based on the locale.
|
||||
/// For example `VK_DECIMAL` is sometimes "." and sometimes ",". Note: numpad-specific virtual
|
||||
/// keys are only produced by Windows when the NumLock is active.
|
||||
///
|
||||
/// Making this field separate from the `keys` field saves having to add NumLock as a modifier
|
||||
/// to `WindowsModifiers`, which would double the number of items in keys.
|
||||
pub numlock_on_keys: HashMap<VIRTUAL_KEY, Key>,
|
||||
/// Like `numlock_on_keys` but this will map to the key that would be produced if numlock was
|
||||
/// off. The keys of this map are identical to the keys of `numlock_on_keys`.
|
||||
pub numlock_off_keys: HashMap<VIRTUAL_KEY, Key>,
|
||||
|
||||
/// Maps a modifier state to group of key strings
|
||||
/// We're not using `ModifiersState` here because that object cannot express caps lock,
|
||||
/// but we need to handle caps lock too.
|
||||
///
|
||||
/// This map shouldn't need to exist.
|
||||
/// However currently this seems to be the only good way
|
||||
/// of getting the label for the pressed key. Note that calling `ToUnicode`
|
||||
/// just when the key is pressed/released would be enough if `ToUnicode` wouldn't
|
||||
/// change the keyboard state (it clears the dead key). There is a flag to prevent
|
||||
/// changing the state, but that flag requires Windows 10, version 1607 or newer)
|
||||
pub keys: HashMap<WindowsModifiers, HashMap<KeyCode, Key>>,
|
||||
pub has_alt_graph: bool,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn get_key(
|
||||
&self,
|
||||
mods: WindowsModifiers,
|
||||
num_lock_on: bool,
|
||||
vkey: VIRTUAL_KEY,
|
||||
physical_key: &PhysicalKey,
|
||||
) -> Key {
|
||||
let native_code = NativeKey::Windows(vkey);
|
||||
|
||||
let unknown_alt = vkey == VK_MENU;
|
||||
if !unknown_alt {
|
||||
// Here we try using the virtual key directly but if the virtual key doesn't distinguish
|
||||
// between left and right alt, we can't report AltGr. Therefore, we only do this if the
|
||||
// key is not the "unknown alt" key.
|
||||
//
|
||||
// The reason for using the virtual key directly is that `MapVirtualKeyExW` (used when
|
||||
// building the keys map) sometimes maps virtual keys to odd scancodes that don't match
|
||||
// the scancode coming from the KEYDOWN message for the same key. For example: `VK_LEFT`
|
||||
// is mapped to `0x004B`, but the scancode for the left arrow is `0xE04B`.
|
||||
let key_from_vkey =
|
||||
vkey_to_non_char_key(vkey, native_code.clone(), self.hkl, self.has_alt_graph);
|
||||
|
||||
if !matches!(key_from_vkey, Key::Unidentified(_)) {
|
||||
return key_from_vkey;
|
||||
}
|
||||
}
|
||||
if num_lock_on {
|
||||
if let Some(key) = self.numlock_on_keys.get(&vkey) {
|
||||
return key.clone();
|
||||
}
|
||||
} else if let Some(key) = self.numlock_off_keys.get(&vkey) {
|
||||
return key.clone();
|
||||
}
|
||||
if let PhysicalKey::Code(code) = physical_key {
|
||||
if let Some(keys) = self.keys.get(&mods) {
|
||||
if let Some(key) = keys.get(code) {
|
||||
return key.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
Key::Unidentified(native_code)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct LayoutCache {
|
||||
/// Maps locale identifiers (HKL) to layouts
|
||||
pub layouts: HashMap<u64, Layout>,
|
||||
}
|
||||
|
||||
impl LayoutCache {
|
||||
/// Checks whether the current layout is already known and
|
||||
/// prepares the layout if it isn't known.
|
||||
/// The current layout is then returned.
|
||||
pub fn get_current_layout(&mut self) -> (u64, &Layout) {
|
||||
let locale_id = unsafe { GetKeyboardLayout(0) } as u64;
|
||||
match self.layouts.entry(locale_id) {
|
||||
Entry::Occupied(entry) => (locale_id, entry.into_mut()),
|
||||
Entry::Vacant(entry) => {
|
||||
let layout = Self::prepare_layout(locale_id);
|
||||
(locale_id, entry.insert(layout))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_agnostic_mods(&mut self) -> ModifiersState {
|
||||
let (_, layout) = self.get_current_layout();
|
||||
let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU);
|
||||
let mut mods = ModifiersState::empty();
|
||||
mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT));
|
||||
mods.set(ModifiersState::CONTROL, key_pressed(VK_CONTROL) && !filter_out_altgr);
|
||||
mods.set(ModifiersState::ALT, key_pressed(VK_MENU) && !filter_out_altgr);
|
||||
mods.set(ModifiersState::META, key_pressed(VK_LWIN) || key_pressed(VK_RWIN));
|
||||
mods
|
||||
}
|
||||
|
||||
fn prepare_layout(locale_id: u64) -> Layout {
|
||||
let mut layout = Layout {
|
||||
hkl: locale_id,
|
||||
numlock_on_keys: Default::default(),
|
||||
numlock_off_keys: Default::default(),
|
||||
keys: Default::default(),
|
||||
has_alt_graph: false,
|
||||
};
|
||||
|
||||
// We initialize the keyboard state with all zeros to
|
||||
// simulate a scenario when no modifier is active.
|
||||
let mut key_state = [0u8; 256];
|
||||
|
||||
// `MapVirtualKeyExW` maps (non-numpad-specific) virtual keys to scancodes as if numlock
|
||||
// was off. We rely on this behavior to find all virtual keys which are not numpad-specific
|
||||
// but map to the numpad.
|
||||
//
|
||||
// src_vkey: VK ==> scancode: u16 (on the numpad)
|
||||
//
|
||||
// Then we convert the source virtual key into a `Key` and the scancode into a virtual key
|
||||
// to get the reverse mapping.
|
||||
//
|
||||
// src_vkey: VK ==> scancode: u16 (on the numpad)
|
||||
// || ||
|
||||
// \/ \/
|
||||
// map_value: Key <- map_vkey: VK
|
||||
layout.numlock_off_keys.reserve(NUMPAD_KEYCODES.len());
|
||||
for vk in 0..256 {
|
||||
let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) };
|
||||
if scancode == 0 {
|
||||
continue;
|
||||
}
|
||||
let keycode = match scancode_to_physicalkey(scancode) {
|
||||
PhysicalKey::Code(code) => code,
|
||||
// TODO: validate that we can skip on unidentified keys (probably never occurs?)
|
||||
_ => continue,
|
||||
};
|
||||
if !is_numpad_specific(vk as VIRTUAL_KEY) && NUMPAD_KEYCODES.contains(&keycode) {
|
||||
let native_code = NativeKey::Windows(vk as VIRTUAL_KEY);
|
||||
let map_vkey = keycode_to_vkey(keycode, locale_id);
|
||||
if map_vkey == 0 {
|
||||
continue;
|
||||
}
|
||||
let map_value =
|
||||
vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false);
|
||||
if matches!(map_value, Key::Unidentified(_)) {
|
||||
continue;
|
||||
}
|
||||
layout.numlock_off_keys.insert(map_vkey, map_value);
|
||||
}
|
||||
}
|
||||
|
||||
layout.numlock_on_keys.reserve(NUMPAD_VKEYS.len());
|
||||
for vk in NUMPAD_VKEYS.iter() {
|
||||
let vk = (*vk) as u32;
|
||||
let scancode = unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) };
|
||||
let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id);
|
||||
if let ToUnicodeResult::Str(s) = unicode {
|
||||
layout.numlock_on_keys.insert(vk as VIRTUAL_KEY, Key::Character(SmolStr::new(s)));
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through every combination of modifiers
|
||||
let mods_end = WindowsModifiers::FLAGS_END.bits();
|
||||
for mod_state in 0..mods_end {
|
||||
let mut keys_for_this_mod = HashMap::with_capacity(256);
|
||||
|
||||
let mod_state = WindowsModifiers::from_bits_retain(mod_state);
|
||||
mod_state.apply_to_kbd_state(&mut key_state);
|
||||
|
||||
// Virtual key values are in the domain [0, 255].
|
||||
// This is reinforced by the fact that the keyboard state array has 256
|
||||
// elements. This array is allowed to be indexed by virtual key values
|
||||
// giving the key state for the virtual key used for indexing.
|
||||
for vk in 0..256 {
|
||||
let scancode =
|
||||
unsafe { MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC_EX, locale_id as HKL) };
|
||||
if scancode == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let native_code = NativeKey::Windows(vk as VIRTUAL_KEY);
|
||||
let key_code = match scancode_to_physicalkey(scancode) {
|
||||
PhysicalKey::Code(code) => code,
|
||||
// TODO: validate that we can skip on unidentified keys (probably never occurs?)
|
||||
_ => continue,
|
||||
};
|
||||
// Let's try to get the key from just the scancode and vk
|
||||
// We don't necessarily know yet if AltGraph is present on this layout so we'll
|
||||
// assume it isn't. Then we'll do a second pass where we set the "AltRight" keys to
|
||||
// "AltGr" in case we find out that there's an AltGraph.
|
||||
let preliminary_key =
|
||||
vkey_to_non_char_key(vk as VIRTUAL_KEY, native_code, locale_id, false);
|
||||
match preliminary_key {
|
||||
Key::Unidentified(_) => (),
|
||||
_ => {
|
||||
keys_for_this_mod.insert(key_code, preliminary_key);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
|
||||
let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id);
|
||||
let key = match unicode {
|
||||
ToUnicodeResult::Str(str) => Key::Character(SmolStr::new(str)),
|
||||
ToUnicodeResult::Dead(dead_char) => {
|
||||
// println!("{:?} - {:?} produced dead {:?}", key_code, mod_state,
|
||||
// dead_char);
|
||||
Key::Dead(dead_char)
|
||||
},
|
||||
ToUnicodeResult::None => {
|
||||
let has_alt = mod_state.contains(WindowsModifiers::ALT);
|
||||
let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL);
|
||||
// HACK: `ToUnicodeEx` seems to fail getting the string for the numpad
|
||||
// divide key, so we handle that explicitly here
|
||||
if !has_alt && !has_ctrl && key_code == KeyCode::NumpadDivide {
|
||||
Key::Character(SmolStr::new("/"))
|
||||
} else {
|
||||
// Just use the unidentified key, we got earlier
|
||||
preliminary_key
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Check for alt graph.
|
||||
// The logic is that if a key pressed with no modifier produces
|
||||
// a different `Character` from when it's pressed with CTRL+ALT then the layout
|
||||
// has AltGr.
|
||||
let ctrl_alt: WindowsModifiers = WindowsModifiers::CONTROL | WindowsModifiers::ALT;
|
||||
let is_in_ctrl_alt = mod_state == ctrl_alt;
|
||||
if !layout.has_alt_graph && is_in_ctrl_alt {
|
||||
// Unwrapping here because if we are in the ctrl+alt modifier state
|
||||
// then the alt modifier state must have come before.
|
||||
let simple_keys = layout.keys.get(&WindowsModifiers::empty()).unwrap();
|
||||
if let Some(Key::Character(key_no_altgr)) = simple_keys.get(&key_code) {
|
||||
if let Key::Character(key) = &key {
|
||||
layout.has_alt_graph = key != key_no_altgr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keys_for_this_mod.insert(key_code, key);
|
||||
}
|
||||
layout.keys.insert(mod_state, keys_for_this_mod);
|
||||
}
|
||||
|
||||
// Second pass: replace right alt keys with AltGr if the layout has alt graph
|
||||
if layout.has_alt_graph {
|
||||
for mod_state in 0..mods_end {
|
||||
let mod_state = WindowsModifiers::from_bits_retain(mod_state);
|
||||
if let Some(keys) = layout.keys.get_mut(&mod_state) {
|
||||
if let Some(key) = keys.get_mut(&KeyCode::AltRight) {
|
||||
*key = Key::Named(NamedKey::AltGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layout
|
||||
}
|
||||
|
||||
fn to_unicode_string(
|
||||
key_state: &[u8; 256],
|
||||
vkey: u32,
|
||||
scancode: u32,
|
||||
locale_id: u64,
|
||||
) -> ToUnicodeResult {
|
||||
unsafe {
|
||||
let mut label_wide = [0u16; 8];
|
||||
let mut wide_len = ToUnicodeEx(
|
||||
vkey,
|
||||
scancode,
|
||||
(&key_state[0]) as *const _,
|
||||
(&mut label_wide[0]) as *mut _,
|
||||
label_wide.len() as i32,
|
||||
0,
|
||||
locale_id as HKL,
|
||||
);
|
||||
if wide_len < 0 {
|
||||
// If it's dead, we run `ToUnicode` again to consume the dead-key
|
||||
wide_len = ToUnicodeEx(
|
||||
vkey,
|
||||
scancode,
|
||||
(&key_state[0]) as *const _,
|
||||
(&mut label_wide[0]) as *mut _,
|
||||
label_wide.len() as i32,
|
||||
0,
|
||||
locale_id as HKL,
|
||||
);
|
||||
if wide_len > 0 {
|
||||
let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]);
|
||||
if let Ok(label_str) = os_string.into_string() {
|
||||
if let Some(ch) = label_str.chars().next() {
|
||||
return ToUnicodeResult::Dead(Some(ch));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ToUnicodeResult::Dead(None);
|
||||
}
|
||||
if wide_len > 0 {
|
||||
let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]);
|
||||
if let Ok(label_str) = os_string.into_string() {
|
||||
return ToUnicodeResult::Str(label_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
ToUnicodeResult::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
enum ToUnicodeResult {
|
||||
Str(String),
|
||||
Dead(Option<char>),
|
||||
None,
|
||||
}
|
||||
|
||||
fn is_numpad_specific(vk: VIRTUAL_KEY) -> bool {
|
||||
matches!(
|
||||
vk,
|
||||
VK_NUMPAD0
|
||||
| VK_NUMPAD1
|
||||
| VK_NUMPAD2
|
||||
| VK_NUMPAD3
|
||||
| VK_NUMPAD4
|
||||
| VK_NUMPAD5
|
||||
| VK_NUMPAD6
|
||||
| VK_NUMPAD7
|
||||
| VK_NUMPAD8
|
||||
| VK_NUMPAD9
|
||||
| VK_ADD
|
||||
| VK_SUBTRACT
|
||||
| VK_DIVIDE
|
||||
| VK_DECIMAL
|
||||
| VK_SEPARATOR
|
||||
)
|
||||
}
|
||||
|
||||
fn keycode_to_vkey(keycode: KeyCode, hkl: u64) -> VIRTUAL_KEY {
|
||||
let primary_lang_id = primarylangid(loword(hkl as u32));
|
||||
let is_korean = primary_lang_id as u32 == LANG_KOREAN;
|
||||
let is_japanese = primary_lang_id as u32 == LANG_JAPANESE;
|
||||
|
||||
match keycode {
|
||||
KeyCode::Backquote => 0,
|
||||
KeyCode::Backslash => 0,
|
||||
KeyCode::BracketLeft => 0,
|
||||
KeyCode::BracketRight => 0,
|
||||
KeyCode::Comma => 0,
|
||||
KeyCode::Digit0 => 0,
|
||||
KeyCode::Digit1 => 0,
|
||||
KeyCode::Digit2 => 0,
|
||||
KeyCode::Digit3 => 0,
|
||||
KeyCode::Digit4 => 0,
|
||||
KeyCode::Digit5 => 0,
|
||||
KeyCode::Digit6 => 0,
|
||||
KeyCode::Digit7 => 0,
|
||||
KeyCode::Digit8 => 0,
|
||||
KeyCode::Digit9 => 0,
|
||||
KeyCode::Equal => 0,
|
||||
KeyCode::IntlBackslash => 0,
|
||||
KeyCode::IntlRo => 0,
|
||||
KeyCode::IntlYen => 0,
|
||||
KeyCode::KeyA => 0,
|
||||
KeyCode::KeyB => 0,
|
||||
KeyCode::KeyC => 0,
|
||||
KeyCode::KeyD => 0,
|
||||
KeyCode::KeyE => 0,
|
||||
KeyCode::KeyF => 0,
|
||||
KeyCode::KeyG => 0,
|
||||
KeyCode::KeyH => 0,
|
||||
KeyCode::KeyI => 0,
|
||||
KeyCode::KeyJ => 0,
|
||||
KeyCode::KeyK => 0,
|
||||
KeyCode::KeyL => 0,
|
||||
KeyCode::KeyM => 0,
|
||||
KeyCode::KeyN => 0,
|
||||
KeyCode::KeyO => 0,
|
||||
KeyCode::KeyP => 0,
|
||||
KeyCode::KeyQ => 0,
|
||||
KeyCode::KeyR => 0,
|
||||
KeyCode::KeyS => 0,
|
||||
KeyCode::KeyT => 0,
|
||||
KeyCode::KeyU => 0,
|
||||
KeyCode::KeyV => 0,
|
||||
KeyCode::KeyW => 0,
|
||||
KeyCode::KeyX => 0,
|
||||
KeyCode::KeyY => 0,
|
||||
KeyCode::KeyZ => 0,
|
||||
KeyCode::Minus => 0,
|
||||
KeyCode::Period => 0,
|
||||
KeyCode::Quote => 0,
|
||||
KeyCode::Semicolon => 0,
|
||||
KeyCode::Slash => 0,
|
||||
KeyCode::AltLeft => VK_LMENU,
|
||||
KeyCode::AltRight => VK_RMENU,
|
||||
KeyCode::Backspace => VK_BACK,
|
||||
KeyCode::CapsLock => VK_CAPITAL,
|
||||
KeyCode::ContextMenu => VK_APPS,
|
||||
KeyCode::ControlLeft => VK_LCONTROL,
|
||||
KeyCode::ControlRight => VK_RCONTROL,
|
||||
KeyCode::Enter => VK_RETURN,
|
||||
KeyCode::MetaLeft => VK_LWIN,
|
||||
KeyCode::MetaRight => VK_RWIN,
|
||||
KeyCode::ShiftLeft => VK_RSHIFT,
|
||||
KeyCode::ShiftRight => VK_LSHIFT,
|
||||
KeyCode::Space => VK_SPACE,
|
||||
KeyCode::Tab => VK_TAB,
|
||||
KeyCode::Convert => VK_CONVERT,
|
||||
KeyCode::KanaMode => VK_KANA,
|
||||
KeyCode::Lang1 if is_korean => VK_HANGUL,
|
||||
KeyCode::Lang1 if is_japanese => VK_KANA,
|
||||
KeyCode::Lang2 if is_korean => VK_HANJA,
|
||||
KeyCode::Lang2 if is_japanese => 0,
|
||||
KeyCode::Lang3 if is_japanese => VK_OEM_FINISH,
|
||||
KeyCode::Lang4 if is_japanese => 0,
|
||||
KeyCode::Lang5 if is_japanese => 0,
|
||||
KeyCode::NonConvert => VK_NONCONVERT,
|
||||
KeyCode::Delete => VK_DELETE,
|
||||
KeyCode::End => VK_END,
|
||||
KeyCode::Help => VK_HELP,
|
||||
KeyCode::Home => VK_HOME,
|
||||
KeyCode::Insert => VK_INSERT,
|
||||
KeyCode::PageDown => VK_NEXT,
|
||||
KeyCode::PageUp => VK_PRIOR,
|
||||
KeyCode::ArrowDown => VK_DOWN,
|
||||
KeyCode::ArrowLeft => VK_LEFT,
|
||||
KeyCode::ArrowRight => VK_RIGHT,
|
||||
KeyCode::ArrowUp => VK_UP,
|
||||
KeyCode::NumLock => VK_NUMLOCK,
|
||||
KeyCode::Numpad0 => VK_NUMPAD0,
|
||||
KeyCode::Numpad1 => VK_NUMPAD1,
|
||||
KeyCode::Numpad2 => VK_NUMPAD2,
|
||||
KeyCode::Numpad3 => VK_NUMPAD3,
|
||||
KeyCode::Numpad4 => VK_NUMPAD4,
|
||||
KeyCode::Numpad5 => VK_NUMPAD5,
|
||||
KeyCode::Numpad6 => VK_NUMPAD6,
|
||||
KeyCode::Numpad7 => VK_NUMPAD7,
|
||||
KeyCode::Numpad8 => VK_NUMPAD8,
|
||||
KeyCode::Numpad9 => VK_NUMPAD9,
|
||||
KeyCode::NumpadAdd => VK_ADD,
|
||||
KeyCode::NumpadBackspace => VK_BACK,
|
||||
KeyCode::NumpadClear => VK_CLEAR,
|
||||
KeyCode::NumpadClearEntry => 0,
|
||||
KeyCode::NumpadComma => VK_SEPARATOR,
|
||||
KeyCode::NumpadDecimal => VK_DECIMAL,
|
||||
KeyCode::NumpadDivide => VK_DIVIDE,
|
||||
KeyCode::NumpadEnter => VK_RETURN,
|
||||
KeyCode::NumpadEqual => 0,
|
||||
KeyCode::NumpadHash => 0,
|
||||
KeyCode::NumpadMemoryAdd => 0,
|
||||
KeyCode::NumpadMemoryClear => 0,
|
||||
KeyCode::NumpadMemoryRecall => 0,
|
||||
KeyCode::NumpadMemoryStore => 0,
|
||||
KeyCode::NumpadMemorySubtract => 0,
|
||||
KeyCode::NumpadMultiply => VK_MULTIPLY,
|
||||
KeyCode::NumpadParenLeft => 0,
|
||||
KeyCode::NumpadParenRight => 0,
|
||||
KeyCode::NumpadStar => 0,
|
||||
KeyCode::NumpadSubtract => VK_SUBTRACT,
|
||||
KeyCode::Escape => VK_ESCAPE,
|
||||
KeyCode::Fn => 0,
|
||||
KeyCode::FnLock => 0,
|
||||
KeyCode::PrintScreen => VK_SNAPSHOT,
|
||||
KeyCode::ScrollLock => VK_SCROLL,
|
||||
KeyCode::Pause => VK_PAUSE,
|
||||
KeyCode::BrowserBack => VK_BROWSER_BACK,
|
||||
KeyCode::BrowserFavorites => VK_BROWSER_FAVORITES,
|
||||
KeyCode::BrowserForward => VK_BROWSER_FORWARD,
|
||||
KeyCode::BrowserHome => VK_BROWSER_HOME,
|
||||
KeyCode::BrowserRefresh => VK_BROWSER_REFRESH,
|
||||
KeyCode::BrowserSearch => VK_BROWSER_SEARCH,
|
||||
KeyCode::BrowserStop => VK_BROWSER_STOP,
|
||||
KeyCode::Eject => 0,
|
||||
KeyCode::LaunchApp1 => VK_LAUNCH_APP1,
|
||||
KeyCode::LaunchApp2 => VK_LAUNCH_APP2,
|
||||
KeyCode::LaunchMail => VK_LAUNCH_MAIL,
|
||||
KeyCode::MediaPlayPause => VK_MEDIA_PLAY_PAUSE,
|
||||
KeyCode::MediaSelect => VK_LAUNCH_MEDIA_SELECT,
|
||||
KeyCode::MediaStop => VK_MEDIA_STOP,
|
||||
KeyCode::MediaTrackNext => VK_MEDIA_NEXT_TRACK,
|
||||
KeyCode::MediaTrackPrevious => VK_MEDIA_PREV_TRACK,
|
||||
KeyCode::Power => 0,
|
||||
KeyCode::Sleep => 0,
|
||||
KeyCode::AudioVolumeDown => VK_VOLUME_DOWN,
|
||||
KeyCode::AudioVolumeMute => VK_VOLUME_MUTE,
|
||||
KeyCode::AudioVolumeUp => VK_VOLUME_UP,
|
||||
KeyCode::WakeUp => 0,
|
||||
#[allow(deprecated)]
|
||||
KeyCode::Hyper => 0,
|
||||
#[allow(deprecated)]
|
||||
KeyCode::Turbo => 0,
|
||||
KeyCode::Abort => 0,
|
||||
KeyCode::Resume => 0,
|
||||
KeyCode::Suspend => 0,
|
||||
KeyCode::Again => 0,
|
||||
KeyCode::Copy => 0,
|
||||
KeyCode::Cut => 0,
|
||||
KeyCode::Find => 0,
|
||||
KeyCode::Open => 0,
|
||||
KeyCode::Paste => 0,
|
||||
KeyCode::Props => 0,
|
||||
KeyCode::Select => VK_SELECT,
|
||||
KeyCode::Undo => 0,
|
||||
KeyCode::Hiragana => 0,
|
||||
KeyCode::Katakana => 0,
|
||||
KeyCode::F1 => VK_F1,
|
||||
KeyCode::F2 => VK_F2,
|
||||
KeyCode::F3 => VK_F3,
|
||||
KeyCode::F4 => VK_F4,
|
||||
KeyCode::F5 => VK_F5,
|
||||
KeyCode::F6 => VK_F6,
|
||||
KeyCode::F7 => VK_F7,
|
||||
KeyCode::F8 => VK_F8,
|
||||
KeyCode::F9 => VK_F9,
|
||||
KeyCode::F10 => VK_F10,
|
||||
KeyCode::F11 => VK_F11,
|
||||
KeyCode::F12 => VK_F12,
|
||||
KeyCode::F13 => VK_F13,
|
||||
KeyCode::F14 => VK_F14,
|
||||
KeyCode::F15 => VK_F15,
|
||||
KeyCode::F16 => VK_F16,
|
||||
KeyCode::F17 => VK_F17,
|
||||
KeyCode::F18 => VK_F18,
|
||||
KeyCode::F19 => VK_F19,
|
||||
KeyCode::F20 => VK_F20,
|
||||
KeyCode::F21 => VK_F21,
|
||||
KeyCode::F22 => VK_F22,
|
||||
KeyCode::F23 => VK_F23,
|
||||
KeyCode::F24 => VK_F24,
|
||||
KeyCode::F25 => 0,
|
||||
KeyCode::F26 => 0,
|
||||
KeyCode::F27 => 0,
|
||||
KeyCode::F28 => 0,
|
||||
KeyCode::F29 => 0,
|
||||
KeyCode::F30 => 0,
|
||||
KeyCode::F31 => 0,
|
||||
KeyCode::F32 => 0,
|
||||
KeyCode::F33 => 0,
|
||||
KeyCode::F34 => 0,
|
||||
KeyCode::F35 => 0,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// This converts virtual keys to `Key`s. Only virtual keys which can be unambiguously converted to
|
||||
/// a `Key`, with only the information passed in as arguments, are converted.
|
||||
///
|
||||
/// In other words: this function does not need to "prepare" the current layout in order to do
|
||||
/// the conversion, but as such it cannot convert certain keys, like language-specific character
|
||||
/// keys.
|
||||
///
|
||||
/// The result includes all non-character keys defined within `Key` plus characters from numpad
|
||||
/// keys. For example, backspace and tab are included.
|
||||
fn vkey_to_non_char_key(
|
||||
vkey: VIRTUAL_KEY,
|
||||
native_code: NativeKey,
|
||||
hkl: u64,
|
||||
has_alt_graph: bool,
|
||||
) -> Key {
|
||||
// List of the Web key names and their corresponding platform-native key names:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||
|
||||
let primary_lang_id = primarylangid(loword(hkl as u32));
|
||||
let is_korean = primary_lang_id as u32 == LANG_KOREAN;
|
||||
let is_japanese = primary_lang_id as u32 == LANG_JAPANESE;
|
||||
|
||||
match vkey {
|
||||
VK_LBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse
|
||||
VK_RBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse
|
||||
|
||||
// I don't think this can be represented with a Key
|
||||
VK_CANCEL => Key::Unidentified(native_code),
|
||||
|
||||
VK_MBUTTON => Key::Unidentified(NativeKey::Unidentified), // Mouse
|
||||
VK_XBUTTON1 => Key::Unidentified(NativeKey::Unidentified), // Mouse
|
||||
VK_XBUTTON2 => Key::Unidentified(NativeKey::Unidentified), // Mouse
|
||||
VK_BACK => Key::Named(NamedKey::Backspace),
|
||||
VK_TAB => Key::Named(NamedKey::Tab),
|
||||
VK_CLEAR => Key::Named(NamedKey::Clear),
|
||||
VK_RETURN => Key::Named(NamedKey::Enter),
|
||||
VK_SHIFT => Key::Named(NamedKey::Shift),
|
||||
VK_CONTROL => Key::Named(NamedKey::Control),
|
||||
VK_MENU => Key::Named(NamedKey::Alt),
|
||||
VK_PAUSE => Key::Named(NamedKey::Pause),
|
||||
VK_CAPITAL => Key::Named(NamedKey::CapsLock),
|
||||
|
||||
// VK_HANGEUL => Key::Named(NamedKey::HangulMode), // Deprecated in favour of VK_HANGUL
|
||||
|
||||
// VK_HANGUL and VK_KANA are defined as the same constant, therefore
|
||||
// we use appropriate conditions to differentiate between them
|
||||
VK_HANGUL if is_korean => Key::Named(NamedKey::HangulMode),
|
||||
VK_KANA if is_japanese => Key::Named(NamedKey::KanaMode),
|
||||
|
||||
VK_JUNJA => Key::Named(NamedKey::JunjaMode),
|
||||
VK_FINAL => Key::Named(NamedKey::FinalMode),
|
||||
|
||||
// VK_HANJA and VK_KANJI are defined as the same constant, therefore
|
||||
// we use appropriate conditions to differentiate between them
|
||||
VK_HANJA if is_korean => Key::Named(NamedKey::HanjaMode),
|
||||
VK_KANJI if is_japanese => Key::Named(NamedKey::KanjiMode),
|
||||
|
||||
VK_ESCAPE => Key::Named(NamedKey::Escape),
|
||||
VK_CONVERT => Key::Named(NamedKey::Convert),
|
||||
VK_NONCONVERT => Key::Named(NamedKey::NonConvert),
|
||||
VK_ACCEPT => Key::Named(NamedKey::Accept),
|
||||
VK_MODECHANGE => Key::Named(NamedKey::ModeChange),
|
||||
VK_SPACE => Key::Character(" ".into()),
|
||||
VK_PRIOR => Key::Named(NamedKey::PageUp),
|
||||
VK_NEXT => Key::Named(NamedKey::PageDown),
|
||||
VK_END => Key::Named(NamedKey::End),
|
||||
VK_HOME => Key::Named(NamedKey::Home),
|
||||
VK_LEFT => Key::Named(NamedKey::ArrowLeft),
|
||||
VK_UP => Key::Named(NamedKey::ArrowUp),
|
||||
VK_RIGHT => Key::Named(NamedKey::ArrowRight),
|
||||
VK_DOWN => Key::Named(NamedKey::ArrowDown),
|
||||
VK_SELECT => Key::Named(NamedKey::Select),
|
||||
VK_PRINT => Key::Named(NamedKey::Print),
|
||||
VK_EXECUTE => Key::Named(NamedKey::Execute),
|
||||
VK_SNAPSHOT => Key::Named(NamedKey::PrintScreen),
|
||||
VK_INSERT => Key::Named(NamedKey::Insert),
|
||||
VK_DELETE => Key::Named(NamedKey::Delete),
|
||||
VK_HELP => Key::Named(NamedKey::Help),
|
||||
VK_LWIN => Key::Named(NamedKey::Meta),
|
||||
VK_RWIN => Key::Named(NamedKey::Meta),
|
||||
VK_APPS => Key::Named(NamedKey::ContextMenu),
|
||||
VK_SLEEP => Key::Named(NamedKey::Standby),
|
||||
|
||||
// Numpad keys produce characters
|
||||
VK_NUMPAD0 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD1 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD2 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD3 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD4 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD5 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD6 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD7 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD8 => Key::Unidentified(native_code),
|
||||
VK_NUMPAD9 => Key::Unidentified(native_code),
|
||||
VK_MULTIPLY => Key::Unidentified(native_code),
|
||||
VK_ADD => Key::Unidentified(native_code),
|
||||
VK_SEPARATOR => Key::Unidentified(native_code),
|
||||
VK_SUBTRACT => Key::Unidentified(native_code),
|
||||
VK_DECIMAL => Key::Unidentified(native_code),
|
||||
VK_DIVIDE => Key::Unidentified(native_code),
|
||||
|
||||
VK_F1 => Key::Named(NamedKey::F1),
|
||||
VK_F2 => Key::Named(NamedKey::F2),
|
||||
VK_F3 => Key::Named(NamedKey::F3),
|
||||
VK_F4 => Key::Named(NamedKey::F4),
|
||||
VK_F5 => Key::Named(NamedKey::F5),
|
||||
VK_F6 => Key::Named(NamedKey::F6),
|
||||
VK_F7 => Key::Named(NamedKey::F7),
|
||||
VK_F8 => Key::Named(NamedKey::F8),
|
||||
VK_F9 => Key::Named(NamedKey::F9),
|
||||
VK_F10 => Key::Named(NamedKey::F10),
|
||||
VK_F11 => Key::Named(NamedKey::F11),
|
||||
VK_F12 => Key::Named(NamedKey::F12),
|
||||
VK_F13 => Key::Named(NamedKey::F13),
|
||||
VK_F14 => Key::Named(NamedKey::F14),
|
||||
VK_F15 => Key::Named(NamedKey::F15),
|
||||
VK_F16 => Key::Named(NamedKey::F16),
|
||||
VK_F17 => Key::Named(NamedKey::F17),
|
||||
VK_F18 => Key::Named(NamedKey::F18),
|
||||
VK_F19 => Key::Named(NamedKey::F19),
|
||||
VK_F20 => Key::Named(NamedKey::F20),
|
||||
VK_F21 => Key::Named(NamedKey::F21),
|
||||
VK_F22 => Key::Named(NamedKey::F22),
|
||||
VK_F23 => Key::Named(NamedKey::F23),
|
||||
VK_F24 => Key::Named(NamedKey::F24),
|
||||
VK_NAVIGATION_VIEW => Key::Unidentified(native_code),
|
||||
VK_NAVIGATION_MENU => Key::Unidentified(native_code),
|
||||
VK_NAVIGATION_UP => Key::Unidentified(native_code),
|
||||
VK_NAVIGATION_DOWN => Key::Unidentified(native_code),
|
||||
VK_NAVIGATION_LEFT => Key::Unidentified(native_code),
|
||||
VK_NAVIGATION_RIGHT => Key::Unidentified(native_code),
|
||||
VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code),
|
||||
VK_NAVIGATION_CANCEL => Key::Unidentified(native_code),
|
||||
VK_NUMLOCK => Key::Named(NamedKey::NumLock),
|
||||
VK_SCROLL => Key::Named(NamedKey::ScrollLock),
|
||||
VK_OEM_NEC_EQUAL => Key::Unidentified(native_code),
|
||||
// VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL`
|
||||
VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code),
|
||||
VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code),
|
||||
VK_OEM_FJ_LOYA => Key::Unidentified(native_code),
|
||||
VK_OEM_FJ_ROYA => Key::Unidentified(native_code),
|
||||
VK_LSHIFT => Key::Named(NamedKey::Shift),
|
||||
VK_RSHIFT => Key::Named(NamedKey::Shift),
|
||||
VK_LCONTROL => Key::Named(NamedKey::Control),
|
||||
VK_RCONTROL => Key::Named(NamedKey::Control),
|
||||
VK_LMENU => Key::Named(NamedKey::Alt),
|
||||
VK_RMENU => {
|
||||
if has_alt_graph {
|
||||
Key::Named(NamedKey::AltGraph)
|
||||
} else {
|
||||
Key::Named(NamedKey::Alt)
|
||||
}
|
||||
},
|
||||
VK_BROWSER_BACK => Key::Named(NamedKey::BrowserBack),
|
||||
VK_BROWSER_FORWARD => Key::Named(NamedKey::BrowserForward),
|
||||
VK_BROWSER_REFRESH => Key::Named(NamedKey::BrowserRefresh),
|
||||
VK_BROWSER_STOP => Key::Named(NamedKey::BrowserStop),
|
||||
VK_BROWSER_SEARCH => Key::Named(NamedKey::BrowserSearch),
|
||||
VK_BROWSER_FAVORITES => Key::Named(NamedKey::BrowserFavorites),
|
||||
VK_BROWSER_HOME => Key::Named(NamedKey::BrowserHome),
|
||||
VK_VOLUME_MUTE => Key::Named(NamedKey::AudioVolumeMute),
|
||||
VK_VOLUME_DOWN => Key::Named(NamedKey::AudioVolumeDown),
|
||||
VK_VOLUME_UP => Key::Named(NamedKey::AudioVolumeUp),
|
||||
VK_MEDIA_NEXT_TRACK => Key::Named(NamedKey::MediaTrackNext),
|
||||
VK_MEDIA_PREV_TRACK => Key::Named(NamedKey::MediaTrackPrevious),
|
||||
VK_MEDIA_STOP => Key::Named(NamedKey::MediaStop),
|
||||
VK_MEDIA_PLAY_PAUSE => Key::Named(NamedKey::MediaPlayPause),
|
||||
VK_LAUNCH_MAIL => Key::Named(NamedKey::LaunchMail),
|
||||
VK_LAUNCH_MEDIA_SELECT => Key::Named(NamedKey::LaunchMediaPlayer),
|
||||
VK_LAUNCH_APP1 => Key::Named(NamedKey::LaunchApplication1),
|
||||
VK_LAUNCH_APP2 => Key::Named(NamedKey::LaunchApplication2),
|
||||
|
||||
// This function only converts "non-printable"
|
||||
VK_OEM_1 => Key::Unidentified(native_code),
|
||||
VK_OEM_PLUS => Key::Unidentified(native_code),
|
||||
VK_OEM_COMMA => Key::Unidentified(native_code),
|
||||
VK_OEM_MINUS => Key::Unidentified(native_code),
|
||||
VK_OEM_PERIOD => Key::Unidentified(native_code),
|
||||
VK_OEM_2 => Key::Unidentified(native_code),
|
||||
VK_OEM_3 => Key::Unidentified(native_code),
|
||||
|
||||
VK_GAMEPAD_A => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_B => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_X => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_Y => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_MENU => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_VIEW => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code),
|
||||
VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code),
|
||||
|
||||
// This function only converts "non-printable"
|
||||
VK_OEM_4 => Key::Unidentified(native_code),
|
||||
VK_OEM_5 => Key::Unidentified(native_code),
|
||||
VK_OEM_6 => Key::Unidentified(native_code),
|
||||
VK_OEM_7 => Key::Unidentified(native_code),
|
||||
VK_OEM_8 => Key::Unidentified(native_code),
|
||||
VK_OEM_AX => Key::Unidentified(native_code),
|
||||
VK_OEM_102 => Key::Unidentified(native_code),
|
||||
|
||||
VK_ICO_HELP => Key::Unidentified(native_code),
|
||||
VK_ICO_00 => Key::Unidentified(native_code),
|
||||
|
||||
VK_PROCESSKEY => Key::Named(NamedKey::Process),
|
||||
|
||||
VK_ICO_CLEAR => Key::Unidentified(native_code),
|
||||
VK_PACKET => Key::Unidentified(native_code),
|
||||
VK_OEM_RESET => Key::Unidentified(native_code),
|
||||
VK_OEM_JUMP => Key::Unidentified(native_code),
|
||||
VK_OEM_PA1 => Key::Unidentified(native_code),
|
||||
VK_OEM_PA2 => Key::Unidentified(native_code),
|
||||
VK_OEM_PA3 => Key::Unidentified(native_code),
|
||||
VK_OEM_WSCTRL => Key::Unidentified(native_code),
|
||||
VK_OEM_CUSEL => Key::Unidentified(native_code),
|
||||
|
||||
VK_OEM_ATTN => Key::Named(NamedKey::Attn),
|
||||
VK_OEM_FINISH => {
|
||||
if is_japanese {
|
||||
Key::Named(NamedKey::Katakana)
|
||||
} else {
|
||||
// This matches IE and Firefox behaviour according to
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||
// At the time of writing, there is no `NamedKey::Finish` variant as
|
||||
// Finish is not mentioned at https://w3c.github.io/uievents-key/
|
||||
// Also see: https://github.com/pyfisch/keyboard-types/issues/9
|
||||
Key::Unidentified(native_code)
|
||||
}
|
||||
},
|
||||
VK_OEM_COPY => Key::Named(NamedKey::Copy),
|
||||
VK_OEM_AUTO => Key::Named(NamedKey::Hankaku),
|
||||
VK_OEM_ENLW => Key::Named(NamedKey::Zenkaku),
|
||||
VK_OEM_BACKTAB => Key::Named(NamedKey::Romaji),
|
||||
VK_ATTN => Key::Named(NamedKey::KanaMode),
|
||||
VK_CRSEL => Key::Named(NamedKey::CrSel),
|
||||
VK_EXSEL => Key::Named(NamedKey::ExSel),
|
||||
VK_EREOF => Key::Named(NamedKey::EraseEof),
|
||||
VK_PLAY => Key::Named(NamedKey::Play),
|
||||
VK_ZOOM => Key::Named(NamedKey::ZoomToggle),
|
||||
VK_NONAME => Key::Unidentified(native_code),
|
||||
VK_PA1 => Key::Unidentified(native_code),
|
||||
VK_OEM_CLEAR => Key::Named(NamedKey::Clear),
|
||||
_ => Key::Unidentified(native_code),
|
||||
}
|
||||
}
|
||||
749
winit-win32/src/lib.rs
Normal file
749
winit-win32/src/lib.rs
Normal file
|
|
@ -0,0 +1,749 @@
|
|||
//! # Winit Win32 / Windows backend
|
||||
//!
|
||||
//! The supported OS version is Windows 7 or higher, though Windows 10 is
|
||||
//! tested regularly.
|
||||
#![cfg(target_os = "windows")] // FIXME(madsmtm): Allow compiling on all platforms.
|
||||
|
||||
#[macro_use]
|
||||
mod util;
|
||||
mod dark_mode;
|
||||
mod definitions;
|
||||
mod dpi;
|
||||
mod drop_handler;
|
||||
mod event_loop;
|
||||
mod icon;
|
||||
mod ime;
|
||||
mod keyboard;
|
||||
mod keyboard_layout;
|
||||
mod monitor;
|
||||
mod raw_input;
|
||||
mod window;
|
||||
mod window_state;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::ffi::c_void;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::dpi::PhysicalSize;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
use winit_core::event::DeviceId;
|
||||
use winit_core::icon::{BadIcon, Icon};
|
||||
use winit_core::window::{PlatformWindowAttributes, Window as CoreWindow};
|
||||
|
||||
pub use self::event_loop::{EventLoop, PlatformSpecificEventLoopAttributes};
|
||||
use self::icon::{RaiiIcon, SelectedCursor};
|
||||
pub use self::keyboard::{physicalkey_to_scancode, scancode_to_physicalkey};
|
||||
pub use self::monitor::{MonitorHandle, VideoModeHandle};
|
||||
pub use self::window::Window;
|
||||
|
||||
/// Window Handle type used by Win32 API
|
||||
pub type HWND = *mut c_void;
|
||||
/// Menu Handle type used by Win32 API
|
||||
pub type HMENU = *mut c_void;
|
||||
/// Monitor Handle type used by Win32 API
|
||||
pub type HMONITOR = *mut c_void;
|
||||
|
||||
/// Describes a system-drawn backdrop material of a window.
|
||||
///
|
||||
/// For a detailed explanation, see [`DWM_SYSTEMBACKDROP_TYPE docs`].
|
||||
///
|
||||
/// [`DWM_SYSTEMBACKDROP_TYPE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_systembackdrop_type
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum BackdropType {
|
||||
/// Corresponds to `DWMSBT_AUTO`.
|
||||
///
|
||||
/// Usually draws a default backdrop effect on the title bar.
|
||||
#[default]
|
||||
Auto = 0,
|
||||
|
||||
/// Corresponds to `DWMSBT_NONE`.
|
||||
None = 1,
|
||||
|
||||
/// Corresponds to `DWMSBT_MAINWINDOW`.
|
||||
///
|
||||
/// Draws the Mica backdrop material.
|
||||
MainWindow = 2,
|
||||
|
||||
/// Corresponds to `DWMSBT_TRANSIENTWINDOW`.
|
||||
///
|
||||
/// Draws the Background Acrylic backdrop material.
|
||||
TransientWindow = 3,
|
||||
|
||||
/// Corresponds to `DWMSBT_TABBEDWINDOW`.
|
||||
///
|
||||
/// Draws the Alt Mica backdrop material.
|
||||
TabbedWindow = 4,
|
||||
}
|
||||
|
||||
/// Describes a color used by Windows
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Color(u32);
|
||||
|
||||
impl Color {
|
||||
// Special constant only valid for the window border and therefore modeled using Option<Color>
|
||||
// for user facing code
|
||||
const NONE: Color = Color(0xfffffffe);
|
||||
/// Use the system's default color
|
||||
pub const SYSTEM_DEFAULT: Color = Color(0xffffffff);
|
||||
|
||||
/// Create a new color from the given RGB values
|
||||
pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Self((r as u32) | ((g as u32) << 8) | ((b as u32) << 16))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Self::SYSTEM_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes how the corners of a window should look like.
|
||||
///
|
||||
/// For a detailed explanation, see [`DWM_WINDOW_CORNER_PREFERENCE docs`].
|
||||
///
|
||||
/// [`DWM_WINDOW_CORNER_PREFERENCE docs`]: https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwm_window_corner_preference
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum CornerPreference {
|
||||
/// Corresponds to `DWMWCP_DEFAULT`.
|
||||
///
|
||||
/// Let the system decide when to round window corners.
|
||||
#[default]
|
||||
Default = 0,
|
||||
|
||||
/// Corresponds to `DWMWCP_DONOTROUND`.
|
||||
///
|
||||
/// Never round window corners.
|
||||
DoNotRound = 1,
|
||||
|
||||
/// Corresponds to `DWMWCP_ROUND`.
|
||||
///
|
||||
/// Round the corners, if appropriate.
|
||||
Round = 2,
|
||||
|
||||
/// Corresponds to `DWMWCP_ROUNDSMALL`.
|
||||
///
|
||||
/// Round the corners if appropriate, with a small radius.
|
||||
RoundSmall = 3,
|
||||
}
|
||||
|
||||
/// A wrapper around a [`Window`] that ignores thread-specific window handle limitations.
|
||||
///
|
||||
/// See [`WindowBorrowExtWindows::any_thread`] for more information.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AnyThread<W: CoreWindow>(W);
|
||||
|
||||
impl<W: CoreWindow> AnyThread<W> {
|
||||
/// Get a reference to the inner window.
|
||||
#[inline]
|
||||
pub fn get_ref(&self) -> &dyn CoreWindow {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: CoreWindow> Deref for AnyThread<W> {
|
||||
type Target = W;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: CoreWindow> rwh_06::HasWindowHandle for AnyThread<W> {
|
||||
fn window_handle(&self) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
// SAFETY: The top level user has asserted this is only used safely.
|
||||
unsafe { self.get_ref().window_handle_any_thread() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
||||
pub trait EventLoopBuilderExtWindows {
|
||||
/// Whether to allow the event loop to be created off of the main thread.
|
||||
///
|
||||
/// By default, the window is only allowed to be created on the main
|
||||
/// thread, to make platform compatibility easier.
|
||||
///
|
||||
/// # `Window` caveats
|
||||
///
|
||||
/// Note that any `Window` created on the new thread will be destroyed when the thread
|
||||
/// terminates. Attempting to use a `Window` after its parent thread terminates has
|
||||
/// unspecified, although explicitly not undefined, behavior.
|
||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
|
||||
|
||||
/// Whether to enable process-wide DPI awareness.
|
||||
///
|
||||
/// By default, `winit` will attempt to enable process-wide DPI awareness. If
|
||||
/// that's undesirable, you can disable it with this function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Disable process-wide DPI awareness.
|
||||
///
|
||||
/// ```
|
||||
/// use winit::event_loop::EventLoop;
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// use winit::platform::windows::EventLoopBuilderExtWindows;
|
||||
///
|
||||
/// let mut builder = EventLoop::builder();
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// builder.with_dpi_aware(false);
|
||||
/// # if false { // We can't test this part
|
||||
/// let event_loop = builder.build();
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_dpi_aware(&mut self, dpi_aware: bool) -> &mut Self;
|
||||
|
||||
/// A callback to be executed before dispatching a win32 message to the window procedure.
|
||||
/// Return true to disable winit's internal message dispatching.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use windows_sys::Win32::UI::WindowsAndMessaging::{ACCEL, CreateAcceleratorTableW, TranslateAcceleratorW, DispatchMessageW, TranslateMessage, MSG};
|
||||
/// use winit::event_loop::EventLoop;
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// use winit::platform::windows::EventLoopBuilderExtWindows;
|
||||
///
|
||||
/// let mut builder = EventLoop::builder();
|
||||
/// #[cfg(target_os = "windows")]
|
||||
/// builder.with_msg_hook(|msg|{
|
||||
/// let msg = msg as *const MSG;
|
||||
/// # let accels: Vec<ACCEL> = Vec::new();
|
||||
/// let translated = unsafe {
|
||||
/// TranslateAcceleratorW(
|
||||
/// (*msg).hwnd,
|
||||
/// CreateAcceleratorTableW(accels.as_ptr() as _, 1),
|
||||
/// msg,
|
||||
/// ) == 1
|
||||
/// };
|
||||
/// translated
|
||||
/// });
|
||||
/// ```
|
||||
fn with_msg_hook<F>(&mut self, callback: F) -> &mut Self
|
||||
where
|
||||
F: FnMut(*const c_void) -> bool + 'static;
|
||||
}
|
||||
|
||||
/// Additional methods on `Window` that are specific to Windows.
|
||||
pub trait WindowExtWindows {
|
||||
/// Enables or disables mouse and keyboard input to the specified window.
|
||||
///
|
||||
/// A window must be enabled before it can be activated.
|
||||
/// If an application has create a modal dialog box by disabling its owner window
|
||||
/// (as described in [`WindowAttributesWindows::with_owner_window`]), the application must
|
||||
/// enable the owner window before destroying the dialog box.
|
||||
/// Otherwise, another window will receive the keyboard focus and be activated.
|
||||
///
|
||||
/// If a child window is disabled, it is ignored when the system tries to determine which
|
||||
/// window should receive mouse messages.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablewindow#remarks>
|
||||
/// and <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#disabled-windows>
|
||||
fn set_enable(&self, enabled: bool);
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
||||
|
||||
/// Whether to show or hide the window icon in the taskbar.
|
||||
fn set_skip_taskbar(&self, skip: bool);
|
||||
|
||||
/// Shows or hides the background drop shadow for undecorated windows.
|
||||
///
|
||||
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
|
||||
fn set_undecorated_shadow(&self, shadow: bool);
|
||||
|
||||
/// Sets system-drawn backdrop type.
|
||||
///
|
||||
/// Requires Windows 11 build 22523+.
|
||||
fn set_system_backdrop(&self, backdrop_type: BackdropType);
|
||||
|
||||
/// Sets the color of the window border.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn set_border_color(&self, color: Option<Color>);
|
||||
|
||||
/// Sets the background color of the title bar.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn set_title_background_color(&self, color: Option<Color>);
|
||||
|
||||
/// Sets the color of the window title.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn set_title_text_color(&self, color: Color);
|
||||
|
||||
/// Sets the preferred style of the window corners.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
fn set_corner_preference(&self, preference: CornerPreference);
|
||||
|
||||
/// Get the raw window handle for this [`Window`] without checking for thread affinity.
|
||||
///
|
||||
/// Window handles in Win32 have a property called "thread affinity" that ties them to their
|
||||
/// origin thread. Some operations can only happen on the window's origin thread, while others
|
||||
/// can be called from any thread. For example, [`SetWindowSubclass`] is not thread safe while
|
||||
/// [`GetDC`] is thread safe.
|
||||
///
|
||||
/// In Rust terms, the window handle is `Send` sometimes but `!Send` other times.
|
||||
///
|
||||
/// Therefore, in order to avoid confusing threading errors, [`Window`] only returns the
|
||||
/// window handle when the [`window_handle`] function is called from the thread that created
|
||||
/// the window. In other cases, it returns an [`Unavailable`] error.
|
||||
///
|
||||
/// However in some cases you may already know that you are using the window handle for
|
||||
/// operations that are guaranteed to be thread-safe. In which case this function aims
|
||||
/// to provide an escape hatch so these functions are still accessible from other threads.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// It is the responsibility of the user to only pass the window handle into thread-safe
|
||||
/// Win32 APIs.
|
||||
///
|
||||
/// [`SetWindowSubclass`]: https://learn.microsoft.com/en-us/windows/win32/api/commctrl/nf-commctrl-setwindowsubclass
|
||||
/// [`GetDC`]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdc
|
||||
/// [`Window`]: crate::window::Window
|
||||
/// [`window_handle`]: https://docs.rs/raw-window-handle/latest/raw_window_handle/trait.HasWindowHandle.html#tymethod.window_handle
|
||||
/// [`Unavailable`]: https://docs.rs/raw-window-handle/latest/raw_window_handle/enum.HandleError.html#variant.Unavailable
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use winit::window::Window;
|
||||
/// # fn scope(window: Box<dyn Window>) {
|
||||
/// use std::thread;
|
||||
///
|
||||
/// use winit::platform::windows::WindowExtWindows;
|
||||
/// use winit::raw_window_handle::HasWindowHandle;
|
||||
///
|
||||
/// // We can get the window handle on the current thread.
|
||||
/// let handle = window.window_handle().unwrap();
|
||||
///
|
||||
/// // However, on another thread, we can't!
|
||||
/// thread::spawn(move || {
|
||||
/// assert!(window.window_handle().is_err());
|
||||
///
|
||||
/// // We can use this function as an escape hatch.
|
||||
/// let handle = unsafe { window.window_handle_any_thread().unwrap() };
|
||||
/// });
|
||||
/// # }
|
||||
/// ```
|
||||
unsafe fn window_handle_any_thread(
|
||||
&self,
|
||||
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError>;
|
||||
}
|
||||
|
||||
impl WindowExtWindows for dyn CoreWindow + '_ {
|
||||
#[inline]
|
||||
fn set_enable(&self, enabled: bool) {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_enable(enabled)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_taskbar_icon(taskbar_icon)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_skip_taskbar(&self, skip: bool) {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_skip_taskbar(skip)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_undecorated_shadow(&self, shadow: bool) {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_undecorated_shadow(shadow)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_system_backdrop(&self, backdrop_type: BackdropType) {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_system_backdrop(backdrop_type)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_border_color(&self, color: Option<Color>) {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_border_color(color.unwrap_or(Color::NONE))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_title_background_color(&self, color: Option<Color>) {
|
||||
// The windows docs don't mention NONE as a valid options but it works in practice and is
|
||||
// useful to circumvent the Windows option "Show accent color on title bars and
|
||||
// window borders"
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_title_background_color(color.unwrap_or(Color::NONE))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_title_text_color(&self, color: Color) {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_title_text_color(color)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_corner_preference(&self, preference: CornerPreference) {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
window.set_corner_preference(preference)
|
||||
}
|
||||
|
||||
unsafe fn window_handle_any_thread(
|
||||
&self,
|
||||
) -> Result<rwh_06::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
let window = self.cast_ref::<Window>().unwrap();
|
||||
unsafe {
|
||||
let handle = window.rwh_06_no_thread_check()?;
|
||||
|
||||
// SAFETY: The handle is valid in this context.
|
||||
Ok(rwh_06::WindowHandle::borrow_raw(handle))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods for anything that dereference to [`Window`].
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
pub trait WindowBorrowExtWindows: Borrow<dyn CoreWindow> + Sized {
|
||||
/// Create an object that allows accessing the inner window handle in a thread-unsafe way.
|
||||
///
|
||||
/// It is possible to call [`window_handle_any_thread`] to get around Windows's thread
|
||||
/// affinity limitations. However, it may be desired to pass the [`Window`] into something
|
||||
/// that requires the [`HasWindowHandle`] trait, while ignoring thread affinity limitations.
|
||||
///
|
||||
/// This function wraps anything that implements `Borrow<Window>` into a structure that
|
||||
/// uses the inner window handle as a mean of implementing [`HasWindowHandle`]. It wraps
|
||||
/// `Window`, `&Window`, `Arc<Window>`, and other reference types.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// It is the responsibility of the user to only pass the window handle into thread-safe
|
||||
/// Win32 APIs.
|
||||
///
|
||||
/// [`Window`]: crate::window::Window
|
||||
/// [`HasWindowHandle`]: rwh_06::HasWindowHandle
|
||||
/// [`window_handle_any_thread`]: WindowExtWindows::window_handle_any_thread
|
||||
unsafe fn any_thread(self) -> AnyThread<Self>
|
||||
where
|
||||
Self: CoreWindow,
|
||||
{
|
||||
AnyThread(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Borrow<dyn CoreWindow> + Sized> WindowBorrowExtWindows for W {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WindowAttributesWindows {
|
||||
pub(crate) owner: Option<HWND>,
|
||||
pub(crate) menu: Option<HMENU>,
|
||||
pub(crate) taskbar_icon: Option<Icon>,
|
||||
pub(crate) no_redirection_bitmap: bool,
|
||||
pub(crate) drag_and_drop: bool,
|
||||
pub(crate) skip_taskbar: bool,
|
||||
pub(crate) class_name: String,
|
||||
pub(crate) decoration_shadow: bool,
|
||||
pub(crate) backdrop_type: BackdropType,
|
||||
pub(crate) clip_children: bool,
|
||||
pub(crate) border_color: Option<Color>,
|
||||
pub(crate) title_background_color: Option<Color>,
|
||||
pub(crate) title_text_color: Option<Color>,
|
||||
pub(crate) corner_preference: Option<CornerPreference>,
|
||||
}
|
||||
|
||||
impl Default for WindowAttributesWindows {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
owner: None,
|
||||
menu: None,
|
||||
taskbar_icon: None,
|
||||
no_redirection_bitmap: false,
|
||||
drag_and_drop: true,
|
||||
skip_taskbar: false,
|
||||
class_name: "Window Class".to_string(),
|
||||
decoration_shadow: false,
|
||||
backdrop_type: BackdropType::default(),
|
||||
clip_children: true,
|
||||
border_color: None,
|
||||
title_background_color: None,
|
||||
title_text_color: None,
|
||||
corner_preference: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for WindowAttributesWindows {}
|
||||
unsafe impl Sync for WindowAttributesWindows {}
|
||||
|
||||
impl WindowAttributesWindows {
|
||||
/// Set an owner to the window to be created. Can be used to create a dialog box, for example.
|
||||
/// This only works when [`WindowAttributes::with_parent_window`] isn't called or set to `None`.
|
||||
/// Can be used in combination with
|
||||
/// [`WindowExtWindows::set_enable(false)`][WindowExtWindows::set_enable] on the owner
|
||||
/// window to create a modal dialog box.
|
||||
///
|
||||
/// From MSDN:
|
||||
/// - An owned window is always above its owner in the z-order.
|
||||
/// - The system automatically destroys an owned window when its owner is destroyed.
|
||||
/// - An owned window is hidden when its owner is minimized.
|
||||
///
|
||||
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
|
||||
///
|
||||
/// [`WindowAttributes::with_parent_window`]: winit_core::window::WindowAttributes::with_parent_window
|
||||
pub fn with_owner_window(mut self, parent: HWND) -> Self {
|
||||
self.owner = Some(parent);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a menu on the window to be created.
|
||||
///
|
||||
/// Parent and menu are mutually exclusive; a child window cannot have a menu!
|
||||
///
|
||||
/// The menu must have been manually created beforehand with [`CreateMenu`] or similar.
|
||||
///
|
||||
/// Note: Dark mode cannot be supported for win32 menus, it's simply not possible to change how
|
||||
/// the menus look. If you use this, it is recommended that you combine it with
|
||||
/// `with_theme(Some(Theme::Light))` to avoid a jarring effect.
|
||||
///
|
||||
/// [`CreateMenu`]: windows_sys::Win32::UI::WindowsAndMessaging::CreateMenu"
|
||||
pub fn with_menu(mut self, menu: HMENU) -> Self {
|
||||
self.menu = Some(menu);
|
||||
self
|
||||
}
|
||||
|
||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||
pub fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> Self {
|
||||
self.taskbar_icon = taskbar_icon;
|
||||
self
|
||||
}
|
||||
|
||||
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
|
||||
pub fn with_no_redirection_bitmap(mut self, flag: bool) -> Self {
|
||||
self.no_redirection_bitmap = flag;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables or disables drag and drop support (enabled by default). Will interfere with other
|
||||
/// crates that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED`
|
||||
/// instead of `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still
|
||||
/// attempt to initialize COM API regardless of this option. Currently only fullscreen mode
|
||||
/// does that, but there may be more in the future. If you need COM API with
|
||||
/// `COINIT_MULTITHREADED` you must initialize it before calling any winit functions. See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> for more information.
|
||||
pub fn with_drag_and_drop(mut self, flag: bool) -> Self {
|
||||
self.drag_and_drop = flag;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether show or hide the window icon in the taskbar.
|
||||
pub fn with_skip_taskbar(mut self, skip: bool) -> Self {
|
||||
self.skip_taskbar = skip;
|
||||
self
|
||||
}
|
||||
|
||||
/// Customize the window class name.
|
||||
pub fn with_class_name<S: Into<String>>(mut self, class_name: S) -> Self {
|
||||
self.class_name = class_name.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Shows or hides the background drop shadow for undecorated windows.
|
||||
///
|
||||
/// The shadow is hidden by default.
|
||||
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
|
||||
pub fn with_undecorated_shadow(mut self, shadow: bool) -> Self {
|
||||
self.decoration_shadow = shadow;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets system-drawn backdrop type.
|
||||
///
|
||||
/// Requires Windows 11 build 22523+.
|
||||
pub fn with_system_backdrop(mut self, backdrop_type: BackdropType) -> Self {
|
||||
self.backdrop_type = backdrop_type;
|
||||
self
|
||||
}
|
||||
|
||||
/// This sets or removes `WS_CLIPCHILDREN` style.
|
||||
pub fn with_clip_children(mut self, flag: bool) -> Self {
|
||||
self.clip_children = flag;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the color of the window border.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
pub fn with_border_color(mut self, color: Option<Color>) -> Self {
|
||||
self.border_color = Some(color.unwrap_or(Color::NONE));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the background color of the title bar.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
pub fn with_title_background_color(mut self, color: Option<Color>) -> Self {
|
||||
self.title_background_color = Some(color.unwrap_or(Color::NONE));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the color of the window title.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
pub fn with_title_text_color(mut self, color: Color) -> Self {
|
||||
self.title_text_color = Some(color);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the preferred style of the window corners.
|
||||
///
|
||||
/// Supported starting with Windows 11 Build 22000.
|
||||
pub fn with_corner_preference(mut self, corners: CornerPreference) -> Self {
|
||||
self.corner_preference = Some(corners);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformWindowAttributes for WindowAttributesWindows {
|
||||
fn box_clone(&self) -> Box<dyn PlatformWindowAttributes> {
|
||||
Box::from(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on `DeviceId` that are specific to Windows.
|
||||
pub trait DeviceIdExtWindows {
|
||||
/// Returns an identifier that persistently refers to this specific device.
|
||||
///
|
||||
/// Will return `None` if the device is no longer available.
|
||||
fn persistent_identifier(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl DeviceIdExtWindows for DeviceId {
|
||||
fn persistent_identifier(&self) -> Option<String> {
|
||||
let raw_id = self.into_raw();
|
||||
if raw_id != 0 {
|
||||
raw_input::get_raw_input_device_name(raw_id as HANDLE)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows specific `Icon`.
|
||||
///
|
||||
/// Windows icons can be created from files, or from the [`embedded resources`](https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files).
|
||||
///
|
||||
/// The `ICON` resource definition statement use the following syntax:
|
||||
/// ```rc
|
||||
/// nameID ICON filename
|
||||
/// ```
|
||||
/// `nameID` is a unique name or a 16-bit unsigned integer value identifying the resource,
|
||||
/// `filename` is the name of the file that contains the resource.
|
||||
///
|
||||
/// More information about the `ICON` resource can be found at [`Microsoft Learn`](https://learn.microsoft.com/en-us/windows/win32/menurc/icon-resource) portal.
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct WinIcon {
|
||||
pub(crate) inner: Arc<RaiiIcon>,
|
||||
}
|
||||
|
||||
impl WinIcon {
|
||||
/// Create an icon from a file path.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
pub fn from_path<P: AsRef<Path>>(
|
||||
path: P,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
Self::from_path_impl(path, size)
|
||||
}
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library by its ordinal id.
|
||||
///
|
||||
/// The valid `ordinal` values range from 1 to [`u16::MAX`] (inclusive). The value `0` is an
|
||||
/// invalid ordinal id, but it can be used with [`from_resource_name`] as `"0"`.
|
||||
///
|
||||
/// [`from_resource_name`]: Self::from_resource_name
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
pub fn from_resource(
|
||||
resource_id: u16,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
Self::from_resource_impl(resource_id, size)
|
||||
}
|
||||
|
||||
/// Create an icon from a resource embedded in this executable or library by its name.
|
||||
///
|
||||
/// Specify `size` to load a specific icon size from the file, or `None` to load the default
|
||||
/// icon size from the file.
|
||||
///
|
||||
/// In cases where the specified size does not exist in the file, Windows may perform scaling
|
||||
/// to get an icon of the desired size.
|
||||
///
|
||||
/// # Notes
|
||||
///
|
||||
/// Consider the following resource definition statements:
|
||||
/// ```rc
|
||||
/// app ICON "app.ico"
|
||||
/// 1 ICON "a.ico"
|
||||
/// 0027 ICON "custom.ico"
|
||||
/// 0 ICON "alt.ico"
|
||||
/// ```
|
||||
///
|
||||
/// Due to some internal implementation details of the resource embedding/loading process on
|
||||
/// Windows platform, strings that can be interpreted as 16-bit unsigned integers (`"1"`,
|
||||
/// `"002"`, etc.) cannot be used as valid resource names, and instead should be passed into
|
||||
/// [`from_resource`]:
|
||||
///
|
||||
/// [`from_resource`]: Self::from_resource
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use winit::platform::windows::WinIcon;
|
||||
///
|
||||
/// assert!(WinIcon::from_resource_name("app", None).is_ok());
|
||||
/// assert!(WinIcon::from_resource(1, None).is_ok());
|
||||
/// assert!(WinIcon::from_resource(27, None).is_ok());
|
||||
/// assert!(WinIcon::from_resource_name("27", None).is_err());
|
||||
/// assert!(WinIcon::from_resource_name("0027", None).is_err());
|
||||
/// ```
|
||||
///
|
||||
/// While `0` cannot be used as an ordinal id (see [`from_resource`]), it can be used as a
|
||||
/// name:
|
||||
///
|
||||
/// [`from_resource`]: IconExtWindows::from_resource
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use winit::platform::windows::WinIcon;
|
||||
/// # use winit::icon::Icon;
|
||||
/// assert!(WinIcon::from_resource_name("0", None).is_ok());
|
||||
/// assert!(WinIcon::from_resource(0, None).is_err());
|
||||
/// ```
|
||||
pub fn from_resource_name(
|
||||
resource_name: &str,
|
||||
size: Option<PhysicalSize<u32>>,
|
||||
) -> Result<Self, BadIcon> {
|
||||
Self::from_resource_name_impl(resource_name, size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WinIcon> for Icon {
|
||||
fn from(value: WinIcon) -> Self {
|
||||
Self(Arc::new(value))
|
||||
}
|
||||
}
|
||||
67
winit-win32/src/minimal_ime.rs
Normal file
67
winit-win32/src/minimal_ime.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering::Relaxed},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
use winapi::{
|
||||
shared::{
|
||||
minwindef::{LPARAM, WPARAM},
|
||||
windef::HWND,
|
||||
},
|
||||
um::winuser,
|
||||
};
|
||||
|
||||
use crate::{event_loop::ProcResult, keyboard::next_kbd_msg};
|
||||
|
||||
pub struct MinimalIme {
|
||||
// True if we're currently receiving messages belonging to a finished IME session.
|
||||
getting_ime_text: AtomicBool,
|
||||
|
||||
utf16parts: Mutex<Vec<u16>>,
|
||||
}
|
||||
impl Default for MinimalIme {
|
||||
fn default() -> Self {
|
||||
MinimalIme {
|
||||
getting_ime_text: AtomicBool::new(false),
|
||||
utf16parts: Mutex::new(Vec::with_capacity(16)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MinimalIme {
|
||||
pub(crate) fn process_message(
|
||||
&self,
|
||||
hwnd: HWND,
|
||||
msg_kind: u32,
|
||||
wparam: WPARAM,
|
||||
_lparam: LPARAM,
|
||||
result: &mut ProcResult,
|
||||
) -> Option<String> {
|
||||
match msg_kind {
|
||||
winuser::WM_IME_ENDCOMPOSITION => {
|
||||
self.getting_ime_text.store(true, Relaxed);
|
||||
}
|
||||
winuser::WM_CHAR | winuser::WM_SYSCHAR => {
|
||||
if self.getting_ime_text.load(Relaxed) {
|
||||
*result = ProcResult::Value(0);
|
||||
self.utf16parts.lock().unwrap().push(wparam as u16);
|
||||
// It's important that we push the new character and release the lock
|
||||
// before getting the next message
|
||||
let next_msg = next_kbd_msg(hwnd);
|
||||
let more_char_coming = next_msg
|
||||
.map(|m| matches!(m.message, winuser::WM_CHAR | winuser::WM_SYSCHAR))
|
||||
.unwrap_or(false);
|
||||
if !more_char_coming {
|
||||
let mut utf16parts = self.utf16parts.lock().unwrap();
|
||||
let result = String::from_utf16(&utf16parts).ok();
|
||||
utf16parts.clear();
|
||||
self.getting_ime_text.store(false, Relaxed);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
211
winit-win32/src/monitor.rs
Normal file
211
winit-win32/src/monitor.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
use std::collections::{HashSet, VecDeque};
|
||||
use std::hash::Hash;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
use std::{io, iter, mem, ptr};
|
||||
|
||||
use dpi::{PhysicalPosition, PhysicalSize};
|
||||
use windows_sys::Win32::Foundation::{BOOL, HWND, LPARAM, POINT, RECT};
|
||||
use windows_sys::Win32::Graphics::Gdi::{
|
||||
EnumDisplayMonitors, EnumDisplaySettingsExW, GetMonitorInfoW, MonitorFromPoint,
|
||||
MonitorFromWindow, DEVMODEW, DM_BITSPERPEL, DM_DISPLAYFREQUENCY, DM_PELSHEIGHT, DM_PELSWIDTH,
|
||||
ENUM_CURRENT_SETTINGS, HDC, HMONITOR, MONITORINFO, MONITORINFOEXW, MONITOR_DEFAULTTONEAREST,
|
||||
MONITOR_DEFAULTTOPRIMARY,
|
||||
};
|
||||
use winit_core::monitor::{MonitorHandleProvider, VideoMode};
|
||||
|
||||
use super::util::decode_wide;
|
||||
use crate::dpi::{dpi_to_scale_factor, get_monitor_dpi};
|
||||
use crate::util::has_flag;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VideoModeHandle {
|
||||
pub(crate) mode: VideoMode,
|
||||
// DEVMODEW is huge so we box it to avoid blowing up the size of winit::window::Fullscreen
|
||||
pub(crate) native_video_mode: Box<DEVMODEW>,
|
||||
}
|
||||
|
||||
impl PartialEq for VideoModeHandle {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.mode == other.mode
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for VideoModeHandle {}
|
||||
|
||||
impl std::hash::Hash for VideoModeHandle {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.mode.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VideoModeHandle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("VideoMode").field("mode", &self.mode).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl VideoModeHandle {
|
||||
fn new(native_video_mode: DEVMODEW) -> Self {
|
||||
const REQUIRED_FIELDS: u32 =
|
||||
DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY;
|
||||
assert!(has_flag(native_video_mode.dmFields, REQUIRED_FIELDS));
|
||||
|
||||
let mode = VideoMode::new(
|
||||
(native_video_mode.dmPelsWidth, native_video_mode.dmPelsHeight).into(),
|
||||
NonZeroU16::new(native_video_mode.dmBitsPerPel as u16),
|
||||
NonZeroU32::new(native_video_mode.dmDisplayFrequency * 1000),
|
||||
);
|
||||
|
||||
VideoModeHandle { mode, native_video_mode: Box::new(native_video_mode) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn monitor_enum_proc(
|
||||
hmonitor: HMONITOR,
|
||||
_hdc: HDC,
|
||||
_place: *mut RECT,
|
||||
data: LPARAM,
|
||||
) -> BOOL {
|
||||
let monitors = data as *mut VecDeque<MonitorHandle>;
|
||||
unsafe { (*monitors).push_back(MonitorHandle::new(hmonitor)) };
|
||||
true.into() // continue enumeration
|
||||
}
|
||||
|
||||
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||
let mut monitors: VecDeque<MonitorHandle> = VecDeque::new();
|
||||
unsafe {
|
||||
EnumDisplayMonitors(
|
||||
ptr::null_mut(),
|
||||
ptr::null(),
|
||||
Some(monitor_enum_proc),
|
||||
&mut monitors as *mut _ as LPARAM,
|
||||
);
|
||||
}
|
||||
monitors
|
||||
}
|
||||
|
||||
pub fn primary_monitor() -> MonitorHandle {
|
||||
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
||||
let hmonitor = unsafe { MonitorFromPoint(ORIGIN, MONITOR_DEFAULTTOPRIMARY) };
|
||||
MonitorHandle::new(hmonitor)
|
||||
}
|
||||
|
||||
pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
|
||||
let hmonitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) };
|
||||
MonitorHandle::new(hmonitor)
|
||||
}
|
||||
|
||||
pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<MONITORINFOEXW, io::Error> {
|
||||
let mut monitor_info: MONITORINFOEXW = unsafe { mem::zeroed() };
|
||||
monitor_info.monitorInfo.cbSize = mem::size_of::<MONITORINFOEXW>() as u32;
|
||||
let status = unsafe {
|
||||
GetMonitorInfoW(hmonitor, &mut monitor_info as *mut MONITORINFOEXW as *mut MONITORINFO)
|
||||
};
|
||||
if status == false.into() {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(monitor_info)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||
pub struct MonitorHandle(HMONITOR);
|
||||
|
||||
// Send and Sync are not implemented for HMONITOR, we have to wrap it and implement them manually.
|
||||
|
||||
unsafe impl Send for MonitorHandle {}
|
||||
unsafe impl Sync for MonitorHandle {}
|
||||
|
||||
impl MonitorHandle {
|
||||
pub(crate) fn new(hmonitor: HMONITOR) -> Self {
|
||||
MonitorHandle(hmonitor)
|
||||
}
|
||||
|
||||
pub(crate) fn size(&self) -> PhysicalSize<u32> {
|
||||
let rc_monitor = get_monitor_info(self.0).unwrap().monitorInfo.rcMonitor;
|
||||
PhysicalSize {
|
||||
width: (rc_monitor.right - rc_monitor.left) as u32,
|
||||
height: (rc_monitor.bottom - rc_monitor.top) as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn video_mode_handles(&self) -> Box<dyn Iterator<Item = VideoModeHandle>> {
|
||||
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
||||
// fields are probably changing, but we aren't looking at those fields
|
||||
// anyway), so we're using a BTreeSet deduplicate
|
||||
let mut modes = HashSet::<VideoModeHandle>::new();
|
||||
|
||||
let monitor_info = match get_monitor_info(self.0) {
|
||||
Ok(monitor_info) => monitor_info,
|
||||
Err(error) => {
|
||||
tracing::warn!("Error from get_monitor_info: {error}");
|
||||
return Box::new(iter::empty());
|
||||
},
|
||||
};
|
||||
|
||||
let device_name = monitor_info.szDevice.as_ptr();
|
||||
let mut i = 0;
|
||||
loop {
|
||||
let mut mode: DEVMODEW = unsafe { mem::zeroed() };
|
||||
mode.dmSize = mem::size_of_val(&mode) as u16;
|
||||
if unsafe { EnumDisplaySettingsExW(device_name, i, &mut mode, 0) } == false.into() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Use Ord impl of RootVideoModeHandle
|
||||
modes.insert(VideoModeHandle::new(mode));
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Box::new(modes.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl MonitorHandleProvider for MonitorHandle {
|
||||
fn id(&self) -> u128 {
|
||||
self.native_id() as _
|
||||
}
|
||||
|
||||
fn native_id(&self) -> u64 {
|
||||
self.0 as _
|
||||
}
|
||||
|
||||
fn name(&self) -> Option<std::borrow::Cow<'_, str>> {
|
||||
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||
Some(decode_wide(&monitor_info.szDevice).to_string_lossy().to_string().into())
|
||||
}
|
||||
|
||||
fn position(&self) -> Option<PhysicalPosition<i32>> {
|
||||
get_monitor_info(self.0)
|
||||
.map(|info| {
|
||||
let rc_monitor = info.monitorInfo.rcMonitor;
|
||||
PhysicalPosition { x: rc_monitor.left, y: rc_monitor.top }
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f64 {
|
||||
dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
|
||||
}
|
||||
|
||||
fn current_video_mode(&self) -> Option<winit_core::monitor::VideoMode> {
|
||||
let monitor_info = get_monitor_info(self.0).ok()?;
|
||||
let device_name = monitor_info.szDevice.as_ptr();
|
||||
unsafe {
|
||||
let mut mode: DEVMODEW = mem::zeroed();
|
||||
mode.dmSize = mem::size_of_val(&mode) as u16;
|
||||
if EnumDisplaySettingsExW(device_name, ENUM_CURRENT_SETTINGS, &mut mode, 0)
|
||||
== false.into()
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(VideoModeHandle::new(mode).mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
|
||||
Box::new(self.video_mode_handles().map(|mode| mode.mode))
|
||||
}
|
||||
}
|
||||
301
winit-win32/src/raw_input.rs
Normal file
301
winit-win32/src/raw_input.rs
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
use std::mem::{self, size_of};
|
||||
use std::ptr;
|
||||
|
||||
use windows_sys::Win32::Devices::HumanInterfaceDevice::{
|
||||
HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC,
|
||||
};
|
||||
use windows_sys::Win32::Foundation::{HANDLE, HWND};
|
||||
use windows_sys::Win32::UI::Input::KeyboardAndMouse::{
|
||||
MapVirtualKeyW, MAPVK_VK_TO_VSC_EX, VK_NUMLOCK, VK_SHIFT,
|
||||
};
|
||||
use windows_sys::Win32::UI::Input::{
|
||||
GetRawInputData, GetRawInputDeviceInfoW, GetRawInputDeviceList, RegisterRawInputDevices,
|
||||
HRAWINPUT, RAWINPUT, RAWINPUTDEVICE, RAWINPUTDEVICELIST, RAWINPUTHEADER, RAWKEYBOARD,
|
||||
RIDEV_DEVNOTIFY, RIDEV_INPUTSINK, RIDEV_REMOVE, RIDI_DEVICEINFO, RIDI_DEVICENAME,
|
||||
RID_DEVICE_INFO, RID_DEVICE_INFO_HID, RID_DEVICE_INFO_KEYBOARD, RID_DEVICE_INFO_MOUSE,
|
||||
RID_INPUT, RIM_TYPEHID, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
|
||||
};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
RI_KEY_E0, RI_KEY_E1, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, RI_MOUSE_BUTTON_2_DOWN,
|
||||
RI_MOUSE_BUTTON_2_UP, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP, RI_MOUSE_BUTTON_4_DOWN,
|
||||
RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP,
|
||||
};
|
||||
use winit_core::event::ElementState;
|
||||
use winit_core::event_loop::DeviceEvents;
|
||||
use winit_core::keyboard::{KeyCode, PhysicalKey};
|
||||
|
||||
use super::scancode_to_physicalkey;
|
||||
use crate::util;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_list() -> Option<Vec<RAWINPUTDEVICELIST>> {
|
||||
let list_size = size_of::<RAWINPUTDEVICELIST>() as u32;
|
||||
|
||||
let mut num_devices = 0;
|
||||
let status = unsafe { GetRawInputDeviceList(ptr::null_mut(), &mut num_devices, list_size) };
|
||||
|
||||
if status == u32::MAX {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buffer = Vec::with_capacity(num_devices as _);
|
||||
|
||||
let num_stored =
|
||||
unsafe { GetRawInputDeviceList(buffer.as_mut_ptr(), &mut num_devices, list_size) };
|
||||
|
||||
if num_stored == u32::MAX {
|
||||
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.Anonymous.mouse),
|
||||
RIM_TYPEKEYBOARD => RawDeviceInfo::Keyboard(info.Anonymous.keyboard),
|
||||
RIM_TYPEHID => RawDeviceInfo::Hid(info.Anonymous.hid),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_raw_input_device_info(handle: HANDLE) -> Option<RawDeviceInfo> {
|
||||
let mut info: RID_DEVICE_INFO = unsafe { mem::zeroed() };
|
||||
let info_size = size_of::<RID_DEVICE_INFO>() as u32;
|
||||
|
||||
info.cbSize = info_size;
|
||||
|
||||
let mut minimum_size = 0;
|
||||
let status = unsafe {
|
||||
GetRawInputDeviceInfoW(handle, RIDI_DEVICEINFO, &mut info as *mut _ as _, &mut minimum_size)
|
||||
};
|
||||
|
||||
if status == u32::MAX || 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 {
|
||||
GetRawInputDeviceInfoW(handle, RIDI_DEVICENAME, ptr::null_mut(), &mut minimum_size)
|
||||
};
|
||||
|
||||
if status != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut name: Vec<u16> = Vec::with_capacity(minimum_size as _);
|
||||
|
||||
let status = unsafe {
|
||||
GetRawInputDeviceInfoW(handle, RIDI_DEVICENAME, name.as_ptr() as _, &mut minimum_size)
|
||||
};
|
||||
|
||||
if status == u32::MAX || status == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug_assert_eq!(minimum_size, status);
|
||||
|
||||
unsafe { name.set_len(minimum_size as _) };
|
||||
|
||||
util::decode_wide(&name).into_string().ok()
|
||||
}
|
||||
|
||||
pub fn register_raw_input_devices(devices: &[RAWINPUTDEVICE]) -> bool {
|
||||
let device_size = size_of::<RAWINPUTDEVICE>() as u32;
|
||||
|
||||
unsafe {
|
||||
RegisterRawInputDevices(devices.as_ptr(), devices.len() as u32, device_size) == true.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_all_mice_and_keyboards_for_raw_input(
|
||||
mut window_handle: HWND,
|
||||
filter: DeviceEvents,
|
||||
) -> bool {
|
||||
// RIDEV_DEVNOTIFY: receive hotplug events
|
||||
// RIDEV_INPUTSINK: receive events even if we're not in the foreground
|
||||
// RIDEV_REMOVE: don't receive device events (requires NULL hwndTarget)
|
||||
let flags = match filter {
|
||||
DeviceEvents::Never => {
|
||||
window_handle = ptr::null_mut();
|
||||
RIDEV_REMOVE
|
||||
},
|
||||
DeviceEvents::WhenFocused => RIDEV_DEVNOTIFY,
|
||||
DeviceEvents::Always => 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::zeroed() };
|
||||
let mut data_size = size_of::<RAWINPUT>() as u32;
|
||||
let header_size = size_of::<RAWINPUTHEADER>() as u32;
|
||||
|
||||
let status = unsafe {
|
||||
GetRawInputData(handle, RID_INPUT, &mut data as *mut _ as _, &mut data_size, header_size)
|
||||
};
|
||||
|
||||
if status == u32::MAX || status == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(data)
|
||||
}
|
||||
|
||||
fn button_flags_to_element_state(
|
||||
button_flags: u32,
|
||||
down_flag: u32,
|
||||
up_flag: u32,
|
||||
) -> 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: u32) -> [Option<ElementState>; 5] {
|
||||
[
|
||||
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP),
|
||||
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP),
|
||||
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP),
|
||||
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP),
|
||||
button_flags_to_element_state(button_flags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn get_keyboard_physical_key(keyboard: RAWKEYBOARD) -> Option<PhysicalKey> {
|
||||
let extension = {
|
||||
if util::has_flag(keyboard.Flags, RI_KEY_E0 as _) {
|
||||
0xe000
|
||||
} else if util::has_flag(keyboard.Flags, RI_KEY_E1 as _) {
|
||||
0xe100
|
||||
} else {
|
||||
0x0000
|
||||
}
|
||||
};
|
||||
let scancode = if keyboard.MakeCode == 0 {
|
||||
// In some cases (often with media keys) the device reports a scancode of 0 but a
|
||||
// valid virtual key. In these cases we obtain the scancode from the virtual key.
|
||||
unsafe { MapVirtualKeyW(keyboard.VKey as u32, MAPVK_VK_TO_VSC_EX) as u16 }
|
||||
} else {
|
||||
keyboard.MakeCode | extension
|
||||
};
|
||||
if scancode == 0xe11d || scancode == 0xe02a {
|
||||
// At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing
|
||||
// Ctrl+NumLock.
|
||||
// This equivalence means that if the user presses Pause, the keyboard will emit two
|
||||
// subsequent keypresses:
|
||||
// 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100)
|
||||
// 2, 0x0045 - Which on its own can be interpreted as Pause
|
||||
//
|
||||
// There's another combination which isn't quite an equivalence:
|
||||
// PrtSc used to be Shift+Asterisk. This means that on some keyboards, pressing
|
||||
// PrtSc (print screen) produces the following sequence:
|
||||
// 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000)
|
||||
// 2, 0xE037 - Which is a numpad multiply (0x37) with an extension flag (0xE000). This on
|
||||
// its own it can be interpreted as PrtSc
|
||||
//
|
||||
// For this reason, if we encounter the first keypress, we simply ignore it, trusting
|
||||
// that there's going to be another event coming, from which we can extract the
|
||||
// appropriate key.
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
return None;
|
||||
}
|
||||
let physical_key = if keyboard.VKey == VK_NUMLOCK {
|
||||
// Historically, the NumLock and the Pause key were one and the same physical key.
|
||||
// The user could trigger Pause by pressing Ctrl+NumLock.
|
||||
// Now these are often physically separate and the two keys can be differentiated by
|
||||
// checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045.
|
||||
//
|
||||
// However in this event, both keys are reported as 0x0045 even on modern hardware.
|
||||
// Therefore we use the virtual key instead to determine whether it's a NumLock and
|
||||
// set the KeyCode accordingly.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "Why does Ctrl+ScrollLock cancel dialogs?"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503
|
||||
PhysicalKey::Code(KeyCode::NumLock)
|
||||
} else {
|
||||
scancode_to_physicalkey(scancode as u32)
|
||||
};
|
||||
if keyboard.VKey == VK_SHIFT {
|
||||
if let PhysicalKey::Code(
|
||||
KeyCode::NumpadDecimal
|
||||
| KeyCode::Numpad0
|
||||
| KeyCode::Numpad1
|
||||
| KeyCode::Numpad2
|
||||
| KeyCode::Numpad3
|
||||
| KeyCode::Numpad4
|
||||
| KeyCode::Numpad5
|
||||
| KeyCode::Numpad6
|
||||
| KeyCode::Numpad7
|
||||
| KeyCode::Numpad8
|
||||
| KeyCode::Numpad9,
|
||||
) = physical_key
|
||||
{
|
||||
// On Windows, holding the Shift key makes numpad keys behave as if NumLock
|
||||
// wasn't active. The way this is exposed to applications by the system is that
|
||||
// the application receives a fake key release event for the shift key at the
|
||||
// moment when the numpad key is pressed, just before receiving the numpad key
|
||||
// as well.
|
||||
//
|
||||
// The issue is that in the raw device event (here), the fake shift release
|
||||
// event reports the numpad key as the scancode. Unfortunately, the event
|
||||
// doesn't have any information to tell whether it's the
|
||||
// left shift or the right shift that needs to get the fake
|
||||
// release (or press) event so we don't forward this
|
||||
// event to the application at all.
|
||||
//
|
||||
// For more on this, read the article by Raymond Chen, titled:
|
||||
// "The shift key overrides NumLock"
|
||||
// https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
Some(physical_key)
|
||||
}
|
||||
340
winit-win32/src/util.rs
Normal file
340
winit-win32/src/util.rs
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
use std::ffi::{c_void, OsStr, OsString};
|
||||
use std::iter::once;
|
||||
use std::ops::BitAnd;
|
||||
use std::os::windows::prelude::{OsStrExt, OsStringExt};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::LazyLock;
|
||||
use std::{io, mem, ptr};
|
||||
|
||||
use windows_sys::core::{HRESULT, PCWSTR};
|
||||
use windows_sys::Win32::Foundation::{BOOL, HANDLE, HMODULE, HWND, POINT, RECT};
|
||||
use windows_sys::Win32::Graphics::Gdi::{ClientToScreen, HMONITOR};
|
||||
use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
|
||||
use windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER;
|
||||
use windows_sys::Win32::UI::HiDpi::{
|
||||
DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS,
|
||||
};
|
||||
use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow;
|
||||
use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_TOUCH_INFO};
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowPlacement,
|
||||
GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP,
|
||||
IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT,
|
||||
SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE,
|
||||
WINDOWPLACEMENT, WINDOW_LONG_PTR_INDEX,
|
||||
};
|
||||
use winit_core::cursor::CursorIcon;
|
||||
use winit_core::event::DeviceId;
|
||||
|
||||
macro_rules! os_error {
|
||||
($error:expr) => {{
|
||||
winit_core::error::OsError::new(line!(), file!(), $error)
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
|
||||
string.as_ref().encode_wide().chain(once(0)).collect()
|
||||
}
|
||||
|
||||
pub fn decode_wide(mut wide_c_string: &[u16]) -> OsString {
|
||||
if let Some(null_pos) = wide_c_string.iter().position(|c| *c == 0) {
|
||||
wide_c_string = &wide_c_string[..null_pos];
|
||||
}
|
||||
|
||||
OsString::from_wide(wide_c_string)
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
pub(crate) fn win_to_err(result: BOOL) -> Result<(), io::Error> {
|
||||
if result != false.into() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
pub enum WindowArea {
|
||||
Outer,
|
||||
Inner,
|
||||
}
|
||||
|
||||
impl WindowArea {
|
||||
pub fn get_rect(self, hwnd: HWND) -> Result<RECT, io::Error> {
|
||||
let mut rect = unsafe { mem::zeroed() };
|
||||
|
||||
match self {
|
||||
WindowArea::Outer => {
|
||||
win_to_err(unsafe { GetWindowRect(hwnd, &mut rect) })?;
|
||||
},
|
||||
WindowArea::Inner => unsafe {
|
||||
let mut top_left = mem::zeroed();
|
||||
|
||||
win_to_err(ClientToScreen(hwnd, &mut top_left))?;
|
||||
win_to_err(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 is_maximized(window: HWND) -> bool {
|
||||
unsafe {
|
||||
let mut placement: WINDOWPLACEMENT = mem::zeroed();
|
||||
placement.length = mem::size_of::<WINDOWPLACEMENT>() as u32;
|
||||
GetWindowPlacement(window, &mut placement);
|
||||
placement.showCmd == SW_MAXIMIZE as u32
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_cursor_hidden(hidden: bool) {
|
||||
static HIDDEN: AtomicBool = AtomicBool::new(false);
|
||||
let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden;
|
||||
if changed {
|
||||
unsafe { ShowCursor(BOOL::from(!hidden)) };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cursor_position() -> Result<POINT, io::Error> {
|
||||
unsafe {
|
||||
let mut point: POINT = mem::zeroed();
|
||||
win_to_err(GetCursorPos(&mut point)).map(|_| point)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_cursor_clip() -> Result<RECT, io::Error> {
|
||||
unsafe {
|
||||
let mut rect: RECT = mem::zeroed();
|
||||
win_to_err(GetClipCursor(&mut rect)).map(|_| rect)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the cursor's clip rect.
|
||||
///
|
||||
/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event.
|
||||
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(ClipCursor(rect_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_desktop_rect() -> RECT {
|
||||
unsafe {
|
||||
let left = GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||||
let top = GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||
RECT {
|
||||
left,
|
||||
top,
|
||||
right: left + GetSystemMetrics(SM_CXVIRTUALSCREEN),
|
||||
bottom: top + GetSystemMetrics(SM_CYVIRTUALSCREEN),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_focused(window: HWND) -> bool {
|
||||
window == unsafe { GetActiveWindow() }
|
||||
}
|
||||
|
||||
pub fn is_minimized(window: HWND) -> bool {
|
||||
unsafe { IsIconic(window) != false.into() }
|
||||
}
|
||||
|
||||
pub fn get_instance_handle() -> HMODULE {
|
||||
// Gets the instance handle by taking the address of the
|
||||
// pseudo-variable created by the microsoft linker:
|
||||
// https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483
|
||||
|
||||
// This is preferred over GetModuleHandle(NULL) because it also works in DLLs:
|
||||
// https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance
|
||||
|
||||
extern "C" {
|
||||
static __ImageBase: IMAGE_DOS_HEADER;
|
||||
}
|
||||
|
||||
unsafe { &__ImageBase as *const _ as _ }
|
||||
}
|
||||
|
||||
pub(crate) fn to_windows_cursor(cursor: CursorIcon) -> PCWSTR {
|
||||
match cursor {
|
||||
CursorIcon::Default => IDC_ARROW,
|
||||
CursorIcon::Pointer => IDC_HAND,
|
||||
CursorIcon::Crosshair => IDC_CROSS,
|
||||
CursorIcon::Text | CursorIcon::VerticalText => IDC_IBEAM,
|
||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => IDC_NO,
|
||||
CursorIcon::Grab | CursorIcon::Grabbing | CursorIcon::Move | CursorIcon::AllScroll => {
|
||||
IDC_SIZEALL
|
||||
},
|
||||
CursorIcon::EResize
|
||||
| CursorIcon::WResize
|
||||
| CursorIcon::EwResize
|
||||
| CursorIcon::ColResize => IDC_SIZEWE,
|
||||
CursorIcon::NResize
|
||||
| CursorIcon::SResize
|
||||
| CursorIcon::NsResize
|
||||
| CursorIcon::RowResize => IDC_SIZENS,
|
||||
CursorIcon::NeResize | CursorIcon::SwResize | CursorIcon::NeswResize => IDC_SIZENESW,
|
||||
CursorIcon::NwResize | CursorIcon::SeResize | CursorIcon::NwseResize => IDC_SIZENWSE,
|
||||
CursorIcon::Wait => IDC_WAIT,
|
||||
CursorIcon::Progress => IDC_APPSTARTING,
|
||||
CursorIcon::Help => IDC_HELP,
|
||||
_ => IDC_ARROW, // use arrow for the missing cases.
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to dynamically load function pointer as some functions
|
||||
// may not be available on all Windows platforms supported by winit.
|
||||
//
|
||||
// `library` and `function` must be zero-terminated.
|
||||
pub(super) 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()) };
|
||||
if module.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe { GetProcAddress(module, function.as_ptr()) }.map(|function_ptr| function_ptr as _)
|
||||
}
|
||||
|
||||
macro_rules! get_function {
|
||||
($lib:expr, $func:ident) => {
|
||||
crate::util::get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
|
||||
.map(|f| unsafe { std::mem::transmute::<*const _, $func>(f) })
|
||||
};
|
||||
}
|
||||
|
||||
pub type SetProcessDPIAware = unsafe extern "system" fn() -> BOOL;
|
||||
pub type SetProcessDpiAwareness =
|
||||
unsafe extern "system" fn(value: PROCESS_DPI_AWARENESS) -> HRESULT;
|
||||
pub type SetProcessDpiAwarenessContext =
|
||||
unsafe extern "system" fn(value: DPI_AWARENESS_CONTEXT) -> BOOL;
|
||||
pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
|
||||
pub type GetDpiForMonitor = unsafe extern "system" fn(
|
||||
hmonitor: HMONITOR,
|
||||
dpi_type: MONITOR_DPI_TYPE,
|
||||
dpi_x: *mut u32,
|
||||
dpi_y: *mut u32,
|
||||
) -> HRESULT;
|
||||
pub type EnableNonClientDpiScaling = unsafe extern "system" fn(hwnd: HWND) -> BOOL;
|
||||
pub type AdjustWindowRectExForDpi = unsafe extern "system" fn(
|
||||
rect: *mut RECT,
|
||||
dw_style: u32,
|
||||
b_menu: BOOL,
|
||||
dw_ex_style: u32,
|
||||
dpi: u32,
|
||||
) -> BOOL;
|
||||
|
||||
pub type GetPointerFrameInfoHistory = unsafe extern "system" fn(
|
||||
pointer_id: u32,
|
||||
entries_count: *mut u32,
|
||||
pointer_count: *mut u32,
|
||||
pointer_info: *mut POINTER_INFO,
|
||||
) -> BOOL;
|
||||
|
||||
pub type SkipPointerFrameMessages = unsafe extern "system" fn(pointer_id: u32) -> BOOL;
|
||||
pub type GetPointerDeviceRects = unsafe extern "system" fn(
|
||||
device: HANDLE,
|
||||
pointer_device_rect: *mut RECT,
|
||||
display_rect: *mut RECT,
|
||||
) -> BOOL;
|
||||
|
||||
pub type GetPointerTouchInfo =
|
||||
unsafe extern "system" fn(pointer_id: u32, touch_info: *mut POINTER_TOUCH_INFO) -> BOOL;
|
||||
|
||||
pub(crate) static GET_DPI_FOR_WINDOW: LazyLock<Option<GetDpiForWindow>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||
pub(crate) static ADJUST_WINDOW_RECT_EX_FOR_DPI: LazyLock<Option<AdjustWindowRectExForDpi>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", AdjustWindowRectExForDpi));
|
||||
pub(crate) static GET_DPI_FOR_MONITOR: LazyLock<Option<GetDpiForMonitor>> =
|
||||
LazyLock::new(|| get_function!("shcore.dll", GetDpiForMonitor));
|
||||
pub(crate) static ENABLE_NON_CLIENT_DPI_SCALING: LazyLock<Option<EnableNonClientDpiScaling>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", EnableNonClientDpiScaling));
|
||||
pub(crate) static SET_PROCESS_DPI_AWARENESS_CONTEXT: LazyLock<
|
||||
Option<SetProcessDpiAwarenessContext>,
|
||||
> = LazyLock::new(|| get_function!("user32.dll", SetProcessDpiAwarenessContext));
|
||||
pub(crate) static SET_PROCESS_DPI_AWARENESS: LazyLock<Option<SetProcessDpiAwareness>> =
|
||||
LazyLock::new(|| get_function!("shcore.dll", SetProcessDpiAwareness));
|
||||
pub(crate) static SET_PROCESS_DPI_AWARE: LazyLock<Option<SetProcessDPIAware>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", SetProcessDPIAware));
|
||||
pub(crate) static GET_POINTER_FRAME_INFO_HISTORY: LazyLock<Option<GetPointerFrameInfoHistory>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", GetPointerFrameInfoHistory));
|
||||
pub(crate) static SKIP_POINTER_FRAME_MESSAGES: LazyLock<Option<SkipPointerFrameMessages>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", SkipPointerFrameMessages));
|
||||
pub(crate) static GET_POINTER_DEVICE_RECTS: LazyLock<Option<GetPointerDeviceRects>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", GetPointerDeviceRects));
|
||||
pub(crate) static GET_POINTER_TOUCH_INFO: LazyLock<Option<GetPointerTouchInfo>> =
|
||||
LazyLock::new(|| get_function!("user32.dll", GetPointerTouchInfo));
|
||||
|
||||
pub(crate) fn wrap_device_id(id: u32) -> DeviceId {
|
||||
DeviceId::from_raw(id as i64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) const fn get_xbutton_wparam(x: u32) -> u16 {
|
||||
hiword(x)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) const fn get_x_lparam(x: u32) -> i16 {
|
||||
loword(x) as _
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) const fn get_y_lparam(x: u32) -> i16 {
|
||||
hiword(x) as _
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) const fn primarylangid(lgid: u16) -> u16 {
|
||||
lgid & 0x3ff
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) const fn loword(x: u32) -> u16 {
|
||||
(x & 0xffff) as u16
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) const fn hiword(x: u32) -> u16 {
|
||||
((x >> 16) & 0xffff) as u16
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
return unsafe { windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW(hwnd, nindex) };
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
return unsafe {
|
||||
windows_sys::Win32::UI::WindowsAndMessaging::GetWindowLongW(hwnd, nindex) as isize
|
||||
};
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) unsafe fn set_window_long(
|
||||
hwnd: HWND,
|
||||
nindex: WINDOW_LONG_PTR_INDEX,
|
||||
dwnewlong: isize,
|
||||
) -> isize {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
return unsafe {
|
||||
windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW(hwnd, nindex, dwnewlong)
|
||||
};
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
return unsafe {
|
||||
windows_sys::Win32::UI::WindowsAndMessaging::SetWindowLongW(hwnd, nindex, dwnewlong as i32)
|
||||
as isize
|
||||
};
|
||||
}
|
||||
1589
winit-win32/src/window.rs
Normal file
1589
winit-win32/src/window.rs
Normal file
File diff suppressed because it is too large
Load diff
555
winit-win32/src/window_state.rs
Normal file
555
winit-win32/src/window_state.rs
Normal file
|
|
@ -0,0 +1,555 @@
|
|||
use std::sync::MutexGuard;
|
||||
use std::{fmt, io, ptr};
|
||||
|
||||
use bitflags::bitflags;
|
||||
use dpi::{PhysicalPosition, PhysicalSize, Size};
|
||||
use windows_sys::Win32::Foundation::{HWND, RECT};
|
||||
use windows_sys::Win32::Graphics::Gdi::InvalidateRgn;
|
||||
use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
AdjustWindowRectEx, EnableMenuItem, GetMenu, GetSystemMenu, GetWindowLongW, SendMessageW,
|
||||
SetWindowLongW, SetWindowPos, ShowWindow, GWL_EXSTYLE, GWL_STYLE, HWND_BOTTOM, HWND_NOTOPMOST,
|
||||
HWND_TOPMOST, MF_BYCOMMAND, MF_DISABLED, MF_ENABLED, SC_CLOSE, SWP_ASYNCWINDOWPOS,
|
||||
SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER,
|
||||
SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, SW_SHOWNOACTIVATE, WINDOWPLACEMENT,
|
||||
WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, WS_CLIPCHILDREN,
|
||||
WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOREDIRECTIONBITMAP,
|
||||
WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE,
|
||||
WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE,
|
||||
};
|
||||
use winit_core::icon::Icon;
|
||||
use winit_core::keyboard::ModifiersState;
|
||||
use winit_core::monitor::Fullscreen;
|
||||
use winit_core::window::{Theme, WindowAttributes};
|
||||
|
||||
use crate::{event_loop, util, SelectedCursor};
|
||||
|
||||
/// Contains information about states and the window that the callback is going to use.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct WindowState {
|
||||
pub mouse: MouseProperties,
|
||||
|
||||
/// Used by `WM_GETMINMAXINFO`.
|
||||
pub min_size: Option<Size>,
|
||||
pub max_size: Option<Size>,
|
||||
|
||||
pub surface_resize_increments: Option<Size>,
|
||||
|
||||
pub window_icon: Option<Icon>,
|
||||
pub taskbar_icon: Option<Icon>,
|
||||
|
||||
pub saved_window: Option<SavedWindow>,
|
||||
pub scale_factor: f64,
|
||||
|
||||
pub modifiers_state: ModifiersState,
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
pub current_theme: Theme,
|
||||
pub preferred_theme: Option<Theme>,
|
||||
|
||||
pub window_flags: WindowFlags,
|
||||
|
||||
pub ime_state: ImeState,
|
||||
pub ime_allowed: bool,
|
||||
|
||||
// Used by WM_NCACTIVATE, WM_SETFOCUS and WM_KILLFOCUS
|
||||
pub is_active: bool,
|
||||
pub is_focused: bool,
|
||||
|
||||
// Flag whether redraw was requested.
|
||||
pub redraw_requested: bool,
|
||||
|
||||
pub dragging: bool,
|
||||
|
||||
pub skip_taskbar: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SavedWindow {
|
||||
pub placement: WINDOWPLACEMENT,
|
||||
}
|
||||
|
||||
impl fmt::Debug for SavedWindow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SavedWindow").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MouseProperties {
|
||||
pub(crate) selected_cursor: SelectedCursor,
|
||||
pub capture_count: u32,
|
||||
cursor_flags: CursorFlags,
|
||||
pub last_position: Option<PhysicalPosition<f64>>,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct CursorFlags: u8 {
|
||||
const GRABBED = 1 << 0;
|
||||
const HIDDEN = 1 << 1;
|
||||
const IN_WINDOW = 1 << 2;
|
||||
const LOCKED = 1 << 3;
|
||||
}
|
||||
}
|
||||
bitflags! {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct WindowFlags: u32 {
|
||||
const RESIZABLE = 1 << 0;
|
||||
const MINIMIZABLE = 1 << 1;
|
||||
const MAXIMIZABLE = 1 << 2;
|
||||
const CLOSABLE = 1 << 3;
|
||||
const VISIBLE = 1 << 4;
|
||||
const ON_TASKBAR = 1 << 5;
|
||||
const ALWAYS_ON_TOP = 1 << 6;
|
||||
const ALWAYS_ON_BOTTOM = 1 << 7;
|
||||
const NO_BACK_BUFFER = 1 << 8;
|
||||
const TRANSPARENT = 1 << 9;
|
||||
const CHILD = 1 << 10;
|
||||
const MAXIMIZED = 1 << 11;
|
||||
const POPUP = 1 << 12;
|
||||
|
||||
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
|
||||
/// included here to make masking easier.
|
||||
const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 13;
|
||||
const MARKER_BORDERLESS_FULLSCREEN = 1 << 14;
|
||||
|
||||
/// 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 << 15;
|
||||
|
||||
const MARKER_IN_SIZE_MOVE = 1 << 16;
|
||||
|
||||
const MINIMIZED = 1 << 17;
|
||||
|
||||
const IGNORE_CURSOR_EVENT = 1 << 18;
|
||||
|
||||
/// Fully decorated window (incl. caption, border and drop shadow).
|
||||
const MARKER_DECORATIONS = 1 << 19;
|
||||
/// Drop shadow for undecorated windows.
|
||||
const MARKER_UNDECORATED_SHADOW = 1 << 20;
|
||||
|
||||
const MARKER_ACTIVATE = 1 << 21;
|
||||
|
||||
const CLIP_CHILDREN = 1 << 22;
|
||||
|
||||
const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash)]
|
||||
pub enum ImeState {
|
||||
Disabled,
|
||||
Enabled,
|
||||
Preedit,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
pub(crate) fn new(
|
||||
attributes: &WindowAttributes,
|
||||
scale_factor: f64,
|
||||
current_theme: Theme,
|
||||
preferred_theme: Option<Theme>,
|
||||
) -> WindowState {
|
||||
WindowState {
|
||||
mouse: MouseProperties {
|
||||
selected_cursor: SelectedCursor::default(),
|
||||
capture_count: 0,
|
||||
cursor_flags: CursorFlags::empty(),
|
||||
last_position: None,
|
||||
},
|
||||
|
||||
min_size: attributes.min_surface_size,
|
||||
max_size: attributes.max_surface_size,
|
||||
|
||||
surface_resize_increments: attributes.surface_resize_increments,
|
||||
|
||||
window_icon: attributes.window_icon.clone(),
|
||||
taskbar_icon: None,
|
||||
|
||||
saved_window: None,
|
||||
scale_factor,
|
||||
|
||||
modifiers_state: ModifiersState::default(),
|
||||
fullscreen: None,
|
||||
current_theme,
|
||||
preferred_theme,
|
||||
window_flags: WindowFlags::empty(),
|
||||
|
||||
ime_state: ImeState::Disabled,
|
||||
ime_allowed: false,
|
||||
|
||||
is_active: false,
|
||||
is_focused: false,
|
||||
redraw_requested: false,
|
||||
|
||||
dragging: false,
|
||||
|
||||
skip_taskbar: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window_flags(&self) -> WindowFlags {
|
||||
self.window_flags
|
||||
}
|
||||
|
||||
pub fn set_window_flags<F>(mut this: MutexGuard<'_, Self>, window: HWND, f: F)
|
||||
where
|
||||
F: FnOnce(&mut WindowFlags),
|
||||
{
|
||||
let old_flags = this.window_flags;
|
||||
f(&mut this.window_flags);
|
||||
let new_flags = this.window_flags;
|
||||
|
||||
drop(this);
|
||||
old_flags.apply_diff(window, new_flags);
|
||||
}
|
||||
|
||||
pub fn set_window_flags_in_place<F>(&mut self, f: F)
|
||||
where
|
||||
F: FnOnce(&mut WindowFlags),
|
||||
{
|
||||
f(&mut self.window_flags);
|
||||
}
|
||||
|
||||
pub fn has_active_focus(&self) -> bool {
|
||||
self.is_active && self.is_focused
|
||||
}
|
||||
|
||||
// Updates is_active and returns whether active-focus state has changed
|
||||
pub fn set_active(&mut self, is_active: bool) -> bool {
|
||||
let old = self.has_active_focus();
|
||||
self.is_active = is_active;
|
||||
old != self.has_active_focus()
|
||||
}
|
||||
|
||||
// Updates is_focused and returns whether active-focus state has changed
|
||||
pub fn set_focused(&mut self, is_focused: bool) -> bool {
|
||||
let old = self.has_active_focus();
|
||||
self.is_focused = is_focused;
|
||||
old != self.has_active_focus()
|
||||
}
|
||||
}
|
||||
|
||||
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_EXCLUSIVE_FULLSCREEN) {
|
||||
self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) {
|
||||
// Required styles to properly support common window functionality like aero snap.
|
||||
let mut style = WS_CAPTION | WS_BORDER | WS_CLIPSIBLINGS | WS_SYSMENU;
|
||||
let mut style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES;
|
||||
|
||||
if self.contains(WindowFlags::RESIZABLE) {
|
||||
style |= WS_SIZEBOX;
|
||||
}
|
||||
if self.contains(WindowFlags::MAXIMIZABLE) {
|
||||
style |= WS_MAXIMIZEBOX;
|
||||
}
|
||||
if self.contains(WindowFlags::MINIMIZABLE) {
|
||||
style |= WS_MINIMIZEBOX;
|
||||
}
|
||||
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::CHILD) {
|
||||
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
|
||||
|
||||
// Remove decorations window styles for child
|
||||
if !self.contains(WindowFlags::MARKER_DECORATIONS) {
|
||||
style &= !(WS_CAPTION | WS_BORDER);
|
||||
style_ex &= !WS_EX_WINDOWEDGE;
|
||||
}
|
||||
}
|
||||
if self.contains(WindowFlags::POPUP) {
|
||||
style |= WS_POPUP;
|
||||
}
|
||||
if self.contains(WindowFlags::MINIMIZED) {
|
||||
style |= WS_MINIMIZE;
|
||||
}
|
||||
if self.contains(WindowFlags::MAXIMIZED) {
|
||||
style |= WS_MAXIMIZE;
|
||||
}
|
||||
if self.contains(WindowFlags::IGNORE_CURSOR_EVENT) {
|
||||
style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED;
|
||||
}
|
||||
if self.contains(WindowFlags::CLIP_CHILDREN) {
|
||||
style |= WS_CLIPCHILDREN;
|
||||
}
|
||||
|
||||
if self.intersects(
|
||||
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN,
|
||||
) {
|
||||
style &= !WS_OVERLAPPEDWINDOW;
|
||||
}
|
||||
|
||||
(style, style_ex)
|
||||
}
|
||||
|
||||
/// Adjust the window client rectangle to the return value, if present.
|
||||
fn apply_diff(mut self, window: HWND, mut new: WindowFlags) {
|
||||
self = self.mask();
|
||||
new = new.mask();
|
||||
|
||||
let mut diff = self ^ new;
|
||||
|
||||
if diff == WindowFlags::empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if new.contains(WindowFlags::VISIBLE) {
|
||||
let flag = if !self.contains(WindowFlags::MARKER_ACTIVATE) {
|
||||
self.set(WindowFlags::MARKER_ACTIVATE, true);
|
||||
SW_SHOWNOACTIVATE
|
||||
} else {
|
||||
SW_SHOW
|
||||
};
|
||||
unsafe {
|
||||
ShowWindow(window, flag);
|
||||
}
|
||||
}
|
||||
|
||||
if diff.intersects(WindowFlags::ALWAYS_ON_TOP | WindowFlags::ALWAYS_ON_BOTTOM) {
|
||||
unsafe {
|
||||
SetWindowPos(
|
||||
window,
|
||||
match (
|
||||
new.contains(WindowFlags::ALWAYS_ON_TOP),
|
||||
new.contains(WindowFlags::ALWAYS_ON_BOTTOM),
|
||||
) {
|
||||
(true, false) => HWND_TOPMOST,
|
||||
(false, false) => HWND_NOTOPMOST,
|
||||
(false, true) => HWND_BOTTOM,
|
||||
(true, true) => unreachable!(),
|
||||
},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
|
||||
);
|
||||
InvalidateRgn(window, ptr::null_mut(), false.into());
|
||||
}
|
||||
}
|
||||
|
||||
if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) {
|
||||
unsafe {
|
||||
ShowWindow(window, match new.contains(WindowFlags::MAXIMIZED) {
|
||||
true => SW_MAXIMIZE,
|
||||
false => SW_RESTORE,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Minimize operations should execute after maximize for proper window animations
|
||||
if diff.contains(WindowFlags::MINIMIZED) {
|
||||
unsafe {
|
||||
ShowWindow(window, match new.contains(WindowFlags::MINIMIZED) {
|
||||
true => SW_MINIMIZE,
|
||||
false => SW_RESTORE,
|
||||
});
|
||||
}
|
||||
|
||||
diff.remove(WindowFlags::MINIMIZED);
|
||||
}
|
||||
|
||||
if diff.contains(WindowFlags::CLOSABLE) || new.contains(WindowFlags::CLOSABLE) {
|
||||
let flags = MF_BYCOMMAND
|
||||
| if new.contains(WindowFlags::CLOSABLE) { MF_ENABLED } else { MF_DISABLED };
|
||||
|
||||
unsafe {
|
||||
EnableMenuItem(GetSystemMenu(window, 0), SC_CLOSE, flags);
|
||||
}
|
||||
}
|
||||
|
||||
if !new.contains(WindowFlags::VISIBLE) {
|
||||
unsafe {
|
||||
ShowWindow(window, SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
if diff != WindowFlags::empty() {
|
||||
let (style, style_ex) = new.to_window_styles();
|
||||
|
||||
unsafe {
|
||||
SendMessageW(window, event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID.get(), 1, 0);
|
||||
|
||||
// This condition is necessary to avoid having an unrestorable window
|
||||
if !new.contains(WindowFlags::MINIMIZED) {
|
||||
SetWindowLongW(window, GWL_STYLE, style as i32);
|
||||
SetWindowLongW(window, GWL_EXSTYLE, style_ex as i32);
|
||||
}
|
||||
|
||||
let mut flags = SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED;
|
||||
|
||||
// We generally don't want style changes here to affect window
|
||||
// focus, but for fullscreen windows they must be activated
|
||||
// (i.e. focused) so that they appear on top of the taskbar
|
||||
if !new.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN)
|
||||
&& !new.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN)
|
||||
{
|
||||
flags |= SWP_NOACTIVATE;
|
||||
}
|
||||
|
||||
// Refresh the window frame
|
||||
SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags);
|
||||
SendMessageW(window, event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID.get(), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_rect(self, hwnd: HWND, mut rect: RECT) -> Result<RECT, io::Error> {
|
||||
unsafe {
|
||||
let mut style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
|
||||
let style_ex = GetWindowLongW(hwnd, GWL_EXSTYLE) as u32;
|
||||
|
||||
// Frameless style implemented by manually overriding the non-client area in
|
||||
// `WM_NCCALCSIZE`.
|
||||
if !self.contains(WindowFlags::MARKER_DECORATIONS) {
|
||||
style &= !(WS_CAPTION | WS_SIZEBOX);
|
||||
}
|
||||
|
||||
util::win_to_err({
|
||||
let b_menu = !GetMenu(hwnd).is_null();
|
||||
if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) =
|
||||
(*util::GET_DPI_FOR_WINDOW, *util::ADJUST_WINDOW_RECT_EX_FOR_DPI)
|
||||
{
|
||||
let dpi = get_dpi_for_window(hwnd);
|
||||
adjust_window_rect_ex_for_dpi(&mut rect, style, b_menu.into(), style_ex, dpi)
|
||||
} else {
|
||||
AdjustWindowRectEx(&mut rect, style, b_menu.into(), style_ex)
|
||||
}
|
||||
})?;
|
||||
Ok(rect)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_size(self, hwnd: HWND, size: PhysicalSize<u32>) -> PhysicalSize<u32> {
|
||||
let (width, height): (u32, u32) = size.into();
|
||||
let rect = RECT { left: 0, right: width as i32, top: 0, bottom: height as i32 };
|
||||
let rect = self.adjust_rect(hwnd, rect).unwrap_or(rect);
|
||||
|
||||
let outer_x = (rect.right - rect.left).abs();
|
||||
let outer_y = (rect.top - rect.bottom).abs();
|
||||
|
||||
PhysicalSize::new(outer_x as _, outer_y as _)
|
||||
}
|
||||
|
||||
pub fn set_size(self, hwnd: HWND, size: PhysicalSize<u32>) {
|
||||
unsafe {
|
||||
let (width, height): (u32, u32) = self.adjust_size(hwnd, size).into();
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
width as _,
|
||||
height as _,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE,
|
||||
);
|
||||
InvalidateRgn(hwnd, ptr::null_mut(), false.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorFlags {
|
||||
fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> {
|
||||
let client_rect = util::WindowArea::Inner.get_rect(window)?;
|
||||
|
||||
if util::is_focused(window) {
|
||||
let cursor_clip = match self.contains(CursorFlags::GRABBED) {
|
||||
true => {
|
||||
if self.contains(CursorFlags::LOCKED) {
|
||||
if let Ok(pos) = util::get_cursor_position() {
|
||||
Some(RECT {
|
||||
left: pos.x,
|
||||
right: pos.x + 1,
|
||||
top: pos.y,
|
||||
bottom: pos.y + 1,
|
||||
})
|
||||
} else {
|
||||
// If lock is applied while the cursor is not available, lock it to the
|
||||
// middle of the window.
|
||||
let cx = (client_rect.left + client_rect.right) / 2;
|
||||
let cy = (client_rect.top + client_rect.bottom) / 2;
|
||||
Some(RECT { left: cx, right: cx + 1, top: cy, bottom: cy + 1 })
|
||||
}
|
||||
} else if self.contains(CursorFlags::HIDDEN) {
|
||||
// Confine the cursor to the center of the window if the cursor is hidden.
|
||||
// This avoids problems with the cursor activating
|
||||
// the taskbar if the window borders or overlaps that.
|
||||
let cx = (client_rect.left + client_rect.right) / 2;
|
||||
let cy = (client_rect.top + client_rect.bottom) / 2;
|
||||
Some(RECT { left: cx, right: cx + 1, top: cy, bottom: cy + 1 })
|
||||
} else {
|
||||
Some(client_rect)
|
||||
}
|
||||
},
|
||||
false => None,
|
||||
};
|
||||
|
||||
let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom);
|
||||
let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?);
|
||||
let desktop_rect = rect_to_tuple(util::get_desktop_rect());
|
||||
|
||||
let active_cursor_clip = match desktop_rect == active_cursor_clip {
|
||||
true => None,
|
||||
false => Some(active_cursor_clip),
|
||||
};
|
||||
|
||||
// We do this check because calling `set_cursor_clip` incessantly will flood the event
|
||||
// loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by
|
||||
// `set_cursor_flags` which at times gets called once every iteration of the
|
||||
// eventloop.
|
||||
if active_cursor_clip != cursor_clip.map(rect_to_tuple) {
|
||||
util::set_cursor_clip(cursor_clip)?;
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue