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:
@@ -280,11 +280,7 @@ internal static class Program
|
||||
info.AlreadyForeground = Native.GetForegroundWindow() == hwnd;
|
||||
if (!info.AlreadyForeground)
|
||||
{
|
||||
Native.ShowWindowAsync(hwnd, 9); // SW_RESTORE
|
||||
// No ALT tap: this app and Genshin run at the same (elevated)
|
||||
// integrity level, so SetForegroundWindow succeeds on its own. An ALT
|
||||
// tap would toggle menu-mnemonic mode and swallow the next inputs.
|
||||
info.SetForegroundResult = Native.SetForegroundWindow(hwnd);
|
||||
info.SetForegroundResult = ForceForeground(hwnd);
|
||||
Thread.Sleep(140);
|
||||
}
|
||||
|
||||
@@ -294,6 +290,46 @@ internal static class Program
|
||||
return info;
|
||||
}
|
||||
|
||||
// Plain SetForegroundWindow from a background process is silently refused by
|
||||
// Windows' foreground lock. Inventory Kamera and other reliable automation
|
||||
// tools bypass it by attaching the calling thread's input queue to the target
|
||||
// (and current-foreground) window thread and clearing the lock timeout, so the
|
||||
// foreground change is honored. Without this the auto-scan aborts with
|
||||
// "Genshin konnte nicht in den Vordergrund geholt werden".
|
||||
private static bool ForceForeground(IntPtr hwnd)
|
||||
{
|
||||
var current = Native.GetCurrentThreadId();
|
||||
var target = Native.GetWindowThreadProcessId(hwnd, out _);
|
||||
var foregroundHwnd = Native.GetForegroundWindow();
|
||||
var foreground = foregroundHwnd != IntPtr.Zero ? Native.GetWindowThreadProcessId(foregroundHwnd, out _) : 0u;
|
||||
|
||||
var attachedTarget = false;
|
||||
var attachedForeground = false;
|
||||
uint oldTimeout = 0;
|
||||
var timeoutRead = false;
|
||||
try
|
||||
{
|
||||
if (target != 0 && target != current) attachedTarget = Native.AttachThreadInput(current, target, true);
|
||||
if (foreground != 0 && foreground != current && foreground != target)
|
||||
attachedForeground = Native.AttachThreadInput(current, foreground, true);
|
||||
|
||||
timeoutRead = Native.SystemParametersInfo(Native.SPI_GETFOREGROUNDLOCKTIMEOUT, 0, ref oldTimeout, 0);
|
||||
Native.SystemParametersInfo(Native.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, Native.SPIF_SENDCHANGE);
|
||||
|
||||
Native.ShowWindowAsync(hwnd, 9); // SW_RESTORE
|
||||
Native.BringWindowToTop(hwnd);
|
||||
var ok = Native.SetForegroundWindow(hwnd);
|
||||
return ok;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (timeoutRead)
|
||||
Native.SystemParametersInfo(Native.SPI_SETFOREGROUNDLOCKTIMEOUT, 0, new IntPtr((long)oldTimeout), Native.SPIF_SENDCHANGE);
|
||||
if (attachedForeground) Native.AttachThreadInput(current, foreground, false);
|
||||
if (attachedTarget) Native.AttachThreadInput(current, target, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr FindGenshinWindow()
|
||||
{
|
||||
if (_genshinHwnd != IntPtr.Zero && Native.IsWindow(_genshinHwnd)) return _genshinHwnd;
|
||||
@@ -394,6 +430,9 @@ internal static class Native
|
||||
public const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
|
||||
public const uint MOUSEEVENTF_LEFTUP = 0x0004;
|
||||
public const uint MOUSEEVENTF_WHEEL = 0x0800;
|
||||
public const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000;
|
||||
public const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001;
|
||||
public const uint SPIF_SENDCHANGE = 0x0002;
|
||||
public static readonly IntPtr DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = new(-4);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
@@ -455,6 +494,21 @@ internal static class Native
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool BringWindowToTop(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint GetCurrentThreadId();
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref uint pvParam, uint fWinIni);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr GetForegroundWindow();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user