Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(windows): fix aero-snap of borderless window #1891

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
15a5493
fix(windows): fix aero-snap for borderless window
amrbashir Mar 21, 2021
1468c60
feat(windows): allow resizing of borderless window
amrbashir Mar 21, 2021
1c2abd6
chore: update `drag_window` example
amrbashir Mar 21, 2021
0ce8245
chore: add entry in `CHANGELOG.md`
amrbashir Mar 21, 2021
3204e65
chore: use `window_debug` example
amrbashir Mar 21, 2021
d3c1cd1
Merge branch 'master' into fix/borderless-snap-on-windows
amrbashir Apr 28, 2021
07851e8
refactor: unpack values for easier reading
amrbashir Apr 28, 2021
0b6eaed
refactor
amrbashir Apr 28, 2021
c139cdb
refactor: remove the resizing logic, focus on fix aero-snap
amrbashir Apr 28, 2021
3821aed
fix review comments
amrbashir Apr 29, 2021
599fc27
Merge branch 'master' into fix/borderless-snap-on-windows
amrbashir Jul 16, 2021
993ea59
fix errors and optimize window styles
amrbashir Jul 16, 2021
da073e3
update changelog
amrbashir Jul 16, 2021
f280706
Update CHANGELOG.md
amrbashir Jul 16, 2021
5ee0a6c
remove blank line and unused const
amrbashir Jul 16, 2021
927be67
Merge branch 'fix/borderless-snap-on-windows' of https://github.com/a…
amrbashir Jul 16, 2021
4618897
Fix window spillover
maroider Aug 22, 2021
c8f5175
Merge branch 'master' into fix/borderless-snap-on-windows
amrbashir Aug 29, 2021
349acfa
Merge branch 'master' into fix/borderless-snap-on-windows
amrbashir Nov 29, 2021
4c0c760
fix size overflow when window is undecorated
amrbashir Mar 2, 2022
c8b2041
Revert "fix size overflow when window is undecorated"
amrbashir Mar 29, 2022
a41d1d3
Merge branch 'master' into fix/borderless-snap-on-windows
amrbashir Mar 29, 2022
00ee199
attempt 2 at fixing size overflow when window is not decorated
amrbashir Mar 29, 2022
017d1d1
fix build
amrbashir Mar 29, 2022
6e28779
fix build again
amrbashir Mar 29, 2022
0362c5a
fix size overflow when dpi changes
amrbashir Apr 29, 2022
8c4a4d4
Merge branch 'master' into fix/borderless-snap-on-windows
amrbashir Jun 12, 2022
4ded59c
Merge branch 'master' into fix/borderless-snap-on-windows
amrbashir Aug 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ And please only add new entries to the top of this list, right below the `# Unre
- On Wayland and X11, implement `is_maximized` method on `Window`.
- On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`.
- On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events.
- On Windows, fix aero-snap for borderless (undecorated) windows.
- On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`.
- **Breaking:** On Wayland, Theme trait and its support types are dropped.
- On Wayland, bump `smithay-client-toolkit` to 0.15.1.
Expand Down
6 changes: 6 additions & 0 deletions examples/window_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ fn main() {
eprintln!(" (Q) Quit event loop");
eprintln!(" (V) Toggle visibility");
eprintln!(" (X) Toggle maximized");
eprintln!(" (B) Toggle borderless");

let mut minimized = false;
let mut visible = true;
let mut borderless = false;

event_loop.set_device_event_filter(DeviceEventFilter::Never);

Expand Down Expand Up @@ -119,6 +121,10 @@ fn main() {
let is_maximized = window.is_maximized();
window.set_maximized(!is_maximized);
}
VirtualKeyCode::B => {
borderless = !borderless;
window.set_decorations(borderless);
}
_ => (),
},
Event::WindowEvent {
Expand Down
77 changes: 59 additions & 18 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,21 @@ use windows_sys::Win32::{
PeekMessageW, PostMessageW, PostThreadMessageW, RegisterClassExW,
RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage, CREATESTRUCTW,
GIDC_ARRIVAL, GIDC_REMOVAL, GWL_EXSTYLE, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT,
MAPVK_VK_TO_VSC, MINMAXINFO, MSG, MWMO_INPUTAVAILABLE, PM_NOREMOVE, PM_QS_PAINT,
PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL,
SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE, WM_CREATE,
WM_DESTROY, WM_DPICHANGED, WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE,
MAPVK_VK_TO_VSC, MINMAXINFO, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_NOREMOVE,
PM_QS_PAINT, PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, RI_KEY_E0, RI_KEY_E1,
RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE,
SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE,
WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE,
WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT,
WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP,
WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP,
WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCREATE, WM_NCDESTROY,
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE,
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE,
WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED,
WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED,
WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP,
WS_VISIBLE,
WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE,
WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP,
WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS,
WM_SETTINGCHANGE, WM_SIZE, WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP,
WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP,
WNDCLASSEXW, WS_CAPTION, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW,
WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_SIZEBOX, WS_VISIBLE,
},
},
};
Expand Down Expand Up @@ -1914,17 +1914,20 @@ unsafe fn public_window_callback_inner<T: 'static>(
let window_state = userdata.window_state.lock();

if window_state.min_size.is_some() || window_state.max_size.is_some() {
let is_decorated = window_state.window_flags.contains(WindowFlags::DECORATIONS);
if let Some(min_size) = window_state.min_size {
let min_size = min_size.to_physical(window_state.scale_factor);
let (width, height): (u32, u32) = util::adjust_size(window, min_size).into();
let (width, height): (u32, u32) =
util::adjust_size(window, min_size, is_decorated).into();
(*mmi).ptMinTrackSize = POINT {
x: width as i32,
y: height as i32,
};
}
if let Some(max_size) = window_state.max_size {
let max_size = max_size.to_physical(window_state.scale_factor);
let (width, height): (u32, u32) = util::adjust_size(window, max_size).into();
let (width, height): (u32, u32) =
util::adjust_size(window, max_size, is_decorated).into();
(*mmi).ptMaxTrackSize = POINT {
x: width as i32,
y: height as i32,
Expand All @@ -1948,7 +1951,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
let new_scale_factor = dpi_to_scale_factor(new_dpi_x);
let old_scale_factor: f64;

let allow_resize = {
let (allow_resize, is_decorated) = {
let mut window_state = userdata.window_state.lock();
old_scale_factor = window_state.scale_factor;
window_state.scale_factor = new_scale_factor;
Expand All @@ -1957,12 +1960,23 @@ unsafe fn public_window_callback_inner<T: 'static>(
return 0;
}

window_state.fullscreen.is_none()
&& !window_state.window_flags().contains(WindowFlags::MAXIMIZED)
(
window_state.fullscreen.is_none()
&& !window_state.window_flags().contains(WindowFlags::MAXIMIZED),
window_state
.window_flags()
.contains(WindowFlags::DECORATIONS),
)
};

let style = GetWindowLongW(window, GWL_STYLE) as u32;
let mut style = GetWindowLongW(window, GWL_STYLE) as u32;
let style_ex = GetWindowLongW(window, GWL_EXSTYLE) as u32;
// if the window isn't decorated, remove `WS_SIZEBOX` and `WS_CAPTION` so
// `AdjustWindowRect*` functions doesn't account for the hidden caption and borders and
// calculates a correct size for the client area.
if !is_decorated {
style &= !(WS_CAPTION | WS_SIZEBOX);
}

// New size as suggested by Windows.
let suggested_rect = *(lparam as *const RECT);
Expand Down Expand Up @@ -2177,6 +2191,33 @@ unsafe fn public_window_callback_inner<T: 'static>(
DefWindowProcW(window, msg, wparam, lparam)
}

WM_NCCALCSIZE => {
let win_flags = userdata.window_state.lock().window_flags();

if !win_flags.contains(WindowFlags::DECORATIONS) {
let params = &mut *(lparam as *mut NCCALCSIZE_PARAMS);

// adjust the maximized borderless window to fill the work area rectangle of the display monitor and doesn't cover the taskbar
if util::is_maximized(window) {
let monitor = monitor::current_monitor(window);
if let Ok(monitor_info) = monitor::get_monitor_info(monitor.hmonitor()) {
params.rgrc[0] = monitor_info.monitorInfo.rcWork;
}
// } else {
// let decoration_thickness = util::hwnd_decoration_thickness(window, true);
// let rect = &mut params.rgrc[0];
// // We add `1` to the thickness to account for 1px of cross-monitor spill-over.
// // We should probably calculate this some way rather than just emedding a constant offset.
// rect.left += decoration_thickness.left + 1;
// rect.right -= decoration_thickness.right + 1;
// rect.bottom -= decoration_thickness.bottom + 1;
}
0 // return 0 here to make the window borderless aka without decorations
} else {
DefWindowProcW(window, msg, wparam, lparam)
}
}

_ => {
if msg == *DESTROY_MSG_ID {
DestroyWindow(window);
Expand Down
13 changes: 12 additions & 1 deletion src/platform_impl/windows/event_loop/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ use std::{
use windows_sys::Win32::{
Foundation::HWND,
Graphics::Gdi::{RedrawWindow, RDW_INTERNALPAINT},
UI::WindowsAndMessaging::GWL_USERDATA,
};

use crate::{
dpi::PhysicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow,
platform_impl::platform::util,
platform_impl::platform::{get_window_long, util, window_state::WindowFlags},
window::WindowId,
};

Expand Down Expand Up @@ -434,10 +435,20 @@ impl<T> BufferedEvent<T> {
new_inner_size: &mut new_inner_size,
},
});
let is_decorated = unsafe {
let userdata =
get_window_long((window_id.0).0, GWL_USERDATA) as *mut super::WindowData<T>;
(*userdata)
.window_state
.lock()
.window_flags()
.contains(WindowFlags::DECORATIONS)
};
util::set_inner_size_physical(
(window_id.0).0,
new_inner_size.width as _,
new_inner_size.height as _,
is_decorated,
);
}
}
Expand Down
83 changes: 74 additions & 9 deletions src/platform_impl/windows/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,19 @@ use windows_sys::{
Input::KeyboardAndMouse::GetActiveWindow,
WindowsAndMessaging::{
AdjustWindowRectEx, ClipCursor, GetClientRect, GetClipCursor, GetMenu,
GetSystemMetrics, GetWindowLongW, GetWindowRect, SetWindowPos, ShowCursor,
GetSystemMetrics, GetWindowPlacement, GetWindowRect, SetWindowPos, ShowCursor,
GWL_EXSTYLE, GWL_STYLE, 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, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOMOVE,
SWP_NOREPOSITION, SWP_NOZORDER, WINDOW_EX_STYLE, WINDOW_STYLE,
SWP_NOREPOSITION, SWP_NOZORDER, SW_MAXIMIZE, WINDOWPLACEMENT, WINDOW_EX_STYLE,
WINDOW_STYLE, WS_CAPTION, WS_SIZEBOX,
},
},
},
};

use crate::{dpi::PhysicalSize, window::CursorIcon};
use crate::{dpi::PhysicalSize, platform_impl::platform::get_window_long, window::CursorIcon};

pub fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
string.as_ref().encode_wide().chain(once(0)).collect()
Expand Down Expand Up @@ -93,19 +94,19 @@ pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> {
}
}

pub fn adjust_size(hwnd: HWND, size: PhysicalSize<u32>) -> PhysicalSize<u32> {
pub fn adjust_size(hwnd: HWND, size: PhysicalSize<u32>, is_decorated: bool) -> 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 = adjust_window_rect(hwnd, rect).unwrap_or(rect);
let rect = adjust_window_rect(hwnd, rect, is_decorated).unwrap_or(rect);
PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _)
}

pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) {
pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32, is_decorated: bool) {
unsafe {
let rect = adjust_window_rect(
window,
Expand All @@ -115,6 +116,7 @@ pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) {
bottom: y as i32,
right: x as i32,
},
is_decorated,
)
.expect("adjust_window_rect failed");

Expand All @@ -133,10 +135,16 @@ pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) {
}
}

pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option<RECT> {
pub fn adjust_window_rect(hwnd: HWND, rect: RECT, is_decorated: bool) -> Option<RECT> {
unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
let style_ex = GetWindowLongW(hwnd, GWL_EXSTYLE) as u32;
let mut style = get_window_long(hwnd, GWL_STYLE) as u32;
// if the window isn't decorated, remove `WS_SIZEBOX` and `WS_CAPTION` so
// `AdjustWindowRect*` functions doesn't account for the hidden caption and borders and
// calculates a correct size for the client area.
if !is_decorated {
style &= !(WS_CAPTION | WS_SIZEBOX);
}
let style_ex = get_window_long(hwnd, GWL_EXSTYLE) as u32;
adjust_window_rect_with_styles(hwnd, style, style_ex, rect)
}
}
Expand Down Expand Up @@ -209,6 +217,63 @@ pub fn is_focused(window: HWND) -> bool {
window == unsafe { GetActiveWindow() }
}

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
}
}

// pub fn hwnd_decoration_thickness(hwnd: HWND, border_only: bool) -> RECT {
// unsafe {
// let style = get_window_long(hwnd, GWL_STYLE) as u32;
// let style_ex = get_window_long(hwnd, GWL_EXSTYLE) as u32;

// let adjust_style = if !border_only {
// style
// } else {
// style & !WS_CAPTION
// };
// let mut decoration_thickness = RECT {
// left: 0,
// top: 0,
// right: 0,
// bottom: 0,
// };
// if has_flag(style, WS_SIZEBOX) {
// #[allow(non_snake_case)]
// if let Some(AdjustWindowRectExForDpi) = *ADJUST_WINDOW_RECT_EX_FOR_DPI {
// AdjustWindowRectExForDpi(
// &mut decoration_thickness,
// adjust_style,
// false as _,
// style_ex,
// hwnd_dpi(hwnd),
// );
// } else {
// AdjustWindowRectEx(
// &mut decoration_thickness,
// adjust_style,
// false as _,
// style_ex,
// );
// }
// decoration_thickness.left *= -1;
// decoration_thickness.top *= -1;
// } else if has_flag(style, WS_BORDER) {
// decoration_thickness = RECT {
// left: 1,
// top: 1,
// right: 1,
// bottom: 1,
// };
// }
// decoration_thickness
// }
// }

pub fn get_instance_handle() -> HINSTANCE {
// Gets the instance handle by taking the address of the
// pseudo-variable created by the microsoft linker:
Expand Down
8 changes: 7 additions & 1 deletion src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ impl Window {
let (width, height) = size.to_physical::<u32>(scale_factor).into();

let window_state = Arc::clone(&self.window_state);

let is_decorated = window_state
.lock()
.window_flags
.contains(WindowFlags::DECORATIONS);

let window = self.window.clone();
self.thread_executor.execute_in_thread(move || {
let _ = &window;
Expand All @@ -211,7 +217,7 @@ impl Window {
});
});

util::set_inner_size_physical(self.hwnd(), width, height);
util::set_inner_size_physical(self.hwnd(), width, height, is_decorated);
}

#[inline]
Expand Down
Loading