//! Ely's window on Windows: a bare popup owned by the main window, so it stays above it and hides with it, sized and moved onto an edge of its pane as it moves. use std::{ cell::Cell, sync::atomic::{AtomicIsize, Ordering}, }; use anyhow::bail; use gpui::{Bounds, Pixels, Window}; use raw_window_handle::{HasWindowHandle, RawWindowHandle}; use windows_sys::Win32::{ Foundation::{HWND, LPARAM, LRESULT, POINT, RECT, WPARAM}, Graphics::Gdi::{ GetMonitorInfoW, MONITOR_DEFAULTTONEAREST, MONITORINFO, MonitorFromRect, MonitorFromWindow, }, UI::{ HiDpi::{GetDpiForMonitor, GetDpiForWindow, MDT_EFFECTIVE_DPI}, WindowsAndMessaging::{ CallWindowProcW, GWL_STYLE, GWLP_HWNDPARENT, GWLP_WNDPROC, GetCursorPos, GetWindowInfo, GetWindowLongPtrW, HWND_NOTOPMOST, MA_NOACTIVATE, PostMessageW, SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SetWindowLongPtrW, SetWindowPos, USER_DEFAULT_SCREEN_DPI, WINDOWINFO, WM_APP, WM_DPICHANGED, WM_MOUSEACTIVATE, WNDPROC, WS_CAPTION, WS_POPUP, }, }, }; use super::{Edge, HEAD, ROOM, WIDTH, edge, left, pane}; #[cfg(test)] mod tests; /// gpui's window procedure, which hers passes every message on to but a press's activation and her places. static GPUI: AtomicIsize = AtomicIsize::new(0); thread_local! { /// The bottom left corner her last place gave her, on the thread her window lives on. static CORNER: Cell> = const { Cell::new(None) }; } /// Her place, posted to her window: Windows sends a new size at once, which gpui hears only outside its own updates. const PLACE: u32 = WM_APP; /// Her corner at a new DPI, posted so it follows the whole move that brought the change. const KEEP: u32 = WM_APP + 1; const _: () = assert!( size_of::() == 8, "a place packs two i32 into a word" ); /// Her window and the main one. pub struct Glue { pet: HWND, main: HWND, } impl Glue { pub fn new(pet: &Window, main: &Window) -> anyhow::Result { Self::hold(native(pet)?, native(main)?) } /// Gives her window the main window as owner, a bare popup's frame, none of the topmost place gpui gives a popup, and keeps a press from activating it, so the keys stay with the terminal. fn hold(pet: HWND, main: HWND) -> anyhow::Result { unsafe { SetWindowLongPtrW(pet, GWLP_HWNDPARENT, main as isize); // gpui's popup is overlapped, which Windows gives a caption and a frame. let style = (GetWindowLongPtrW(pet, GWL_STYLE) as u32 & !WS_CAPTION) | WS_POPUP; SetWindowLongPtrW(pet, GWL_STYLE, style as isize); let gpui = SetWindowLongPtrW(pet, GWLP_WNDPROC, inactive as *const () as isize); anyhow::ensure!(gpui != 0, "pet: her window's procedure does not read"); GPUI.store(gpui, Ordering::Relaxed); let flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_FRAMECHANGED; anyhow::ensure!( SetWindowPos(pet, HWND_NOTOPMOST, 0, 0, 0, 0, flags) != 0, "pet: her window keeps the topmost place or its frame" ); } Ok(Self { pet, main }) } /// Stands her on the main window's top edge while her box fits under the top of its display's work area, else on its bottom edge, `at` in as far as its width allows, and gives the edge. pub fn place(&self, main: &Window, at: f32) -> anyhow::Result { self.put(main.scale_factor(), pane(main), at) } /// The pointer's x on the screen, in points at `scale`. pub fn pointer(&self, scale: f32) -> anyhow::Result { let mut at = POINT::default(); let read = unsafe { GetCursorPos(&mut at) }; anyhow::ensure!(read != 0, "pet: the pointer's place does not read"); Ok(at.x as f32 / scale) } /// Posts her bottom left corner on an edge of `pane` at `scale`, `at` points in, in the main window's points; her window sizes her at the DPI it has when the place lands, as gpui did not when it opened her off the display. fn put(&self, scale: f32, pane: Bounds, at: f32) -> anyhow::Result { let mut main = WINDOWINFO { cbSize: size_of::() as u32, ..Default::default() }; let read = unsafe { GetWindowInfo(self.main, &mut main) }; anyhow::ensure!(read != 0, "pet: the main window's place does not read"); let mut display = MONITORINFO { cbSize: size_of::() as u32, ..Default::default() }; let monitor = unsafe { MonitorFromWindow(self.main, MONITOR_DEFAULTTONEAREST) }; let read = unsafe { GetMonitorInfoW(monitor, &mut display) }; anyhow::ensure!(read != 0, "pet: the main window's display does not read"); let (_, top) = stand(main.rcClient, scale, pane, Edge::Top, at); let edge = edge((top - display.rcWork.top) as f32 / scale); let corner = stand(main.rcClient, scale, pane, edge, at); let posted = unsafe { PostMessageW(self.pet, PLACE, pack(corner), 0) }; anyhow::ensure!(posted != 0, "pet: her place does not post"); log::debug!("pet: Ely is to stand on {corner:?}, the pane's {edge:?} edge"); Ok(edge) } } /// Her bottom left corner on `edge` of `pane`, `at` points in, at `scale` over the main window's `client` rect, in pixels. fn stand(client: RECT, scale: f32, pane: Bounds, edge: Edge, at: f32) -> (i32, i32) { let y = match edge { Edge::Top => pane.origin.y, Edge::Bottom => pane.bottom(), }; let x = f32::from(pane.origin.x) + left(pane.size.width, at); ( client.left + (x * scale).round() as i32, client.top + (f32::from(y) * scale).round() as i32, ) } /// Her box at `dpi` on her bottom left `corner`. fn boxed((left, bottom): (i32, i32), dpi: u32) -> RECT { let (width, height) = sized(dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32); RECT { left, top: bottom - height, right: left + width, bottom, } } /// Her box's size in pixels at `own` scale. fn sized(own: f32) -> (i32, i32) { ( (WIDTH * own).round() as i32, ((HEAD + ROOM) * own).round() as i32, ) } /// Her window's procedure: a press does not activate her window, she takes her places, and all else is gpui's. unsafe extern "system" fn inactive( window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM, ) -> LRESULT { let gpui: WNDPROC = unsafe { std::mem::transmute(GPUI.load(Ordering::Relaxed)) }; match message { WM_MOUSEACTIVATE => MA_NOACTIVATE as LRESULT, PLACE => { let corner = unpack(wparam); CORNER.set(Some(corner)); match unsafe { GetDpiForWindow(window) } { 0 => log::error!("pet: her window's DPI does not read at her place"), dpi => move_to(window, boxed(corner, dpi)), } 0 } WM_DPICHANGED => { // gpui takes the rect Windows suggests; her corner follows once the move is done. let answer = unsafe { CallWindowProcW(gpui, window, message, wparam, lparam) }; if unsafe { PostMessageW(window, KEEP, wparam & 0xFFFF, 0) } == 0 { log::error!("pet: her corner does not post at a new DPI"); } answer } KEEP => { let (dpi, current) = (wparam as u32, unsafe { GetDpiForWindow(window) }); if current == 0 { log::error!("pet: her window's DPI does not read at a new DPI"); return 0; } let Some(corner) = CORNER.get() else { log::debug!("pet: at {dpi} DPI Ely has no place to keep yet"); return 0; }; match keeping(corner, dpi, current, display_dpi) { Ok(Some(kept)) => { move_to(window, kept); log::debug!("pet: at {dpi} DPI Ely keeps her corner at {corner:?}"); } Ok(None) => log::debug!( "pet: a keep at {dpi} DPI, hers {current}, leaves Ely where Windows put her" ), Err(error) => log::error!("{error:#}"), } 0 } _ => unsafe { CallWindowProcW(gpui, window, message, wparam, lparam) }, } } /// Moves her window to `rect`. fn move_to(window: HWND, rect: RECT) { let (width, height) = (rect.right - rect.left, rect.bottom - rect.top); let flags = SWP_NOZORDER | SWP_NOACTIVATE; let null = std::ptr::null_mut(); if unsafe { SetWindowPos(window, null, rect.left, rect.top, width, height, flags) } == 0 { log::error!( "pet: her window does not move to {},{}", rect.left, rect.top ); } } /// Her box at `dpi` on the bottom left `corner` her place gave her, while `dpi` is still hers, `current`, and the box lies mostly on a display of that DPI, as `dpi_of` reads it, so the move changes no DPI and posts no other keep; else none, and Windows' rect stands. fn keeping( corner: (i32, i32), dpi: u32, current: u32, dpi_of: impl Fn(&RECT) -> anyhow::Result, ) -> anyhow::Result> { if dpi != current { return Ok(None); } let kept = boxed(corner, dpi); Ok((dpi_of(&kept)? == dpi).then_some(kept)) } /// The DPI of the display under most of `rect`, which Windows gives a window there. fn display_dpi(rect: &RECT) -> anyhow::Result { let monitor = unsafe { MonitorFromRect(rect, MONITOR_DEFAULTTONEAREST) }; let (mut x, mut y) = (0, 0); let read = unsafe { GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut x, &mut y) }; anyhow::ensure!( read == 0, "pet: the display under her box gives no DPI: {read:#x}" ); Ok(x) } fn pack((low, high): (i32, i32)) -> WPARAM { low as u32 as WPARAM | (high as u32 as WPARAM) << 32 } fn unpack(word: WPARAM) -> (i32, i32) { (word as u32 as i32, (word >> 32) as u32 as i32) } /// The HWND gpui made for `window`. fn native(window: &Window) -> anyhow::Result { let handle = HasWindowHandle::window_handle(window) .map_err(|error| anyhow::anyhow!("pet: no native handle: {error:?}"))?; let RawWindowHandle::Win32(win32) = handle.as_raw() else { bail!("pet: not a Win32 window"); }; Ok(win32.hwnd.get() as HWND) }