Autofocus biometric: activate app before Touch ID and fix timing
Details
The Touch ID system dialog wasn't getting foreground focus because:
1. Biometric was triggered in OnDataContextChanged (before the window
was even visible/activated)
2. The app wasn't explicitly activated before calling LAContext
Fix: Call [NSApp activateIgnoringOtherApps:YES] via ObjC runtime before
evaluatePolicy (DEBUG) and SecItemCopyMatching (RELEASE) to ensure the
app is the active application when Touch ID triggers. This is what
Electron-based apps like VS Code do to get the system biometric dialog
to appear with proper foreground focus.
Also moved biometric init from UnlockView.OnDataContextChanged to
UnlockWindow.OnOpened (after Activate()), ensuring the window is fully
shown and activated before triggering the biometric prompt.
When biometric is available, focus the biometric button instead of the
password box in both UnlockView and SensitiveUnlockOverlay, so users
can press Enter/Space to retry biometric without clicking.
Gate FFI eprintln! logs by PRIVSTACK_LOG_LEVEL
Details
Add FFI_LOG_LEVEL atomic + ffi_error!/ffi_warn!/ffi_info!/ffi_debug!
macros that check the level before printing. privstack_init() reads
PRIVSTACK_LOG_LEVEL env var (error/warn/info/debug) and configures both
the FFI macros and the tracing subscriber filter.
Replaced all 111 raw eprintln! calls across 9 FFI source files with
level-appropriate macros:
- ffi_debug: DB opening, peer ID loading, store init, sync lifecycle
- ffi_info: cloud sync summaries, major lifecycle events
- ffi_warn: corrupt files, poison recovery, non-critical failures
- ffi_error: actual operation failures, panics
android_log() on non-Android platforms now gates output by mapping
the level string to FFI_LOG_LEVEL before printing.
Running with --warn now silences INFO/DEBUG output from both the C#
Serilog layer and the Rust FFI layer.
Autofocus biometric button when biometric unlock is enabled
Details
When biometric is enabled, focus the biometric button instead of the
password box on both the main unlock view and the sensitive data overlay.
This lets users press Enter/Space to trigger biometric without having
to click the button manually. Falls back to password box focus when
biometric is not available. Uses PropertyChanged to handle the async
biometric availability check — switches focus to biometric button
as soon as availability is confirmed.
Fix ObjC block bridging crash in Touch ID evaluation
Details
The block struct had Flags = 1 << 25 (BLOCK_HAS_COPY_DISPOSE) but the
descriptor only contained Reserved and Size — no copy/dispose function
pointers. When the ObjC runtime copied the block for async dispatch, it
read garbage memory as the copy helper, corrupting the block and causing
a NullReferenceException when the callback tried to read the Context
GCHandle.
Fixed by:
- Using _NSConcreteGlobalBlock with BLOCK_IS_GLOBAL flag (no copy/dispose)
- Pinning the invoker delegate as a static field (prevents GC collection)
- Making the invoker null-safe with try/catch for native callback safety
- Returning a cleanup Action from EvaluatePolicy so the caller frees
resources after gate.Wait() instead of a racy 2-second Task.Delay
Fix biometric validation overlay to use verify-only check
Details
The validation overlay was calling AuthenticateAsync (Touch ID + keychain
read + password validation) when it only needs to verify Touch ID works.
Since the app is already unlocked at this point, there's no need to
retrieve the password from the keychain.
Added VerifyBiometricAsync to IBiometricService — prompts biometric only
with no keychain access. Moved EvaluateBiometricPolicyAsync out of the
DEBUG preprocessor block into the shared section so it's available in
both build configurations. Added logging to trace the verification flow.
Get notified about new releases