diff --git a/Cargo.toml b/Cargo.toml index e842f0c4..05a09fa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ windows = { version = "=0.61.3", features = [ windows-sys = { version = "0.61.2", features = [ "Win32_Graphics_Gdi", "Win32_Graphics_OpenGL", - "Win32_System_Com", + "Win32_System_Rpc", "Win32_System_LibraryLoader", "Win32_System_Ole", "Win32_System_SystemServices", diff --git a/src/win/window.rs b/src/win/window.rs index 8af3d8fc..8310bc38 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -1,10 +1,7 @@ use windows_core::{ComObject, Interface}; use windows_sys::Win32::{ Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM}, - System::{ - Com::CoCreateGuid, - Ole::{OleInitialize, RevokeDragDrop}, - }, + System::Ole::{OleInitialize, RevokeDragDrop}, UI::{ Controls::{HOVER_DEFAULT, WM_MOUSELEAVE}, HiDpi::{ @@ -16,16 +13,15 @@ use windows_sys::Win32::{ }, WindowsAndMessaging::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, - GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW, SetCursor, - SetTimer, SetWindowLongPtrW, SetWindowPos, TranslateMessage, UnregisterClassW, - CS_OWNDC, GWLP_USERDATA, HTCLIENT, IDC_ARROW, MSG, SWP_NOACTIVATE, SWP_NOMOVE, - SWP_NOZORDER, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DPICHANGED, - WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, - WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCDESTROY, - WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, - WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, - WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, - WS_SIZEBOX, WS_VISIBLE, + GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, SetCursor, SetTimer, + SetWindowLongPtrW, SetWindowPos, TranslateMessage, GWLP_USERDATA, HTCLIENT, MSG, + SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOZORDER, WHEEL_DELTA, WM_CHAR, WM_CLOSE, WM_CREATE, + WM_DPICHANGED, WM_INPUTLANGCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, + WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, + WM_NCDESTROY, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SHOWWINDOW, WM_SIZE, + WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, + WM_XBUTTONUP, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, + WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, }, }, }; @@ -42,7 +38,6 @@ use raw_window_handle::{ WindowsDisplayHandle, }; use windows::Win32::System::Ole::IDropTarget; -use windows_sys::core::GUID; use windows_sys::Win32::Foundation::FALSE; use windows_sys::Win32::System::Ole::RegisterDragDrop; @@ -60,25 +55,8 @@ use super::keyboard::KeyboardState; #[cfg(feature = "opengl")] use crate::gl::GlContext; - -unsafe fn generate_guid() -> String { - let mut guid: GUID = std::mem::zeroed(); - CoCreateGuid(&mut guid); - format!( - "{:0X}-{:0X}-{:0X}-{:0X}{:0X}-{:0X}{:0X}{:0X}{:0X}{:0X}{:0X}\0", - guid.data1, - guid.data2, - guid.data3, - guid.data4[0], - guid.data4[1], - guid.data4[2], - guid.data4[3], - guid.data4[4], - guid.data4[5], - guid.data4[6], - guid.data4[7] - ) -} +use crate::wrappers::win32::h_instance::HInstance; +use crate::wrappers::win32::window_class::RegisteredClass; #[allow(non_snake_case)] fn HIWORD(wparam: WPARAM) -> u16 { @@ -174,7 +152,7 @@ pub(crate) unsafe extern "system" fn wnd_proc( // NOTE: This is not handled in `wnd_proc_inner` because of the deferred task loop above if msg == WM_NCDESTROY { RevokeDragDrop(hwnd); - unregister_wnd_class((*window_state_ptr).window_class); + let _ = (*window_state_ptr).window_class.take(); SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); drop(Rc::from_raw(window_state_ptr)); } @@ -497,35 +475,6 @@ unsafe fn wnd_proc_inner( } } -#[allow(clippy::upper_case_acronyms)] -type ATOM = u16; - -unsafe fn register_wnd_class() -> ATOM { - // We generate a unique name for the new window class to prevent name collisions - let class_name_str = format!("Baseview-{}", generate_guid()); - let mut class_name: Vec = OsStr::new(&class_name_str).encode_wide().collect(); - class_name.push(0); - - let wnd_class = WNDCLASSW { - style: CS_OWNDC, - lpfnWndProc: Some(wnd_proc), - hInstance: null_mut(), - lpszClassName: class_name.as_ptr(), - cbClsExtra: 0, - cbWndExtra: 0, - hIcon: null_mut(), - hCursor: LoadCursorW(null_mut(), IDC_ARROW), - hbrBackground: null_mut(), - lpszMenuName: null_mut(), - }; - - RegisterClassW(&wnd_class) -} - -unsafe fn unregister_wnd_class(wnd_class: ATOM) { - UnregisterClassW(wnd_class as _, null_mut()); -} - /// All data associated with the window. This uses internal mutability so the outer struct doesn't /// need to be mutably borrowed. Mutably borrowing the entire `WindowState` can be problematic /// because of the Windows message loops' reentrant nature. Care still needs to be taken to prevent @@ -536,7 +485,7 @@ pub(super) struct WindowState { /// struct associated with this HWND through `unsafe { GetWindowLongPtrW(self.hwnd, /// GWLP_USERDATA) } as *const WindowState`. pub hwnd: HWND, - window_class: ATOM, + window_class: Cell>, current_size: Cell, current_scale_factor: Cell, _parent_handle: Option, @@ -680,12 +629,13 @@ impl Window<'_> { B: FnOnce(&mut crate::Window) -> H, B: Send + 'static, { + let instance = HInstance::get(); + unsafe { let mut title: Vec = OsStr::new(&options.title[..]).encode_wide().collect(); title.push(0); - let window_class = register_wnd_class(); - // todo: manage error ^ + let window_class = RegisteredClass::register_new(instance, Some(wnd_proc)).unwrap(); let scaling = match options.scale { WindowScalePolicy::SystemScaleFactor => 1.0, @@ -720,7 +670,7 @@ impl Window<'_> { let hwnd = CreateWindowExW( 0, - window_class as _, + window_class.as_atom_ptr(), title.as_ptr(), flags, 0, @@ -750,7 +700,7 @@ impl Window<'_> { let window_state = Rc::new(WindowState { hwnd, - window_class, + window_class: Some(window_class).into(), current_scale_factor: scaling.into(), current_size: current_size.into(), _parent_handle: parent_handle, diff --git a/src/wrappers.rs b/src/wrappers.rs index a392a348..abb9280f 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -18,3 +18,6 @@ pub mod xlib; /// Wrappers and utilities around GLX #[cfg(all(target_os = "linux", feature = "opengl"))] pub mod glx; + +#[cfg(target_os = "windows")] +pub mod win32; diff --git a/src/wrappers/win32.rs b/src/wrappers/win32.rs new file mode 100644 index 00000000..957fd141 --- /dev/null +++ b/src/wrappers/win32.rs @@ -0,0 +1,3 @@ +pub mod h_instance; +pub mod uuid; +pub mod window_class; diff --git a/src/wrappers/win32/h_instance.rs b/src/wrappers/win32/h_instance.rs new file mode 100644 index 00000000..8f2ee843 --- /dev/null +++ b/src/wrappers/win32/h_instance.rs @@ -0,0 +1,34 @@ +use std::ptr::null_mut; +use windows_core::Error; +use windows_sys::Win32::Foundation::HINSTANCE; +use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW; + +#[derive(Copy, Clone, PartialEq)] +pub struct HInstance(HINSTANCE); + +// SAFETY: This is actually a pointer to the memory image of the executable file. It is guaranteed +// to be valid for the process's lifetime. +// This getting invalidated would imply our own executable has been unloaded already. At that point, +// pointer invalidation would the least of our concerns anyway. +unsafe impl Send for HInstance {} +// SAFETY: same as above +unsafe impl Sync for HInstance {} + +impl HInstance { + pub fn get() -> Self { + let result = unsafe { GetModuleHandleW(null_mut()) }; + if result.is_null() { + panic!( + "Failed to get HInstance pointer: GetModuleHandleW failed: {}", + Error::from_win32() + ); + } + + Self(result) + } + + #[inline] + pub fn as_raw(&self) -> HINSTANCE { + self.0 + } +} diff --git a/src/wrappers/win32/uuid.rs b/src/wrappers/win32/uuid.rs new file mode 100644 index 00000000..4bba92c4 --- /dev/null +++ b/src/wrappers/win32/uuid.rs @@ -0,0 +1,36 @@ +use std::fmt::{Display, Formatter}; +use windows_sys::core::GUID; +use windows_sys::Win32::System::Rpc::UuidCreate; + +pub struct Uuid(GUID); + +impl Uuid { + pub fn new() -> Self { + let mut guid = GUID::default(); + + // SAFETY: the passed pointer is valid, it comes from a mut reference + unsafe { UuidCreate(&mut guid) }; + + Self(guid) + } +} + +impl Display for Uuid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{:0X}-{:0X}-{:0X}-{:0X}{:0X}-{:0X}{:0X}{:0X}{:0X}{:0X}{:0X}\0", + self.0.data1, + self.0.data2, + self.0.data3, + self.0.data4[0], + self.0.data4[1], + self.0.data4[2], + self.0.data4[3], + self.0.data4[4], + self.0.data4[5], + self.0.data4[6], + self.0.data4[7] + ) + } +} diff --git a/src/wrappers/win32/window_class.rs b/src/wrappers/win32/window_class.rs new file mode 100644 index 00000000..6f28b0b0 --- /dev/null +++ b/src/wrappers/win32/window_class.rs @@ -0,0 +1,65 @@ +use crate::wrappers::win32::h_instance::HInstance; +use crate::wrappers::win32::uuid::Uuid; +use std::ptr::null_mut; +use std::sync::Arc; +use windows_core::{Error, Result, HSTRING}; +use windows_sys::core::PCWSTR; +use windows_sys::Win32::UI::WindowsAndMessaging::{ + LoadCursorW, RegisterClassW, UnregisterClassW, CS_OWNDC, IDC_ARROW, WNDCLASSW, WNDPROC, +}; + +#[derive(Clone)] +pub struct RegisteredClass(Arc); + +impl RegisteredClass { + pub fn register_new(instance: HInstance, wnd_proc: WNDPROC) -> Result { + let class_name = format!("Baseview-{}", Uuid::new()); + let class_name = HSTRING::from(&class_name); + + let class_info = WNDCLASSW { + lpfnWndProc: wnd_proc, + hInstance: instance.as_raw(), + lpszClassName: class_name.as_ptr(), + + style: CS_OWNDC, // TODO: this is very suspicious + cbClsExtra: 0, + cbWndExtra: 0, + hIcon: null_mut(), // Default icon + hCursor: unsafe { LoadCursorW(null_mut(), IDC_ARROW) }, // Arrow cursor + hbrBackground: null_mut(), // No default background + lpszMenuName: null_mut(), // No default menu + }; + + let class_atom = unsafe { RegisterClassW(&class_info) }; + if class_atom == 0 { + return Err(Error::from_win32()); + } + + Ok(Self(Arc::new(RegisteredClassInner(class_atom, instance)))) + } + + #[inline] + pub fn as_atom_ptr(&self) -> PCWSTR { + self.0.as_atom_ptr() + } +} + +struct RegisteredClassInner(u16, HInstance); + +impl RegisteredClassInner { + // See: + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw + // https://networkdls.com/Win32Ref/MAKEINTATOM.html + // https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types + #[inline] + pub fn as_atom_ptr(&self) -> PCWSTR { + self.0 as u32 as PCWSTR + } +} + +impl Drop for RegisteredClassInner { + fn drop(&mut self) { + // Ignore errors from this, at worst this is a small memory leak. + let _ = unsafe { UnregisterClassW(self.as_atom_ptr(), self.1.as_raw()) }; + } +}