Files
punktfunk/clients/windows/src/shell_window.rs
T
enricobuehlerandClaude Fable 5 35c61fee64 chore(client): the windows-rs pin moves a month forward, onto the SDK-metadata bindings
The July 2026 windows-rs brings a reconciler keyed-child-order fix (#4728), widget
validation (#4727), a DPI collision fix (#4751), icon elements (#4736), multi-window
support (#4730) and scroll virtualization (#4710) — the re-render fixes the Windows
client has been working around at the architecture level. All three pinned deps
(windows-reactor, windows, windows-reactor-setup) move together so windows-core
stays unified across the swap-chain hand-off, and pf-client-core moves with them.

The bulk of the diff is #4689: windows/windows-sys now generate straight from the
Windows SDK, so the `Win32_*` namespace features became one feature per SDK header
(winuser, dxgi, d3d11, …), the PascalCase namespace modules became header-named
modules, struct-returning COM methods take explicit out-params and return HRESULT,
Win32 functions return their raw BOOL/HANDLE instead of Result, and flag constants
are plain integers. Both crates' Win32 code is rewritten to that shape; behaviour
is unchanged on every path.

Riding along, all already stale before the bump: the README and the three Windows
workflows stop claiming windows-reactor's build.rs needs CARGO_WORKSPACE_DIR (that
build.rs no longer exists — staging moved to windows-reactor-setup via OUT_DIR);
the README layout section stops describing modules that moved into the session
binary long ago and gains the manual smoke checklist; the notices generator learns
the SPDX for crates that ship license files without a `license` field, which turns
windows-reactor-setup's UNKNOWN into MIT OR Apache-2.0; and the crate records its
real rust-version (1.96) instead of inheriting the workspace's 1.82.

Verified: cargo check/clippy/fmt clean on punktfunk-client-windows, pf-client-core
and punktfunk-client-session; both bins build; --discover finds the LAN hosts; the
GUI shell comes up (WinAppSDK bootstrap intact under the new reactor-setup).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 18:31:47 +02:00

79 lines
3.1 KiB
Rust

//! Hide/restore the shell's top-level window around a spawned session, so exactly ONE
//! Punktfunk window is visible at a time: the spawned stream/browse window IS the app
//! while it runs (hidden = no taskbar entry, no Alt-Tab ghost), and the shell reappears
//! the moment the child exits — every exit path funnels through the spawn reader's
//! `Exited` event (clean end, error, crash, Disconnect kill), so the shell can never stay
//! hidden with no child.
//!
//! windows-reactor exposes no window handle, so the HWND is resolved by its (unique)
//! title and cached — the same pattern as `app::apply_window_icon_when_ready` and
//! `stream::window_dpi`.
use std::sync::atomic::{AtomicIsize, Ordering};
use windows::Win32::windef::{HWND, RECT};
use windows::Win32::winuser::{
FindWindowW, GetWindowRect, IsWindow, SetForegroundWindow, ShowWindow, SW_HIDE, SW_SHOW,
};
static SHELL_HWND: AtomicIsize = AtomicIsize::new(0);
fn shell_hwnd() -> Option<HWND> {
// SAFETY: the cached value is an `HWND` this process obtained itself; it is re-validated with
// `IsWindow` before use (a stale handle is treated as absent), and the lookup calls take only
// static wide literals.
unsafe {
let cached = SHELL_HWND.load(Ordering::Relaxed);
if cached != 0 {
let h = HWND(cached as *mut _);
if IsWindow(Some(h)).as_bool() {
return Some(h);
}
}
let h = FindWindowW(None, windows::core::w!("Punktfunk"));
if h.0.is_null() {
return None;
}
SHELL_HWND.store(h.0 as isize, Ordering::Relaxed);
Some(h)
}
}
/// Hide the shell while a spawned session window is up. Called on the child's
/// `{"ready":true}` (its window has presented — never earlier, so a failed connect keeps
/// the shell in view with its error banner).
pub(crate) fn hide() {
if let Some(h) = shell_hwnd() {
// SAFETY: `h` is the validated shell window handle from `shell_hwnd`; `ShowWindow` takes it
// plus a plain flag and dereferences nothing.
unsafe {
let _ = ShowWindow(h, SW_HIDE);
}
}
}
/// Bring the shell back (and to the foreground) when the child exits. Safe to call when
/// it was never hidden — showing a visible window is a no-op.
pub(crate) fn restore() {
if let Some(h) = shell_hwnd() {
// SAFETY: as `hide` — `h` is the validated shell window handle, and both calls take only
// that handle plus a plain flag.
unsafe {
let _ = ShowWindow(h, SW_SHOW);
let _ = SetForegroundWindow(h);
}
}
}
/// The shell window's top-left in desktop coordinates — passed to the spawned session
/// (`--window-pos`) so its window opens on the SAME monitor, roughly where the shell is,
/// and the visibility handoff reads as one window changing content.
pub(crate) fn position() -> Option<(i32, i32)> {
let h = shell_hwnd()?;
let mut r = RECT::default();
// SAFETY: `h` is the validated shell window handle and `r` is a live local the call fills.
if !unsafe { GetWindowRect(h, &mut r) }.as_bool() {
return None;
}
Some((r.left, r.top))
}