Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 12 additions & 7 deletions src/native_interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,15 +315,20 @@ pub fn embed_as_child(hwnd: HWND, parent: HWND) {
unsafe {
let current_parent = GetParent(hwnd).ok();
let ex_style = GetWindowLongW(hwnd, GWL_EXSTYLE);
let _ = SetWindowLongW(
hwnd,
GWL_EXSTYLE,
(ex_style | WS_EX_TOOLWINDOW.0 as i32 | WS_EX_NOACTIVATE.0 as i32)
& !(WS_EX_TOPMOST.0 as i32),
);

let new_ex_style = (ex_style | WS_EX_TOOLWINDOW.0 as i32 | WS_EX_NOACTIVATE.0 as i32)
& !(WS_EX_TOPMOST.0 as i32);
let style = GetWindowLongW(hwnd, GWL_STYLE) as u32;
let new_style = (style & !WS_POPUP_STYLE) | WS_CHILD_STYLE | WS_CLIPSIBLINGS_STYLE;
// Already hosted by this parent with the hosted styles: nothing to do.
// Rewriting the styles and forcing SWP_FRAMECHANGED on every render is
// a window placement change each time, and Windows answers every
// placement change by posting an unmoved WM_MOUSEMOVE to the window
// under the pointer (apps that hide the pointer while typing then
// show it again on every widget refresh).
if current_parent == Some(parent) && new_ex_style == ex_style && new_style == style {
return;
}
let _ = SetWindowLongW(hwnd, GWL_EXSTYLE, new_ex_style);
let _ = SetWindowLongW(hwnd, GWL_STYLE, new_style as i32);

if current_parent != Some(parent) {
Expand Down
34 changes: 34 additions & 0 deletions src/window/layered_window_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::cell::RefCell;

thread_local! {
static LAYERED_TRANSITIONS: RefCell<Vec<bool>> = const { RefCell::new(Vec::new()) };
static FRAME_CHANGES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}

unsafe extern "system" fn observe_layered_style(
Expand All @@ -21,6 +22,12 @@ unsafe extern "system" fn observe_layered_style(
});
}
}
if message == WM_WINDOWPOSCHANGING {
let position = &*(lparam.0 as *const WINDOWPOS);
if position.flags.contains(SWP_FRAMECHANGED) {
FRAME_CHANGES.with(|changes| changes.set(changes.get() + 1));
}
}
DefWindowProcW(hwnd, message, wparam, lparam)
}

Expand Down Expand Up @@ -89,6 +96,10 @@ fn take_transitions() -> Vec<bool> {
LAYERED_TRANSITIONS.with(|transitions| std::mem::take(&mut *transitions.borrow_mut()))
}

fn take_frame_changes() -> usize {
FRAME_CHANGES.with(|changes| changes.replace(0))
}

#[test]
fn watchdog_tray_requests_leave_a_delayed_reposition_pending() {
let window = TestWindow::new(WINDOW_EX_STYLE::default());
Expand Down Expand Up @@ -175,3 +186,26 @@ fn reparenting_desktop_composition_windows_does_not_enable_layering() {
assert_eq!(ex_style & WS_EX_LAYERED.0, 0);
assert_ne!(ex_style & WS_EX_NOREDIRECTIONBITMAP.0, 0);
}

#[test]
fn re_embedding_a_hosted_surface_does_not_change_its_frame() {
let parent = TestWindow::new(WINDOW_EX_STYLE::default());
let window = TestWindow::new(WS_EX_LAYERED);
native_interop::embed_as_child(window.0, parent.0);
assert!(take_frame_changes() > 0);

// Routine renders re-embed into the same host. A frame change there is a
// window placement change, which makes Windows post an unmoved
// WM_MOUSEMOVE to whichever window is under the pointer.
for _ in 0..3 {
native_interop::embed_as_child(window.0, parent.0);
}
assert_eq!(take_frame_changes(), 0);

// A surface that left the hosted form is still converted back.
native_interop::make_popup(window.0, true);
take_frame_changes();
native_interop::embed_as_child(window.0, parent.0);
assert!(take_frame_changes() > 0);
assert_eq!(unsafe { GetParent(window.0) }.unwrap(), parent.0);
}
48 changes: 39 additions & 9 deletions src/window/positioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,32 @@ pub(super) fn render_custom_window(
}
}

/// True when a hosted child is already the top sibling at exactly this
/// rectangle (origin in `parent` client coordinates).
unsafe fn hosted_child_is_placed(
hwnd: HWND,
parent: HWND,
origin: POINT,
width: i32,
height: i32,
) -> bool {
if GetTopWindow(Some(parent)).ok() != Some(hwnd) {
return false;
}
let Some(rect) = native_interop::get_window_rect_safe(hwnd) else {
return false;
};
let mut current = [POINT {
x: rect.left,
y: rect.top,
}];
MapWindowPoints(None, Some(parent), &mut current);
current[0].x == origin.x
&& current[0].y == origin.y
&& rect.right - rect.left == width
&& rect.bottom - rect.top == height
}

pub(super) fn position_custom_theme(hwnd: HWND, theme: &ThemeDocument, scale: f64) -> bool {
position_custom_theme_internal(hwnd, theme, scale)
}
Expand Down Expand Up @@ -320,15 +346,19 @@ pub(super) fn position_custom_theme_internal(
ensure_tray_event_hook_for_taskbar(hwnd, taskbar.hwnd);
let mut point = [POINT { x, y }];
MapWindowPoints(None, Some(taskbar.hwnd), &mut point);
let _ = SetWindowPos(
hwnd,
Some(HWND_TOP),
point[0].x,
point[0].y,
width,
height,
SWP_NOACTIVATE,
);
// Routine renders land on the same rectangle; only touch the
// window when its place or z-order actually has to change.
if !hosted_child_is_placed(hwnd, taskbar.hwnd, point[0], width, height) {
let _ = SetWindowPos(
hwnd,
Some(HWND_TOP),
point[0].x,
point[0].y,
width,
height,
SWP_NOACTIVATE,
);
}
}
SurfaceNest::Desktop => {
if let Some(desktop) = native_interop::find_desktop_host() {
Expand Down