- 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.
133 lines
4.8 KiB
TypeScript
133 lines
4.8 KiB
TypeScript
/**
|
|
* Pure sequencing of the Win32 `IFileOpenDialog` folder-picker COM
|
|
* conversation over an injectable bindings seam, so every outcome path
|
|
* (selection, cancellation, HRESULT failure, cleanup ordering) is testable on
|
|
* any platform. The koffi-backed bindings live in
|
|
* `win32-dialog-bindings.ts`, which only a real win32 process ever loads.
|
|
*/
|
|
|
|
/** `HRESULT_FROM_WIN32(ERROR_CANCELLED)`: the user dismissed the dialog. */
|
|
export const HRESULT_CANCELLED = 0x800704c7 | 0
|
|
|
|
/** `FOS_PICKFOLDERS`: the dialog selects directories, not files. */
|
|
export const FOS_PICKFOLDERS = 0x20
|
|
/** `FOS_FORCEFILESYSTEM`: only results with a filesystem path can be chosen. */
|
|
export const FOS_FORCEFILESYSTEM = 0x40
|
|
/** `FOS_NOCHANGEDIR`: never mutate the process working directory. */
|
|
export const FOS_NOCHANGEDIR = 0x8
|
|
|
|
/** One created folder dialog: the vtable calls the sequencing needs. */
|
|
export interface Win32FolderDialog {
|
|
/**
|
|
* `IFileDialog::SetOptions`.
|
|
* @param options - the `FOS_*` flag union to apply.
|
|
* @returns the call's HRESULT.
|
|
*/
|
|
setOptions(options: number): number
|
|
/**
|
|
* `IFileDialog::SetTitle`.
|
|
* @param title - the dialog title text.
|
|
* @returns the call's HRESULT.
|
|
*/
|
|
setTitle(title: string): number
|
|
/**
|
|
* `IModalWindow::Show` with no owner window; blocks the calling thread
|
|
* until the user selects or dismisses.
|
|
* @returns the call's HRESULT (`HRESULT_CANCELLED` on dismissal).
|
|
*/
|
|
show(): number
|
|
/**
|
|
* `IFileDialog::GetResult` + `IShellItem::GetDisplayName(SIGDN_FILESYSPATH)`,
|
|
* releasing the shell item and freeing the COM string.
|
|
* @returns the call chain's HRESULT and, on success, the selected path.
|
|
*/
|
|
resultPath(): { hr: number; path?: string }
|
|
/** Release the dialog's COM reference. */
|
|
release(): void
|
|
}
|
|
|
|
/** The thread-level native surface the dialog sequencing runs against. */
|
|
export interface Win32DialogBindings {
|
|
/**
|
|
* 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
|
|
/**
|
|
* `CoInitializeEx(COINIT_APARTMENTTHREADED)` on the calling thread.
|
|
* @returns the call's HRESULT (`S_FALSE` re-entry is still a success).
|
|
*/
|
|
coInitializeSta(): number
|
|
/**
|
|
* `CoUninitialize` on the calling thread — COM requires one pairing call
|
|
* for every successful (including `S_FALSE`) `CoInitializeEx`, even on a
|
|
* thread that exits right after the conversation.
|
|
*/
|
|
coUninitialize(): void
|
|
/**
|
|
* `CoCreateInstance(CLSID_FileOpenDialog)`.
|
|
* @returns the created dialog surface; throws when creation fails.
|
|
*/
|
|
createFolderDialog(): Win32FolderDialog
|
|
/**
|
|
* `GetCurrentThreadId` — the native id a driver needs to close this
|
|
* thread's windows from outside.
|
|
* @returns the calling thread's native id.
|
|
*/
|
|
currentThreadId(): number
|
|
}
|
|
|
|
/**
|
|
* Throw when an HRESULT signals failure.
|
|
* @param hr - the HRESULT to check.
|
|
* @param what - the failing call's name for the error message.
|
|
* @returns the (successful) HRESULT unchanged.
|
|
*/
|
|
function check(hr: number, what: string): number {
|
|
if (hr < 0) throw new Error(`${what} failed: HRESULT 0x${(hr >>> 0).toString(16)}`)
|
|
return hr
|
|
}
|
|
|
|
/**
|
|
* Run one modal folder-picker conversation on the calling thread: DPI opt-in,
|
|
* STA init, dialog creation, `Show`, and result extraction, releasing the
|
|
* dialog on every path.
|
|
* @param bindings - the native surface (koffi-backed in production, fakes in tests).
|
|
* @param title - the dialog title text.
|
|
* @param onShowing - called with the native thread id immediately before the
|
|
* blocking `Show`, so a driver on another thread can close the dialog.
|
|
* @returns the selected filesystem path, or null when the user cancels.
|
|
*/
|
|
export function runFolderDialog(
|
|
bindings: Win32DialogBindings,
|
|
title: string,
|
|
onShowing: (threadId: number) => void,
|
|
): string | null {
|
|
bindings.setThreadDpiAwareness()
|
|
check(bindings.coInitializeSta(), 'CoInitializeEx')
|
|
// From here the apartment is initialized (S_OK or S_FALSE) and must be
|
|
// uninitialized exactly once on every path.
|
|
try {
|
|
const dialog = bindings.createFolderDialog()
|
|
try {
|
|
check(dialog.setOptions(FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_NOCHANGEDIR), 'SetOptions')
|
|
check(dialog.setTitle(title), 'SetTitle')
|
|
onShowing(bindings.currentThreadId())
|
|
const shown = dialog.show()
|
|
if (shown === HRESULT_CANCELLED) return null
|
|
check(shown, 'Show')
|
|
const result = dialog.resultPath()
|
|
check(result.hr, 'GetResult')
|
|
return result.path as string
|
|
} finally {
|
|
dialog.release()
|
|
}
|
|
} finally {
|
|
bindings.coUninitialize()
|
|
}
|
|
}
|