feat(e2e): let the test window rename itself and go fullscreen

--rename-to T --after S gives the window another title S seconds after it
opens, as a program that sets its title late does. The message WM_APP + 2
takes it fullscreen, borderless over its monitor, and back.
This commit is contained in:
LGUG2Z committed 2026-10-30 18:22:44 -07:00
1 parent e22d82853c
commit ea449c2d80
1 file changed
+91 -2
+91 -2
View File
@@ -14,8 +14,13 @@
//! ```
//!
//! Options: `--title T`, `--class C` (the window class, `KomorebiTestWin` by
//! default), `--color RRGGBB`, `--size WxH`, `--min-size WxH` and
//! `--fixed` (not resizable).
//! default), `--color RRGGBB`, `--size WxH`, `--min-size WxH`, `--fixed` (not
//! resizable) and `--rename-to T` with `--after SECONDS` (the window gives
//! itself that title after that long, as a program that sets it late does).
//!
//! The message `WM_APP + 2` takes the window fullscreen (`wparam` 1: a
//! borderless window over its monitor, as a program's own key does) or brings
//! it back (`wparam` 0).
#![cfg(windows)]
@@ -41,8 +46,12 @@ use windows::Win32::Graphics::Gdi::CreateSolidBrush;
use windows::Win32::Graphics::Gdi::DeleteObject;
use windows::Win32::Graphics::Gdi::EndPaint;
use windows::Win32::Graphics::Gdi::FillRect;
use windows::Win32::Graphics::Gdi::GetMonitorInfoW;
use windows::Win32::Graphics::Gdi::HGDIOBJ;
use windows::Win32::Graphics::Gdi::InvalidateRect;
use windows::Win32::Graphics::Gdi::MONITOR_DEFAULTTONEAREST;
use windows::Win32::Graphics::Gdi::MONITORINFO;
use windows::Win32::Graphics::Gdi::MonitorFromWindow;
use windows::Win32::Graphics::Gdi::PAINTSTRUCT;
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT;
@@ -50,6 +59,15 @@ use windows::Win32::UI::WindowsAndMessaging::CreateWindowExW;
use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW;
use windows::Win32::UI::WindowsAndMessaging::DestroyWindow;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW;
use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE;
use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::GetWindowRect;
use windows::Win32::UI::WindowsAndMessaging::HWND_TOP;
use windows::Win32::UI::WindowsAndMessaging::SWP_FRAMECHANGED;
use windows::Win32::UI::WindowsAndMessaging::SWP_NOOWNERZORDER;
use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW;
use windows::Win32::UI::WindowsAndMessaging::WS_POPUP;
use windows::Win32::UI::WindowsAndMessaging::WS_VISIBLE;
use windows::Win32::UI::WindowsAndMessaging::GetMessageW;
use windows::Win32::UI::WindowsAndMessaging::HMENU;
use windows::Win32::UI::WindowsAndMessaging::IDC_ARROW;
@@ -82,6 +100,12 @@ use windows::core::PCWSTR;
/// Posted to the window when a command is waiting in [`COMMANDS`].
const WM_COMMAND_READY: u32 = WM_APP + 1;
/// Posted to the window to take it fullscreen (`wparam` 1) or back (0).
const WM_FULLSCREEN: u32 = WM_APP + 2;
/// The style and the rectangle of the window before it went fullscreen.
static BEFORE_FULLSCREEN: Mutex<Option<(isize, RECT)>> = Mutex::new(None);
static COMMANDS: Mutex<VecDeque<String>> = Mutex::new(VecDeque::new());
static HWND_RAW: AtomicIsize = AtomicIsize::new(0);
static COLOR: AtomicU32 = AtomicU32::new(0x0080_5030);
@@ -185,6 +209,49 @@ fn run_command(hwnd: HWND, line: &str) -> bool {
true
}
fn set_fullscreen(hwnd: HWND, on: bool) {
let mut before = BEFORE_FULLSCREEN.lock().unwrap();
// SAFETY: plain Win32 calls on this process's own window, on the thread
// that owns it.
unsafe {
if on && before.is_none() {
let style = GetWindowLongPtrW(hwnd, GWL_STYLE);
let mut rect = RECT::default();
let _ = GetWindowRect(hwnd, &mut rect);
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
let mut info = MONITORINFO {
cbSize: size_of::<MONITORINFO>() as u32,
..Default::default()
};
if GetMonitorInfoW(monitor, &mut info).as_bool() {
*before = Some((style, rect));
let area = info.rcMonitor;
SetWindowLongPtrW(hwnd, GWL_STYLE, (WS_POPUP | WS_VISIBLE).0 as isize);
let _ = SetWindowPos(
hwnd,
Some(HWND_TOP),
area.left,
area.top,
area.right - area.left,
area.bottom - area.top,
SWP_FRAMECHANGED | SWP_NOOWNERZORDER,
);
}
} else if !on && let Some((style, rect)) = before.take() {
SetWindowLongPtrW(hwnd, GWL_STYLE, style);
let _ = SetWindowPos(
hwnd,
None,
rect.left,
rect.top,
rect.right - rect.left,
rect.bottom - rect.top,
SWP_FRAMECHANGED | SWP_NOOWNERZORDER | SWP_NOZORDER,
);
}
}
}
extern "system" fn wndproc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
match msg {
WM_COMMAND_READY => {
@@ -200,6 +267,10 @@ extern "system" fn wndproc(hwnd: HWND, msg: u32, wparam: WPARAM, lparam: LPARAM)
}
LRESULT(0)
}
WM_FULLSCREEN => {
set_fullscreen(hwnd, wparam.0 != 0);
LRESULT(0)
}
WM_ERASEBKGND => LRESULT(1),
WM_PAINT => {
let mut ps = PAINTSTRUCT::default();
@@ -248,6 +319,9 @@ fn main() -> windows::core::Result<()> {
let mut size = (400, 300);
let mut fixed = false;
let mut class_name = wide("KomorebiTestWin");
let mut rename: Option<(String, f64)> = None;
let mut rename_to = None;
let mut after = 0.0;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
@@ -268,12 +342,17 @@ fn main() -> windows::core::Result<()> {
*MIN_SIZE.lock().unwrap() = args.next().as_deref().and_then(parse_size);
}
"--fixed" => fixed = true,
"--rename-to" => rename_to = args.next(),
"--after" => after = args.next().and_then(|s| s.parse().ok()).unwrap_or(0.0),
other => eprintln!("komorebi-testwin: ignoring {other}"),
}
}
if fixed {
*FIXED.lock().unwrap() = Some(size);
}
if let Some(new) = rename_to {
rename = Some((new, after));
}
// SAFETY: plain Win32 setup on the main thread, which then runs the loop.
unsafe {
@@ -309,6 +388,16 @@ fn main() -> windows::core::Result<()> {
HWND_RAW.store(hwnd.0 as isize, Ordering::SeqCst);
let _ = ShowWindow(hwnd, SW_SHOW);
if let Some((new, after)) = rename {
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs_f64(after));
let hwnd = HWND(HWND_RAW.load(Ordering::SeqCst) as *mut _);
let title = wide(&new);
// SAFETY: `title` is NUL-terminated and outlives the call.
let _ = unsafe { SetWindowTextW(hwnd, PCWSTR(title.as_ptr())) };
});
}
std::thread::spawn(move || {
let hwnd = HWND(HWND_RAW.load(Ordering::SeqCst) as *mut _);
for line in std::io::stdin().lock().lines() {