fix(input): force Genshin foreground via AttachThreadInput (auto-scan no longer aborts)

Auto-scan aborted immediately with "Genshin konnte nicht in den Vordergrund
geholt werden". Root cause: the focus call runs in the background input/capture
helper process, and Windows' foreground lock silently refuses SetForegroundWindow
from a process that is neither foreground nor the last input source. When the user
clicks "Auto-Scan starten" the Electron window is foreground, so the helper's plain
SetForegroundWindow is dropped and focus stays false.

Fix (both the C# sidecar and the PowerShell fallback): before SetForegroundWindow,
attach our thread's input queue to the target (and current-foreground) window
thread with AttachThreadInput and clear SPI_..FOREGROUNDLOCKTIMEOUT, then restore.
This is the same technique Inventory Kamera and other reliable automators use; it
is what our helper was missing after the old ALT-tap workaround was removed on the
wrong assumption that equal integrity level is sufficient (that only covers UIPI
input injection, not foreground changes).

Sidecar recompiled + republished; electron build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AzuTear
2026-07-06 16:07:30 +02:00
parent 113601f031
commit c8ae0dd7bf
11 changed files with 255 additions and 27 deletions
+10 -2
View File
@@ -98,6 +98,8 @@ export async function runAutoScanLoop(
let aborted = false;
let consecutiveMisses = 0;
let rowsQueued = 0;
const primaryScreenStartWarning =
"Start-Capture ist vom Primary-Screen, kein spezifischer Genshin-Client-Marker vorhanden - Auto-Scan wird mit Vorsicht fortgesetzt.";
function updateStats() {
setAutoScanStats({ ...stats });
@@ -153,7 +155,8 @@ export async function runAutoScanLoop(
}
let currentCapture = await captureSelectedSource(0, true);
const initialCaptureRejection = captureSourceRejectionReason(currentCapture);
const isPrimaryCapture = currentCapture?.captureTarget === "primary-screen";
const initialCaptureRejection = isPrimaryCapture ? "" : captureSourceRejectionReason(currentCapture);
let gridModel = buildGridModel(currentCapture?.inventoryGrid);
if (initialCaptureRejection || !gridModel || gridModel.targets.length === 0) {
@@ -162,6 +165,10 @@ export async function runAutoScanLoop(
return { status: "blocked", stats, blockedReason: reason, pageCount: 0, gridLabel: reason, targetCount: maxTargets };
}
if (isPrimaryCapture) {
appendAutomationLog(primaryScreenStartWarning);
}
let lastDetailSignature = "";
const initialParsed = parseArtifact(currentCapture);
if (initialParsed) lastDetailSignature = sessionSignature(initialParsed);
@@ -496,5 +503,6 @@ export function fingerprintDataUrl(dataUrl: string) {
}
function wait(ms: number): Promise<void> {
return new Promise((resolve) => window.setTimeout(() => resolve(), ms));
const schedule = typeof window !== "undefined" && window.setTimeout ? window.setTimeout : setTimeout;
return new Promise((resolve) => schedule(() => resolve(), ms));
}