fix(picker): cascade thread DPI contexts and harden the round-three review points
- setThreadDpiAwareness checks SetThreadDpiAwarenessContext's return value
and cascades per-monitor-v2 -> per-monitor -> system-aware; DPI stays a
deliberate cosmetic best-effort - a host accepting none (or lacking the
API, pre-1607) still gets the modern dialog instead of a downgrade to the
legacy fallback chain over a cosmetic concern.
- The mocked-koffi world now uses a distinctive 4-byte pointer width and
rejects mis-sized out-buffers and mis-divided vtable offsets, so a
regression to hardcoded 8s fails the suite (the ia32 bug class).
- A keyless built-worker e2e guard loads lib/worker.cjs under plain
worker_threads on POSIX (the workflow-workerthread shape).
- The 'loaded lazily' module claims are reworded to attribute laziness to
the dynamic import('koffi') calls, and the discarded close-attempt
rejection is named at its catch.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* koffi-backed Win32 bindings for the folder dialog: the COM vtable calls
|
||||
* behind {@link Win32DialogBindings} plus the cross-thread window closer the
|
||||
* driver uses to service aborts. Loaded lazily and only on win32 (the dialog
|
||||
* worker and the driver's abort path), so non-Windows processes never load
|
||||
* koffi — the same containment as the repo's other `win32.ts` modules.
|
||||
* driver uses to service aborts. The module loads on every platform; koffi
|
||||
* itself is imported lazily inside each function, so non-Windows processes
|
||||
* never load it — the same containment as the repo's other `win32.ts`
|
||||
* modules.
|
||||
*
|
||||
* The COM surface used here (IModalWindow/IFileDialog/IFileOpenDialog and
|
||||
* IShellItem vtable order, the GUIDs, `FOS_*` and `SIGDN_FILESYSPATH`) is
|
||||
@@ -29,7 +30,14 @@ interface Koffi {
|
||||
const COINIT_APARTMENTTHREADED = 0x2
|
||||
const CLSCTX_INPROC_SERVER = 0x1
|
||||
const SIGDN_FILESYSPATH = 0x80058000 | 0
|
||||
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
|
||||
/**
|
||||
* Thread DPI awareness contexts, best first: per-monitor-v2 (Windows 10
|
||||
* 1703+), per-monitor (1607+), then system-aware. `SetThreadDpiAwarenessContext`
|
||||
* returns NULL for an unsupported context instead of throwing, so the caller
|
||||
* cascades to the best one the host accepts; DPI stays a cosmetic
|
||||
* best-effort — an unsupported host still gets the modern dialog.
|
||||
*/
|
||||
const DPI_AWARENESS_CONTEXTS = [-4, -3, -2]
|
||||
const WM_CLOSE = 0x10
|
||||
|
||||
/** IFileOpenDialog vtable slots (IUnknown 0-2, IModalWindow 3, IFileDialog 4+). */
|
||||
@@ -94,14 +102,22 @@ export async function loadWin32DialogBindings(): Promise<Win32DialogBindings> {
|
||||
|
||||
return {
|
||||
setThreadDpiAwareness: () => {
|
||||
let setContext: KoffiFunction
|
||||
try {
|
||||
const setThreadDpiAwarenessContext = user32.func('__stdcall', 'SetThreadDpiAwarenessContext', 'void *', ['intptr'])
|
||||
setThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
|
||||
setContext = user32.func('__stdcall', 'SetThreadDpiAwarenessContext', 'void *', ['intptr'])
|
||||
} catch {
|
||||
// SetThreadDpiAwarenessContext absent (Windows 10 pre-1703): the
|
||||
// dialog renders at system DPI; nothing else can fail here because
|
||||
// user32 itself loaded above.
|
||||
// Symbol absent (pre-1607 Windows): no per-thread DPI control exists.
|
||||
// Proceed anyway — the cost is a blurry dialog above 100 % scaling on
|
||||
// museum hosts, and the modern picker still beats dropping to the
|
||||
// legacy 5.1 tree over a cosmetic concern.
|
||||
return
|
||||
}
|
||||
for (const context of DPI_AWARENESS_CONTEXTS) {
|
||||
if (setContext(context) !== null) return
|
||||
}
|
||||
// Unreachable in practice (SYSTEM_AWARE is accepted wherever the symbol
|
||||
// exists); if a host ever refuses everything, the dialog still works —
|
||||
// just without a DPI opt-in.
|
||||
},
|
||||
coInitializeSta: () => coInitializeEx(null, COINIT_APARTMENTTHREADED) as number,
|
||||
coUninitialize: () => {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* Real-process half of the Win32 dialog driver: spawn the dialog worker
|
||||
* (source or built plane) and close a dialog thread's windows. Loaded lazily
|
||||
* and only on the win32 default path, so non-Windows processes never touch
|
||||
* worker or koffi machinery; the driver's logic is tested against fakes of
|
||||
* this surface instead.
|
||||
* (source or built plane) and close a dialog thread's windows. The module
|
||||
* itself loads everywhere (the import chain from native-picker.ts is
|
||||
* static); what stays win32-only is koffi, imported dynamically inside the
|
||||
* bindings' functions. The driver's logic is tested against fakes of this
|
||||
* surface instead.
|
||||
*/
|
||||
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
@@ -49,9 +49,12 @@ export interface Win32FolderDialog {
|
||||
/** The thread-level native surface the dialog sequencing runs against. */
|
||||
export interface Win32DialogBindings {
|
||||
/**
|
||||
* Best-effort per-monitor-v2 DPI opt-in for the calling thread. Absent
|
||||
* before Windows 10 1703; implementations swallow only that absence, so an
|
||||
* old host merely renders the dialog at system DPI.
|
||||
* Opt the calling thread into the best supported DPI awareness
|
||||
* (per-monitor-v2, then per-monitor, then system-aware), checking each
|
||||
* call's result. Best-effort on purpose: a host accepting none of them
|
||||
* (or lacking the API, pre-1607) still shows the modern dialog — possibly
|
||||
* blurry above 100 % scaling — because a cosmetic degradation must not
|
||||
* cost the tier.
|
||||
*/
|
||||
setThreadDpiAwareness(): void
|
||||
/**
|
||||
|
||||
@@ -83,7 +83,9 @@ export async function pickWin32Directory(
|
||||
|
||||
const postClose = (): void => {
|
||||
// Before `showing` there is no window to close; the budget below still
|
||||
// runs so a worker that never reports cannot dangle the pick.
|
||||
// runs so a worker that never reports cannot dangle the pick. A
|
||||
// rejected close attempt (EnumThreadWindows/PostMessageW refusing) is
|
||||
// discarded: the interval retries it and terminate is the backstop.
|
||||
if (dialogThreadId !== undefined) void closeWindows(dialogThreadId).catch(() => undefined)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user