#![cfg(windows_platform)] use std::cell::Cell; use std::ffi::c_void; use std::mem::{self, MaybeUninit}; use std::sync::mpsc::channel; use std::sync::{Arc, Mutex, MutexGuard}; use std::{io, panic, ptr}; use tracing::warn; use windows_sys::Win32::Foundation::{ HWND, LPARAM, OLE_E_WRONGCOMPOBJ, POINT, POINTS, RECT, RPC_E_CHANGED_MODE, S_OK, WPARAM, }; use windows_sys::Win32::Graphics::Dwm::{ DwmEnableBlurBehindWindow, DwmSetWindowAttribute, DWMWA_BORDER_COLOR, DWMWA_CAPTION_COLOR, DWMWA_SYSTEMBACKDROP_TYPE, DWMWA_TEXT_COLOR, DWMWA_WINDOW_CORNER_PREFERENCE, DWM_BB_BLURREGION, DWM_BB_ENABLE, DWM_BLURBEHIND, DWM_SYSTEMBACKDROP_TYPE, DWM_WINDOW_CORNER_PREFERENCE, }; use windows_sys::Win32::Graphics::Gdi::{ ChangeDisplaySettingsExW, ClientToScreen, CreateRectRgn, DeleteObject, InvalidateRgn, RedrawWindow, CDS_FULLSCREEN, DISP_CHANGE_BADFLAGS, DISP_CHANGE_BADMODE, DISP_CHANGE_BADPARAM, DISP_CHANGE_FAILED, DISP_CHANGE_SUCCESSFUL, RDW_INTERNALPAINT, }; use windows_sys::Win32::System::Com::{ CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED, }; use windows_sys::Win32::System::Ole::{OleInitialize, RegisterDragDrop}; use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ EnableWindow, GetActiveWindow, MapVirtualKeyW, ReleaseCapture, SendInput, ToUnicode, INPUT, INPUT_0, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, MAPVK_VK_TO_VSC, VIRTUAL_KEY, VK_LMENU, VK_MENU, VK_SPACE, }; use windows_sys::Win32::UI::Input::Touch::{RegisterTouchWindow, TWF_WANTPALM}; use windows_sys::Win32::UI::WindowsAndMessaging::{ CreateWindowExW, EnableMenuItem, FlashWindowEx, GetClientRect, GetCursorPos, GetForegroundWindow, GetSystemMenu, GetSystemMetrics, GetWindowPlacement, GetWindowTextLengthW, GetWindowTextW, IsWindowVisible, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW, SetCursor, SetCursorPos, SetForegroundWindow, SetMenuDefaultItem, SetWindowDisplayAffinity, SetWindowPlacement, SetWindowPos, SetWindowTextW, TrackPopupMenu, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, FLASHWINFO, FLASHW_ALL, FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, GWLP_HINSTANCE, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, MENU_ITEM_STATE, MFS_DISABLED, MFS_ENABLED, MF_BYCOMMAND, NID_READY, PM_NOREMOVE, SC_CLOSE, SC_MAXIMIZE, SC_MINIMIZE, SC_MOVE, SC_RESTORE, SC_SIZE, SM_DIGITIZER, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE, SWP_NOZORDER, TPM_LEFTALIGN, TPM_RETURNCMD, WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WM_SYSCOMMAND, WNDCLASSEXW, }; use crate::cursor::Cursor; use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; use crate::icon::Icon; use crate::monitor::MonitorHandle as CoreMonitorHandle; use crate::platform::windows::{BackdropType, Color, CornerPreference}; use crate::platform_impl::platform::dark_mode::try_theme; use crate::platform_impl::platform::definitions::{ CLSID_TaskbarList, IID_ITaskbarList, IID_ITaskbarList2, ITaskbarList, ITaskbarList2, }; use crate::platform_impl::platform::dpi::{ dpi_to_scale_factor, enable_non_client_dpi_scaling, hwnd_dpi, }; use crate::platform_impl::platform::drop_handler::FileDropHandler; use crate::platform_impl::platform::event_loop::{self, ActiveEventLoop, DESTROY_MSG_ID}; use crate::platform_impl::platform::icon::{self, IconType}; use crate::platform_impl::platform::ime::ImeContext; use crate::platform_impl::platform::keyboard::KeyEventBuilder; use crate::platform_impl::platform::window_state::{ CursorFlags, SavedWindow, WindowFlags, WindowState, }; use crate::platform_impl::platform::{monitor, util, Fullscreen, SelectedCursor, WindowId}; use crate::window::{ CursorGrabMode, Fullscreen as CoreFullscreen, ImePurpose, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId as CoreWindowId, WindowLevel, }; /// The Win32 implementation of the main `Window` object. pub(crate) struct Window { /// Main handle for the window. window: HWND, /// The current window state. window_state: Arc>, // The events loop proxy. thread_executor: event_loop::EventLoopThreadExecutor, } impl Window { pub(crate) fn new( event_loop: &ActiveEventLoop, w_attr: WindowAttributes, ) -> Result { // We dispatch an `init` function because of code style. // First person to remove the need for cloning here gets a cookie! // // done. you owe me -- ossi unsafe { init(w_attr, event_loop) } } fn window_state_lock(&self) -> MutexGuard<'_, WindowState> { self.window_state.lock().unwrap() } /// Returns the `hwnd` of this window. pub fn hwnd(&self) -> HWND { self.window } #[cfg(feature = "rwh_06")] pub unsafe fn rwh_06_no_thread_check( &self, ) -> Result { let mut window_handle = rwh_06::Win32WindowHandle::new(unsafe { // SAFETY: Handle will never be zero. std::num::NonZeroIsize::new_unchecked(self.window) }); let hinstance = unsafe { super::get_window_long(self.hwnd(), GWLP_HINSTANCE) }; window_handle.hinstance = std::num::NonZeroIsize::new(hinstance); Ok(rwh_06::RawWindowHandle::Win32(window_handle)) } #[cfg(feature = "rwh_06")] pub fn raw_window_handle_rwh_06(&self) -> Result { // TODO: Write a test once integration framework is ready to ensure that it holds. // If we aren't in the GUI thread, we can't return the window. if !self.thread_executor.in_event_loop_thread() { tracing::error!("tried to access window handle outside of the main thread"); return Err(rwh_06::HandleError::Unavailable); } // SAFETY: We are on the correct thread. unsafe { self.rwh_06_no_thread_check() } } #[cfg(feature = "rwh_06")] pub fn raw_display_handle_rwh_06( &self, ) -> Result { Ok(rwh_06::RawDisplayHandle::Windows(rwh_06::WindowsDisplayHandle::new())) } pub fn set_enable(&self, enabled: bool) { unsafe { EnableWindow(self.hwnd(), enabled.into()) }; } pub fn set_skip_taskbar(&self, skip: bool) { self.window_state_lock().skip_taskbar = skip; unsafe { set_skip_taskbar(self.hwnd(), skip) }; } pub fn set_undecorated_shadow(&self, shadow: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow) }); }); } pub fn set_system_backdrop(&self, backdrop_type: BackdropType) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_SYSTEMBACKDROP_TYPE as u32, &(backdrop_type as i32) as *const _ as _, mem::size_of::() as _, ); } } pub fn set_taskbar_icon(&self, taskbar_icon: Option) { if let Some(ref taskbar_icon) = taskbar_icon { taskbar_icon.inner.set_for_window(self.hwnd(), IconType::Big); } else { icon::unset_for_window(self.hwnd(), IconType::Big); } self.window_state_lock().taskbar_icon = taskbar_icon; } unsafe fn handle_os_dragging(&self, wparam: WPARAM) { let window = self.window; let window_state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || { { let mut guard = window_state.lock().unwrap(); if !guard.dragging { guard.dragging = true; } else { return; } } let points = { let mut pos = unsafe { mem::zeroed() }; unsafe { GetCursorPos(&mut pos) }; pos }; let points = POINTS { x: points.x as i16, y: points.y as i16 }; // ReleaseCapture needs to execute on the main thread unsafe { ReleaseCapture() }; unsafe { PostMessageW(window, WM_NCLBUTTONDOWN, wparam, &points as *const _ as LPARAM) }; }); } unsafe fn handle_showing_window_menu(&self, position: Position) { unsafe { let point = { let mut point = POINT { x: 0, y: 0 }; let scale_factor = self.scale_factor(); let (x, y) = position.to_physical::(scale_factor).into(); point.x = x; point.y = y; if ClientToScreen(self.hwnd(), &mut point) == false.into() { warn!( "Can't convert client-area coordinates to screen coordinates when showing \ window menu." ); return; } point }; // get the current system menu let h_menu = GetSystemMenu(self.hwnd(), 0); if h_menu == 0 { warn!("The corresponding window doesn't have a system menu"); // This situation should not be treated as an error so just return without showing // menu. return; } fn enable(b: bool) -> MENU_ITEM_STATE { if b { MFS_ENABLED } else { MFS_DISABLED } } // Change the menu items according to the current window status. let restore_btn = enable(self.is_maximized() && self.is_resizable()); let size_btn = enable(!self.is_maximized() && self.is_resizable()); let maximize_btn = enable(!self.is_maximized() && self.is_resizable()); EnableMenuItem(h_menu, SC_RESTORE, MF_BYCOMMAND | restore_btn); EnableMenuItem(h_menu, SC_MOVE, MF_BYCOMMAND | enable(!self.is_maximized())); EnableMenuItem(h_menu, SC_SIZE, MF_BYCOMMAND | size_btn); EnableMenuItem(h_menu, SC_MINIMIZE, MF_BYCOMMAND | MFS_ENABLED); EnableMenuItem(h_menu, SC_MAXIMIZE, MF_BYCOMMAND | maximize_btn); EnableMenuItem(h_menu, SC_CLOSE, MF_BYCOMMAND | MFS_ENABLED); // Set the default menu item. SetMenuDefaultItem(h_menu, SC_CLOSE, 0); // Popup the system menu at the position. let result = TrackPopupMenu( h_menu, TPM_RETURNCMD | TPM_LEFTALIGN, /* for now im using LTR, but we have to use user * layout direction */ point.x, point.y, 0, self.hwnd(), std::ptr::null_mut(), ); if result == 0 { // User canceled the menu, no need to continue. return; } // Send the command that the user select to the corresponding window. if PostMessageW(self.hwnd(), WM_SYSCOMMAND, result as _, 0) == 0 { warn!("Can't post the system menu message to the window."); } } } #[inline] pub fn set_border_color(&self, color: Color) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_BORDER_COLOR as u32, &color as *const _ as _, mem::size_of::() as _, ); } } #[inline] pub fn set_title_background_color(&self, color: Color) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_CAPTION_COLOR as u32, &color as *const _ as _, mem::size_of::() as _, ); } } #[inline] pub fn set_title_text_color(&self, color: Color) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_TEXT_COLOR as u32, &color as *const _ as _, mem::size_of::() as _, ); } } #[inline] pub fn set_corner_preference(&self, preference: CornerPreference) { unsafe { DwmSetWindowAttribute( self.hwnd(), DWMWA_WINDOW_CORNER_PREFERENCE as u32, &(preference as DWM_WINDOW_CORNER_PREFERENCE) as *const _ as _, mem::size_of::() as _, ); } } } impl Drop for Window { fn drop(&mut self) { // Restore fullscreen video mode on exit. if matches!(self.fullscreen(), Some(CoreFullscreen::Exclusive(_))) { self.set_fullscreen(None); } unsafe { // The window must be destroyed from the same thread that created it, so we send a // custom message to be handled by our callback to do the actual work. PostMessageW(self.hwnd(), DESTROY_MSG_ID.get(), 0, 0); } } } #[cfg(feature = "rwh_06")] impl rwh_06::HasDisplayHandle for Window { fn display_handle(&self) -> Result, rwh_06::HandleError> { let raw = self.raw_display_handle_rwh_06()?; unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) } } } #[cfg(feature = "rwh_06")] impl rwh_06::HasWindowHandle for Window { fn window_handle(&self) -> Result, rwh_06::HandleError> { let raw = self.raw_window_handle_rwh_06()?; unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw)) } } } impl CoreWindow for Window { fn set_title(&self, text: &str) { let wide_text = util::encode_wide(text); unsafe { SetWindowTextW(self.hwnd(), wide_text.as_ptr()); } } fn set_transparent(&self, transparent: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::TRANSPARENT, transparent) }); }); } fn set_blur(&self, _blur: bool) {} fn set_visible(&self, visible: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::VISIBLE, visible) }); }); } fn is_visible(&self) -> Option { Some(unsafe { IsWindowVisible(self.window) == 1 }) } fn request_redraw(&self) { // NOTE: mark that we requested a redraw to handle requests during `WM_PAINT` handling. self.window_state.lock().unwrap().redraw_requested = true; unsafe { RedrawWindow(self.hwnd(), ptr::null(), 0, RDW_INTERNALPAINT); } } fn pre_present_notify(&self) {} fn outer_position(&self) -> Result, NotSupportedError> { util::WindowArea::Outer .get_rect(self.hwnd()) .map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top))) .expect( "Unexpected GetWindowRect failure; please report this error to \ rust-windowing/winit", ) } fn inner_position(&self) -> Result, NotSupportedError> { let mut position: POINT = unsafe { mem::zeroed() }; if unsafe { ClientToScreen(self.hwnd(), &mut position) } == false.into() { panic!( "Unexpected ClientToScreen failure: please report this error to \ rust-windowing/winit" ) } Ok(PhysicalPosition::new(position.x, position.y)) } fn set_outer_position(&self, position: Position) { let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); let window_state = Arc::clone(&self.window_state); let window = self.window; self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); unsafe { SetWindowPos( self.hwnd(), 0, x, y, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE, ); InvalidateRgn(self.hwnd(), 0, false.into()); } } fn surface_size(&self) -> PhysicalSize { let mut rect: RECT = unsafe { mem::zeroed() }; if unsafe { GetClientRect(self.hwnd(), &mut rect) } == false.into() { panic!( "Unexpected GetClientRect failure: please report this error to \ rust-windowing/winit" ) } PhysicalSize::new((rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32) } fn outer_size(&self) -> PhysicalSize { util::WindowArea::Outer .get_rect(self.hwnd()) .map(|rect| { PhysicalSize::new((rect.right - rect.left) as u32, (rect.bottom - rect.top) as u32) }) .unwrap() } fn request_surface_size(&self, size: Size) -> Option> { let scale_factor = self.scale_factor(); let physical_size = size.to_physical::(scale_factor); let window_flags = self.window_state_lock().window_flags; window_flags.set_size(self.hwnd(), physical_size); if physical_size != self.surface_size() { let window_state = Arc::clone(&self.window_state); let window = self.window; self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MAXIMIZED, false) }); }); } None } fn set_min_surface_size(&self, size: Option) { self.window_state_lock().min_size = size; // Make windows re-check the window size bounds. let size = self.surface_size(); let _ = self.request_surface_size(size.into()); } fn set_max_surface_size(&self, size: Option) { self.window_state_lock().max_size = size; // Make windows re-check the window size bounds. let size = self.surface_size(); let _ = self.request_surface_size(size.into()); } fn surface_resize_increments(&self) -> Option> { let w = self.window_state_lock(); let scale_factor = w.scale_factor; w.surface_resize_increments.map(|size| size.to_physical(scale_factor)) } fn set_surface_resize_increments(&self, increments: Option) { self.window_state_lock().surface_resize_increments = increments; } fn set_resizable(&self, resizable: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::RESIZABLE, resizable) }); }); } fn is_resizable(&self) -> bool { let window_state = self.window_state_lock(); window_state.window_flags.contains(WindowFlags::RESIZABLE) } fn set_enabled_buttons(&self, buttons: WindowButtons) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MINIMIZABLE, buttons.contains(WindowButtons::MINIMIZE)); f.set(WindowFlags::MAXIMIZABLE, buttons.contains(WindowButtons::MAXIMIZE)); f.set(WindowFlags::CLOSABLE, buttons.contains(WindowButtons::CLOSE)) }); }); } fn enabled_buttons(&self) -> WindowButtons { let mut buttons = WindowButtons::empty(); let window_state = self.window_state_lock(); if window_state.window_flags.contains(WindowFlags::MINIMIZABLE) { buttons |= WindowButtons::MINIMIZE; } if window_state.window_flags.contains(WindowFlags::MAXIMIZABLE) { buttons |= WindowButtons::MAXIMIZE; } if window_state.window_flags.contains(WindowFlags::CLOSABLE) { buttons |= WindowButtons::CLOSE; } buttons } fn set_cursor(&self, cursor: Cursor) { match cursor { Cursor::Icon(icon) => { self.window_state_lock().mouse.selected_cursor = SelectedCursor::Named(icon); self.thread_executor.execute_in_thread(move || unsafe { let cursor = LoadCursorW(0, util::to_windows_cursor(icon)); SetCursor(cursor); }); }, Cursor::Custom(cursor) => { self.window_state_lock().mouse.selected_cursor = SelectedCursor::Custom(cursor.inner.0.clone()); self.thread_executor.execute_in_thread(move || unsafe { SetCursor(cursor.inner.0.as_raw_handle()); }); }, } } fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { let confine = match mode { CursorGrabMode::None => false, CursorGrabMode::Confined => true, CursorGrabMode::Locked => { return Err(ExternalError::NotSupported(NotSupportedError::new())) }, }; let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); self.thread_executor.execute_in_thread(move || { let _ = &window; let result = window_state .lock() .unwrap() .mouse .set_cursor_flags(window, |f| f.set(CursorFlags::GRABBED, confine)) .map_err(|e| ExternalError::Os(os_error!(e))); let _ = tx.send(result); }); rx.recv().unwrap() } fn set_cursor_visible(&self, visible: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); self.thread_executor.execute_in_thread(move || { let _ = &window; let result = window_state .lock() .unwrap() .mouse .set_cursor_flags(window, |f| f.set(CursorFlags::HIDDEN, !visible)) .map_err(|e| e.to_string()); let _ = tx.send(result); }); rx.recv().unwrap().ok(); } fn scale_factor(&self) -> f64 { self.window_state_lock().scale_factor } fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { let scale_factor = self.scale_factor(); let (x, y) = position.to_physical::(scale_factor).into(); let mut point = POINT { x, y }; unsafe { if ClientToScreen(self.hwnd(), &mut point) == false.into() { return Err(ExternalError::Os(os_error!(io::Error::last_os_error()))); } if SetCursorPos(point.x, point.y) == false.into() { return Err(ExternalError::Os(os_error!(io::Error::last_os_error()))); } } Ok(()) } fn drag_window(&self) -> Result<(), ExternalError> { unsafe { self.handle_os_dragging(HTCAPTION as WPARAM); } Ok(()) } fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { unsafe { self.handle_os_dragging(match direction { ResizeDirection::East => HTRIGHT, ResizeDirection::North => HTTOP, ResizeDirection::NorthEast => HTTOPRIGHT, ResizeDirection::NorthWest => HTTOPLEFT, ResizeDirection::South => HTBOTTOM, ResizeDirection::SouthEast => HTBOTTOMRIGHT, ResizeDirection::SouthWest => HTBOTTOMLEFT, ResizeDirection::West => HTLEFT, } as WPARAM); } Ok(()) } fn show_window_menu(&self, position: Position) { unsafe { self.handle_showing_window_menu(position); } } fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::IGNORE_CURSOR_EVENT, !hittest) }); }); Ok(()) } fn id(&self) -> CoreWindowId { CoreWindowId(WindowId(self.hwnd())) } fn set_minimized(&self, minimized: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); let is_minimized = util::is_minimized(self.hwnd()); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags_in_place(&mut window_state.lock().unwrap(), |f| { f.set(WindowFlags::MINIMIZED, is_minimized) }); WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MINIMIZED, minimized) }); }); } fn is_minimized(&self) -> Option { Some(util::is_minimized(self.hwnd())) } fn set_maximized(&self, maximized: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MAXIMIZED, maximized) }); }); } fn is_maximized(&self) -> bool { let window_state = self.window_state_lock(); window_state.window_flags.contains(WindowFlags::MAXIMIZED) } fn fullscreen(&self) -> Option { let window_state = self.window_state_lock(); window_state.fullscreen.clone().map(Into::into) } fn set_fullscreen(&self, fullscreen: Option) { let fullscreen = fullscreen.map(Into::into); let window = self.window; let window_state = Arc::clone(&self.window_state); let mut window_state_lock = window_state.lock().unwrap(); let old_fullscreen = window_state_lock.fullscreen.clone(); match (&old_fullscreen, &fullscreen) { // Return if we already are in the same fullscreen mode _ if old_fullscreen == fullscreen => return, // Return if saved Borderless(monitor) is the same as current monitor when requested // fullscreen is Borderless(None) (Some(Fullscreen::Borderless(Some(monitor))), Some(Fullscreen::Borderless(None))) if *monitor == monitor::current_monitor(window) => { return }, _ => {}, } window_state_lock.fullscreen.clone_from(&fullscreen); drop(window_state_lock); self.thread_executor.execute_in_thread(move || { let _ = &window; // Change video mode if we're transitioning to or from exclusive // fullscreen match (&old_fullscreen, &fullscreen) { (_, Some(Fullscreen::Exclusive(video_mode))) => { let monitor = video_mode.monitor(); let monitor_info = monitor::get_monitor_info(monitor.hmonitor()).unwrap(); let res = unsafe { ChangeDisplaySettingsExW( monitor_info.szDevice.as_ptr(), &*video_mode.native_video_mode, 0, CDS_FULLSCREEN, ptr::null(), ) }; debug_assert!(res != DISP_CHANGE_BADFLAGS); debug_assert!(res != DISP_CHANGE_BADMODE); debug_assert!(res != DISP_CHANGE_BADPARAM); debug_assert!(res != DISP_CHANGE_FAILED); assert_eq!(res, DISP_CHANGE_SUCCESSFUL); }, (Some(Fullscreen::Exclusive(_)), _) => { let res = unsafe { ChangeDisplaySettingsExW( ptr::null(), ptr::null(), 0, CDS_FULLSCREEN, ptr::null(), ) }; debug_assert!(res != DISP_CHANGE_BADFLAGS); debug_assert!(res != DISP_CHANGE_BADMODE); debug_assert!(res != DISP_CHANGE_BADPARAM); debug_assert!(res != DISP_CHANGE_FAILED); assert_eq!(res, DISP_CHANGE_SUCCESSFUL); }, _ => (), } unsafe { // There are some scenarios where calling `ChangeDisplaySettingsExW` takes long // enough to execute that the DWM thinks our program has frozen and takes over // our program's window. When that happens, the `SetWindowPos` call below gets // eaten and the window doesn't get set to the proper fullscreen position. // // Calling `PeekMessageW` here notifies Windows that our process is still running // fine, taking control back from the DWM and ensuring that the `SetWindowPos` call // below goes through. let mut msg = mem::zeroed(); PeekMessageW(&mut msg, 0, 0, 0, PM_NOREMOVE); } // Update window style WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set( WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN, matches!(fullscreen, Some(Fullscreen::Exclusive(_))), ); f.set( WindowFlags::MARKER_BORDERLESS_FULLSCREEN, matches!(fullscreen, Some(Fullscreen::Borderless(_))), ); }); // Mark as fullscreen window wrt to z-order // // this needs to be called before the below fullscreen SetWindowPos as this itself // will generate WM_SIZE messages of the old window size that can race with what we set // below unsafe { taskbar_mark_fullscreen(window, fullscreen.is_some()); } // Update window bounds match &fullscreen { Some(fullscreen) => { // Save window bounds before entering fullscreen let placement = unsafe { let mut placement = mem::zeroed(); GetWindowPlacement(window, &mut placement); placement }; window_state.lock().unwrap().saved_window = Some(SavedWindow { placement }); let monitor = match &fullscreen { Fullscreen::Exclusive(video_mode) => video_mode.monitor(), Fullscreen::Borderless(Some(monitor)) => monitor.clone(), Fullscreen::Borderless(None) => monitor::current_monitor(window), }; let position: (i32, i32) = monitor.position().unwrap_or_default().into(); let size: (u32, u32) = monitor.size().into(); unsafe { SetWindowPos( window, 0, position.0, position.1, size.0 as i32, size.1 as i32, SWP_ASYNCWINDOWPOS | SWP_NOZORDER, ); InvalidateRgn(window, 0, false.into()); } }, None => { let mut window_state_lock = window_state.lock().unwrap(); if let Some(SavedWindow { placement }) = window_state_lock.saved_window.take() { drop(window_state_lock); unsafe { SetWindowPlacement(window, &placement); InvalidateRgn(window, 0, false.into()); } } }, } }); } fn set_decorations(&self, decorations: bool) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::MARKER_DECORATIONS, decorations) }); }); } fn is_decorated(&self) -> bool { let window_state = self.window_state_lock(); window_state.window_flags.contains(WindowFlags::MARKER_DECORATIONS) } fn set_window_level(&self, level: WindowLevel) { let window = self.window; let window_state = Arc::clone(&self.window_state); self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { f.set(WindowFlags::ALWAYS_ON_TOP, level == WindowLevel::AlwaysOnTop); f.set(WindowFlags::ALWAYS_ON_BOTTOM, level == WindowLevel::AlwaysOnBottom); }); }); } fn current_monitor(&self) -> Option { Some(CoreMonitorHandle { inner: monitor::current_monitor(self.hwnd()) }) } fn available_monitors(&self) -> Box> { Box::new(monitor::available_monitors().into_iter().map(|inner| CoreMonitorHandle { inner })) } fn primary_monitor(&self) -> Option { Some(CoreMonitorHandle { inner: monitor::primary_monitor() }) } fn set_window_icon(&self, window_icon: Option) { if let Some(ref window_icon) = window_icon { window_icon.inner.set_for_window(self.hwnd(), IconType::Small); } else { icon::unset_for_window(self.hwnd(), IconType::Small); } self.window_state_lock().window_icon = window_icon; } fn set_ime_cursor_area(&self, spot: Position, size: Size) { let window = self.window; let state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || unsafe { let scale_factor = state.lock().unwrap().scale_factor; ImeContext::current(window).set_ime_cursor_area(spot, size, scale_factor); }); } fn set_ime_allowed(&self, allowed: bool) { let window = self.window; let state = self.window_state.clone(); self.thread_executor.execute_in_thread(move || unsafe { state.lock().unwrap().ime_allowed = allowed; ImeContext::set_ime_allowed(window, allowed); }) } fn set_ime_purpose(&self, _purpose: ImePurpose) {} fn request_user_attention(&self, request_type: Option) { let window = self.window; let active_window_handle = unsafe { GetActiveWindow() }; if window == active_window_handle { return; } self.thread_executor.execute_in_thread(move || unsafe { let (flags, count) = request_type .map(|ty| match ty { UserAttentionType::Critical => (FLASHW_ALL | FLASHW_TIMERNOFG, u32::MAX), UserAttentionType::Informational => (FLASHW_TRAY | FLASHW_TIMERNOFG, 0), }) .unwrap_or((FLASHW_STOP, 0)); let flash_info = FLASHWINFO { cbSize: mem::size_of::() as u32, hwnd: window, dwFlags: flags, uCount: count, dwTimeout: 0, }; FlashWindowEx(&flash_info); }); } fn set_theme(&self, theme: Option) { try_theme(self.window, theme); } fn theme(&self) -> Option { Some(self.window_state_lock().current_theme) } fn has_focus(&self) -> bool { let window_state = self.window_state.lock().unwrap(); window_state.has_active_focus() } fn title(&self) -> String { let len = unsafe { GetWindowTextLengthW(self.window) } + 1; let mut buf = vec![0; len as usize]; unsafe { GetWindowTextW(self.window, buf.as_mut_ptr(), len) }; util::decode_wide(&buf).to_string_lossy().to_string() } #[inline] fn focus_window(&self) { let window_flags = self.window_state_lock().window_flags(); let is_visible = window_flags.contains(WindowFlags::VISIBLE); let is_minimized = util::is_minimized(self.hwnd()); let is_foreground = self.window == unsafe { GetForegroundWindow() }; if is_visible && !is_minimized && !is_foreground { unsafe { force_window_active(self.window) }; } } #[inline] fn set_content_protected(&self, protected: bool) { unsafe { SetWindowDisplayAffinity( self.hwnd(), if protected { WDA_EXCLUDEFROMCAPTURE } else { WDA_NONE }, ) }; } #[inline] fn reset_dead_keys(&self) { // `ToUnicode` consumes the dead-key by default, so we are constructing a fake (but valid) // key input which we can call `ToUnicode` with. unsafe { let vk = VK_SPACE as VIRTUAL_KEY; let scancode = MapVirtualKeyW(vk as u32, MAPVK_VK_TO_VSC); let kbd_state = [0; 256]; let mut char_buff = [MaybeUninit::uninit(); 8]; ToUnicode( vk as u32, scancode, kbd_state.as_ptr(), char_buff[0].as_mut_ptr(), char_buff.len() as i32, 0, ); } } #[cfg(feature = "rwh_06")] fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle { self } #[cfg(feature = "rwh_06")] fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle { self } } pub(super) struct InitData<'a> { // inputs pub event_loop: &'a ActiveEventLoop, pub attributes: WindowAttributes, pub window_flags: WindowFlags, // outputs pub window: Option, } impl<'a> InitData<'a> { unsafe fn create_window(&self, window: HWND) -> Window { // Register for touch events if applicable { let digitizer = unsafe { GetSystemMetrics(SM_DIGITIZER) as u32 }; if digitizer & NID_READY != 0 { unsafe { RegisterTouchWindow(window, TWF_WANTPALM) }; } } let dpi = unsafe { hwnd_dpi(window) }; let scale_factor = dpi_to_scale_factor(dpi); // If the system theme is dark, we need to set the window theme now // before we update the window flags (and possibly show the // window for the first time). let current_theme = try_theme(window, self.attributes.preferred_theme); let window_state = { let window_state = WindowState::new( &self.attributes, scale_factor, current_theme, self.attributes.preferred_theme, ); let window_state = Arc::new(Mutex::new(window_state)); WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { *f = self.window_flags }); window_state }; enable_non_client_dpi_scaling(window); unsafe { ImeContext::set_ime_allowed(window, false) }; Window { window, window_state, thread_executor: self.event_loop.create_thread_executor() } } unsafe fn create_window_data(&self, win: &Window) -> event_loop::WindowData { let file_drop_handler = if self.attributes.platform_specific.drag_and_drop { let ole_init_result = unsafe { OleInitialize(ptr::null_mut()) }; // It is ok if the initialize result is `S_FALSE` because it might happen that // multiple windows are created on the same thread. if ole_init_result == OLE_E_WRONGCOMPOBJ { panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`"); } else if ole_init_result == RPC_E_CHANGED_MODE { panic!( "OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`. Make sure other \ crates are not using multithreaded COM library on the same thread or disable \ drag and drop support." ); } let file_drop_runner = self.event_loop.runner_shared.clone(); let file_drop_handler = FileDropHandler::new( win.window, Box::new(move |event| file_drop_runner.send_event(event)), ); let handler_interface_ptr = unsafe { &mut (*file_drop_handler.data).interface as *mut _ as *mut c_void }; assert_eq!(unsafe { RegisterDragDrop(win.window, handler_interface_ptr) }, S_OK); Some(file_drop_handler) } else { None }; event_loop::WindowData { window_state: win.window_state.clone(), event_loop_runner: self.event_loop.runner_shared.clone(), key_event_builder: KeyEventBuilder::default(), _file_drop_handler: file_drop_handler, userdata_removed: Cell::new(false), recurse_depth: Cell::new(0), } } // Returns a pointer to window user data on success. // The user data will be registered for the window and can be accessed within the window event // callback. pub unsafe fn on_nccreate(&mut self, window: HWND) -> Option { let runner = self.event_loop.runner_shared.clone(); let result = runner.catch_unwind(|| { let window = unsafe { self.create_window(window) }; let window_data = unsafe { self.create_window_data(&window) }; (window, window_data) }); result.map(|(win, userdata)| { self.window = Some(win); let userdata = Box::into_raw(Box::new(userdata)); userdata as _ }) } pub unsafe fn on_create(&mut self) { let win = self.window.as_mut().expect("failed window creation"); // making the window transparent if self.attributes.transparent && !self.attributes.platform_specific.no_redirection_bitmap { // Empty region for the blur effect, so the window is fully transparent let region = unsafe { CreateRectRgn(0, 0, -1, -1) }; let bb = DWM_BLURBEHIND { dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION, fEnable: true.into(), hRgnBlur: region, fTransitionOnMaximized: false.into(), }; let hr = unsafe { DwmEnableBlurBehindWindow(win.hwnd(), &bb) }; if hr < 0 { warn!("Setting transparent window is failed. HRESULT Code: 0x{:X}", hr); } unsafe { DeleteObject(region) }; } win.set_skip_taskbar(self.attributes.platform_specific.skip_taskbar); win.set_window_icon(self.attributes.window_icon.clone()); win.set_taskbar_icon(self.attributes.platform_specific.taskbar_icon.clone()); let attributes = self.attributes.clone(); if attributes.content_protected { win.set_content_protected(true); } win.set_cursor(attributes.cursor); // Set visible before setting the size to ensure the // attribute is correctly applied. win.set_visible(attributes.visible); win.set_enabled_buttons(attributes.enabled_buttons); let size = attributes.surface_size.unwrap_or_else(|| PhysicalSize::new(800, 600).into()); let max_size = attributes .max_surface_size .unwrap_or_else(|| PhysicalSize::new(f64::MAX, f64::MAX).into()); let min_size = attributes.min_surface_size.unwrap_or_else(|| PhysicalSize::new(0, 0).into()); let clamped_size = Size::clamp(size, min_size, max_size, win.scale_factor()); let _ = win.request_surface_size(clamped_size); // let margins = MARGINS { // cxLeftWidth: 1, // cxRightWidth: 1, // cyTopHeight: 1, // cyBottomHeight: 1, // }; // dbg!(DwmExtendFrameIntoClientArea(win.hwnd(), &margins as *const _)); if let Some(position) = attributes.position { win.set_outer_position(position); } win.set_system_backdrop(self.attributes.platform_specific.backdrop_type); if let Some(color) = self.attributes.platform_specific.border_color { win.set_border_color(color); } if let Some(color) = self.attributes.platform_specific.title_background_color { win.set_title_background_color(color); } if let Some(color) = self.attributes.platform_specific.title_text_color { win.set_title_text_color(color); } if let Some(corner) = self.attributes.platform_specific.corner_preference { win.set_corner_preference(corner); } } } unsafe fn init( attributes: WindowAttributes, event_loop: &ActiveEventLoop, ) -> Result { let title = util::encode_wide(&attributes.title); let class_name = util::encode_wide(&attributes.platform_specific.class_name); unsafe { register_window_class(&class_name) }; let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); window_flags.set( WindowFlags::MARKER_UNDECORATED_SHADOW, attributes.platform_specific.decoration_shadow, ); window_flags .set(WindowFlags::ALWAYS_ON_TOP, attributes.window_level == WindowLevel::AlwaysOnTop); window_flags .set(WindowFlags::ALWAYS_ON_BOTTOM, attributes.window_level == WindowLevel::AlwaysOnBottom); window_flags .set(WindowFlags::NO_BACK_BUFFER, attributes.platform_specific.no_redirection_bitmap); window_flags.set(WindowFlags::MARKER_ACTIVATE, attributes.active); window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent); // WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured. window_flags.set(WindowFlags::RESIZABLE, attributes.resizable); // Will be changed later using `window.set_enabled_buttons` but we need to set a default here // so the diffing later can work. window_flags.set(WindowFlags::CLOSABLE, true); window_flags.set(WindowFlags::CLIP_CHILDREN, attributes.platform_specific.clip_children); let mut fallback_parent = || match attributes.platform_specific.owner { Some(parent) => { window_flags.set(WindowFlags::POPUP, true); Some(parent) }, None => { window_flags.set(WindowFlags::ON_TASKBAR, true); None }, }; #[cfg(feature = "rwh_06")] let parent = match attributes.parent_window.as_ref().map(|handle| handle.0) { Some(rwh_06::RawWindowHandle::Win32(handle)) => { window_flags.set(WindowFlags::CHILD, true); if attributes.platform_specific.menu.is_some() { warn!("Setting a menu on a child window is unsupported"); } Some(handle.hwnd.get() as HWND) }, Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"), None => fallback_parent(), }; #[cfg(not(feature = "rwh_06"))] let parent = fallback_parent(); let menu = attributes.platform_specific.menu; let fullscreen = attributes.fullscreen.clone(); let maximized = attributes.maximized; let mut initdata = InitData { event_loop, attributes, window_flags, window: None }; let (style, ex_style) = window_flags.to_window_styles(); let handle = unsafe { CreateWindowExW( ex_style, class_name.as_ptr(), title.as_ptr(), style, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, parent.unwrap_or(0), menu.unwrap_or(0), util::get_instance_handle(), &mut initdata as *mut _ as *mut _, ) }; // If the window creation in `InitData` panicked, then should resume panicking here if let Err(panic_error) = event_loop.runner_shared.take_panic_error() { panic::resume_unwind(panic_error) } if handle == 0 { return Err(os_error!(io::Error::last_os_error())); } // If the handle is non-null, then window creation must have succeeded, which means // that we *must* have populated the `InitData.window` field. let win = initdata.window.unwrap(); // Need to set FULLSCREEN or MAXIMIZED after CreateWindowEx // This is because if the size is changed in WM_CREATE, the restored size will be stored in that // size. if fullscreen.is_some() { win.set_fullscreen(fullscreen.map(Into::into)); unsafe { force_window_active(win.window) }; } else if maximized { win.set_maximized(true); } Ok(win) } unsafe fn register_window_class(class_name: &[u16]) { let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(super::event_loop::public_window_callback), cbClsExtra: 0, cbWndExtra: 0, hInstance: util::get_instance_handle(), hIcon: 0, hCursor: 0, // must be null in order for cursor state to work properly hbrBackground: 0, lpszMenuName: ptr::null(), lpszClassName: class_name.as_ptr(), hIconSm: 0, }; // We ignore errors because registering the same window class twice would trigger // an error, and because errors here are detected during CreateWindowEx anyway. // Also since there is no weird element in the struct, there is no reason for this // call to fail. unsafe { RegisterClassExW(&class) }; } struct ComInitialized(#[allow(dead_code)] *mut ()); impl Drop for ComInitialized { fn drop(&mut self) { unsafe { CoUninitialize() }; } } thread_local! { static COM_INITIALIZED: ComInitialized = { unsafe { CoInitializeEx(ptr::null(), COINIT_APARTMENTTHREADED as u32); ComInitialized(ptr::null_mut()) } }; static TASKBAR_LIST: Cell<*mut ITaskbarList> = const { Cell::new(ptr::null_mut()) }; static TASKBAR_LIST2: Cell<*mut ITaskbarList2> = const { Cell::new(ptr::null_mut()) }; } pub fn com_initialized() { COM_INITIALIZED.with(|_| {}); } // Reference Implementation: // https://github.com/chromium/chromium/blob/f18e79d901f56154f80eea1e2218544285e62623/ui/views/win/fullscreen_handler.cc // // As per MSDN marking the window as fullscreen should ensure that the // taskbar is moved to the bottom of the Z-order when the fullscreen window // is activated. If the window is not fullscreen, the Shell falls back to // heuristics to determine how the window should be treated, which means // that it could still consider the window as fullscreen. :( unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) { com_initialized(); TASKBAR_LIST2.with(|task_bar_list2_ptr| { let mut task_bar_list2 = task_bar_list2_ptr.get(); if task_bar_list2.is_null() { let hr = unsafe { CoCreateInstance( &CLSID_TaskbarList, ptr::null_mut(), CLSCTX_ALL, &IID_ITaskbarList2, &mut task_bar_list2 as *mut _ as *mut _, ) }; if hr != S_OK { // In visual studio retrieving the taskbar list fails return; } let hr_init = unsafe { (*(*task_bar_list2).lpVtbl).parent.HrInit }; if unsafe { hr_init(task_bar_list2.cast()) } != S_OK { // In some old windows, the taskbar object could not be created, we just ignore it return; } task_bar_list2_ptr.set(task_bar_list2) } task_bar_list2 = task_bar_list2_ptr.get(); let mark_fullscreen_window = unsafe { (*(*task_bar_list2).lpVtbl).MarkFullscreenWindow }; unsafe { mark_fullscreen_window(task_bar_list2, handle, fullscreen.into()) }; }) } pub(crate) unsafe fn set_skip_taskbar(hwnd: HWND, skip: bool) { com_initialized(); TASKBAR_LIST.with(|task_bar_list_ptr| { let mut task_bar_list = task_bar_list_ptr.get(); if task_bar_list.is_null() { let hr = unsafe { CoCreateInstance( &CLSID_TaskbarList, ptr::null_mut(), CLSCTX_ALL, &IID_ITaskbarList, &mut task_bar_list as *mut _ as *mut _, ) }; if hr != S_OK { // In visual studio retrieving the taskbar list fails return; } let hr_init = unsafe { (*(*task_bar_list).lpVtbl).HrInit }; if unsafe { hr_init(task_bar_list.cast()) } != S_OK { // In some old windows, the taskbar object could not be created, we just ignore it return; } task_bar_list_ptr.set(task_bar_list) } task_bar_list = task_bar_list_ptr.get(); if skip { let delete_tab = unsafe { (*(*task_bar_list).lpVtbl).DeleteTab }; unsafe { delete_tab(task_bar_list, hwnd) }; } else { let add_tab = unsafe { (*(*task_bar_list).lpVtbl).AddTab }; unsafe { add_tab(task_bar_list, hwnd) }; } }); } unsafe fn force_window_active(handle: HWND) { // In some situation, calling SetForegroundWindow could not bring up the window, // This is a little hack which can "steal" the foreground window permission // We only call this function in the window creation, so it should be fine. // See : https://stackoverflow.com/questions/10740346/setforegroundwindow-only-working-while-visual-studio-is-open let alt_sc = unsafe { MapVirtualKeyW(VK_MENU as u32, MAPVK_VK_TO_VSC) }; let inputs = [ INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: VK_LMENU, wScan: alt_sc as u16, dwFlags: KEYEVENTF_EXTENDEDKEY, dwExtraInfo: 0, time: 0, }, }, }, INPUT { r#type: INPUT_KEYBOARD, Anonymous: INPUT_0 { ki: KEYBDINPUT { wVk: VK_LMENU, wScan: alt_sc as u16, dwFlags: KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, dwExtraInfo: 0, time: 0, }, }, }, ]; // Simulate a key press and release unsafe { SendInput(inputs.len() as u32, inputs.as_ptr(), mem::size_of::() as i32) }; unsafe { SetForegroundWindow(handle) }; }