Files
punktfunk/crates/pf-presenter/src/win32.rs
T
enricobuehler 65cd388a52 fix(presenter,core): close the last of the proof-lint hole — the Vulkan contract, stated once
pf-presenter's 120 sites are `ash` calls almost without exception, so 120 independent arguments
would have been 120 restatements of the signature — the exact noise this program exists to remove.
They get the `abi.rs` treatment instead: the Vulkan contract stated once in `lib.rs`, each site
naming which of three shapes it is.

The three are not equal, and separating them is the point. CREATE and RECORD carry no real
precondition — the device is owned, the builders are locals, nothing executes until submit. DESTROY
does: the GPU must not still be using the object, and that is established by the path (a fence wait,
a `queue_wait_idle`, a retired swapchain), not by the call. Those sites say so, because getting it
wrong is a use-after-free no type catches. The contract also tells the next person that a block
outside the three shapes needs a real proof, and that writing "as above" is the signal it doesn't
belong in them.

punktfunk-core's Windows half is finished here too: `qos_windows.rs`'s `GetLastError` reads (called
before anything can reset the thread's error slot) and `udp/windows.rs`'s control-message write,
whose argument is that `ctrl` is sized by `WSA_CMSG_SPACE(4)` — computed two lines up — so header
plus payload cannot run past it, and `write_unaligned` is used because `WSA_CMSG_DATA` offers no
alignment guarantee.

⚠️ THE WINDOWS BLIND SPOT BIT A THIRD TIME. A Linux measurement put this crate pair at 113; the
real number was 129 — `d3d11.rs`, `win32.rs`, `qos_windows.rs`, `udp/windows.rs` are all
`cfg`-hidden. Every crate in this sweep had to be finished on .47 after being "done" on .21. For a
cross-platform crate the Linux number is a lower bound, never the answer.

All three crates now deny `undocumented_unsafe_blocks`, which was the goal: it applied to 8 of 11
crates carrying unsafe, and the three exempt ones held 381 items between them — including the C ABI
surface and the presenter. Verified: Linux .21 fmt + both CI clippy steps rc=0; Windows .47 all four
closed crates clippy `-D warnings` rc=0 plus the full Windows CI clippy set and pf-capture's tests.
Only small crates remain unguarded (75 items total, largest 26).
2026-07-29 08:48:42 +02:00

73 lines
3.8 KiB
Rust

//! Windows-only window branding for the SDL session window.
//!
//! SDL registers its window class with a generic icon, so without help the taskbar and
//! Alt-Tab show the SDL default even when the exe embeds ours. [`stamp_window_icon`]
//! stamps the exe's icon resource (ordinal 1, when embedded — see the session binary's
//! `build.rs`) onto the window via `WM_SETICON`, at the native small/big metrics so
//! nothing is scaled at draw time — the same treatment the WinUI shell gives its window.
//!
//! [`set_app_user_model_id`] gives unpackaged (dev/CLI) runs the same explicit
//! AppUserModelID as the shell, so the two windows group as ONE taskbar app across the
//! shell⇄session visibility handoff. MSIX runs already share the package identity, and
//! overriding it would detach the window from the Start-menu pin — so packaged processes
//! are left alone.
use windows_sys::core::w;
use windows_sys::Win32::Foundation::{APPMODEL_ERROR_NO_PACKAGE, HWND, LPARAM, WPARAM};
use windows_sys::Win32::Storage::Packaging::Appx::GetCurrentPackageFullName;
use windows_sys::Win32::System::LibraryLoader::GetModuleHandleW;
use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID;
use windows_sys::Win32::UI::WindowsAndMessaging::{
GetSystemMetrics, LoadImageW, SendMessageW, ICON_BIG, ICON_SMALL, IMAGE_ICON, LR_DEFAULTCOLOR,
SM_CXICON, SM_CXSMICON, WM_SETICON,
};
/// The shared taskbar identity — must stay in sync with the WinUI shell's
/// (`clients/windows/src/main.rs`), or the two windows stop grouping.
const APP_USER_MODEL_ID: *const u16 = w!("unom.punktfunk.client");
/// Tag this process with the shared AppUserModelID — unpackaged runs only (a packaged
/// process already carries the MSIX identity, which must win). Call before any window
/// exists; later calls don't re-tag existing windows.
pub(crate) fn set_app_user_model_id() {
// SAFETY: `GetCurrentPackageFullName` is called with `len = 0` and a NULL buffer, which is the
// documented identity PROBE — it writes nothing and only reports whether the process is
// packaged. `SetCurrentProcessExplicitAppUserModelID` takes a static wide literal.
unsafe {
let mut len: u32 = 0;
// No buffer: just probe whether the process has package identity.
if GetCurrentPackageFullName(&mut len, std::ptr::null_mut()) != APPMODEL_ERROR_NO_PACKAGE {
return; // packaged (or indeterminate) — leave the identity alone
}
let _ = SetCurrentProcessExplicitAppUserModelID(APP_USER_MODEL_ID);
}
}
/// Stamp the exe-embedded icon (resource ordinal 1) onto the SDL window's title bar,
/// taskbar and Alt-Tab. A quiet no-op when the exe embeds no icon.
pub(crate) fn stamp_window_icon(window: &sdl3::video::Window) {
// SAFETY: the SDL property lookups return the `HWND` SDL itself owns for this live `window`
// borrow; the icon calls only pass that handle and a resource ordinal back to Win32, and every
// result is checked before use.
unsafe {
// The HWND behind the SDL window, via SDL3's window properties (the same route
// the sdl3 crate's raw-window-handle integration takes).
let hwnd: HWND = sdl3::sys::properties::SDL_GetPointerProperty(
sdl3::sys::video::SDL_GetWindowProperties(window.raw()),
sdl3::sys::video::SDL_PROP_WINDOW_WIN32_HWND_POINTER,
std::ptr::null_mut(),
) as HWND;
if hwnd.is_null() {
return;
}
let module = GetModuleHandleW(std::ptr::null());
for (which, metric) in [(ICON_SMALL, SM_CXSMICON), (ICON_BIG, SM_CXICON)] {
let px = GetSystemMetrics(metric);
let icon = LoadImageW(module, 1 as *const u16, IMAGE_ICON, px, px, LR_DEFAULTCOLOR);
if !icon.is_null() {
SendMessageW(hwnd, WM_SETICON, which as WPARAM, icon as LPARAM);
}
}
}
}