Skip to content
Merged
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
22 changes: 19 additions & 3 deletions crates/gpui_windows/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,10 @@ impl WindowsWindowInner {
// A minimized window has no client area to draw, so stop requesting frames until it is
// restored. The renderer keeps its size: `WM_SIZE` reports 0x0 while minimized.
if wparam.0 == SIZE_MINIMIZED as usize {
self.state
.restore_from_minimized
.set(self.state.callbacks.request_frame.take());
park_frame_callback(
&self.state.callbacks.request_frame,
&self.state.restore_from_minimized,
);
return Some(0);
}

Expand Down Expand Up @@ -1771,3 +1772,18 @@ fn notify_frame_changed(handle: HWND) {
.log_err();
}
}

/// Move the frame callback into the minimized-window slot until the window is restored.
///
/// A window that is already minimized gets `WM_SIZE(SIZE_MINIMIZED)` again whenever it is moved,
/// resized or minimized a second time while iconic, and Windows does that on its own when the
/// display layout changes (a monitor sleeping, waking or reconnecting, a resolution change, a
/// remote desktop session). The callback is then already parked and `request_frame` is empty, so
/// parking unconditionally would overwrite the parked callback with `None`. The restore would
/// find nothing to put back, and every later `WM_PAINT` would return before drawing: the window
/// keeps its last frame forever while input still reaches it.
fn park_frame_callback<T>(request_frame: &Cell<Option<T>>, parked: &Cell<Option<T>>) {
if let Some(callback) = request_frame.take() {
parked.set(Some(callback));
}
}
85 changes: 85 additions & 0 deletions crates/gpui_windows/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,91 @@ mod tests {
Ok(())
}

/// A minimized window can get `WM_SIZE(SIZE_MINIMIZED)` again before it is restored. It must
/// still draw after the restore: losing its frame callback leaves the last frame on screen for
/// good while input still reaches it.
#[test]
fn a_window_minimized_twice_still_draws_after_restore() -> anyhow::Result<()> {
use super::WindowsPlatform;
use crate::WindowsWindow;
use gpui::{
AnyWindowHandle, Bounds, PlatformWindow as _, WindowHandle, WindowId, WindowKind,
WindowParams, point, px, size,
};
use std::{cell::Cell, rc::Rc};
use windows::Win32::Foundation::{LPARAM, WPARAM};
use windows::Win32::Graphics::Gdi::{RDW_INVALIDATE, RDW_UPDATENOW, RedrawWindow};
use windows::Win32::UI::WindowsAndMessaging::{
DestroyWindow, SIZE_MINIMIZED, SW_RESTORE, SW_SHOWMINNOACTIVE, SW_SHOWNOACTIVATE,
SendMessageW, ShowWindow, WM_SIZE,
};

struct Root;
impl gpui::Render for Root {
fn render(
&mut self,
_: &mut gpui::Window,
_: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
gpui::Empty
}
}

let platform = WindowsPlatform::new(false)?;
let handle: AnyWindowHandle = WindowHandle::<Root>::new(WindowId::from(1)).into();
let window = WindowsWindow::new(
handle,
WindowParams {
bounds: Bounds::new(point(px(100.), px(100.)), size(px(400.), px(300.))),
titlebar: None,
kind: WindowKind::Normal,
is_movable: true,
app_owns_titlebar_drag: false,
is_resizable: true,
is_minimizable: true,
focus: false,
show: false,
icon: None,
display_id: None,
app_id: None,
window_min_size: None,
windows_renderer: Default::default(),
},
platform.generate_creation_info(),
)?;
let hwnd = window.get_raw_handle();
let frames = Rc::new(Cell::new(0));
window.on_request_frame(Box::new({
let frames = frames.clone();
move |_| frames.set(frames.get() + 1)
}));
let paint = || unsafe {
let _ = RedrawWindow(Some(hwnd), None, None, RDW_INVALIDATE | RDW_UPDATENOW);
};

unsafe {
let _ = ShowWindow(hwnd, SW_SHOWNOACTIVATE);
let _ = ShowWindow(hwnd, SW_SHOWMINNOACTIVE);
SendMessageW(
hwnd,
WM_SIZE,
Some(WPARAM(SIZE_MINIMIZED as usize)),
Some(LPARAM(0)),
);
let _ = ShowWindow(hwnd, SW_RESTORE);
}
let before = frames.get();
paint();

assert!(
frames.get() > before,
"the restored window must request a frame when it paints"
);

unsafe { DestroyWindow(hwnd)? };
Ok(())
}

#[test]
fn test_clipboard() {
let item = ClipboardItem::new_string("你好,我是张小白".to_string());
Expand Down
Loading