Files
notify/Notify/Interop/WindowActivator.cs
chuan 5ce2c8a982 feat: Claude Code 原生 Windows 通知(C# / .NET 10 + Avalonia 12)
为 Claude Code 提供原生 Windows toast 通知:点击跳回原窗口、切回 Windows
Terminal 标签、跨虚拟桌面、调用方图标、非阻塞投递;NativeAOT 单文件分发。
2026-06-22 18:05:15 +08:00

73 lines
2.3 KiB
C#

using System;
namespace Notify.Interop;
/// <summary>
/// 把目标窗口拉回前台
///
/// Windows 限制后台进程抢焦点,这里用一套组合技绕过:ALT 键模拟 +
/// AttachThreadInput 把当前线程与前台/目标线程的输入队列挂接 +
/// SetWindowPos/BringWindowToTop/SwitchToThisWindow/SetForegroundWindow 多管齐下
/// </summary>
public static class WindowActivator
{
public static bool Activate(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero || !Win32.IsWindow(hwnd))
{
return false;
}
try
{
// 最小化的先还原
if (Win32.IsIconic(hwnd))
{
Win32.ShowWindow(hwnd, Win32.SW_RESTORE);
}
var foreground = Win32.GetForegroundWindow();
var curThread = Win32.GetCurrentThreadId();
var fgThread = Win32.GetWindowThreadProcessId(foreground, out _);
var targetThread = Win32.GetWindowThreadProcessId(hwnd, out _);
// 模拟一次 ALT 抬起,满足 Windows 的"防焦点抢占"前置条件
Win32.keybd_event(Win32.VK_MENU, 0, 0, IntPtr.Zero);
Win32.keybd_event(Win32.VK_MENU, 0, Win32.KEYEVENTF_KEYUP, IntPtr.Zero);
if (fgThread != curThread)
{
Win32.AttachThreadInput(curThread, fgThread, true);
}
if (targetThread != curThread && targetThread != fgThread)
{
Win32.AttachThreadInput(curThread, targetThread, true);
}
Win32.AllowSetForegroundWindow(Win32.ASFW_ANY);
Win32.SetWindowPos(hwnd, IntPtr.Zero, 0, 0, 0, 0, Win32.SWP_NOMOVE | Win32.SWP_NOSIZE | Win32.SWP_SHOWWINDOW);
Win32.BringWindowToTop(hwnd);
Win32.SwitchToThisWindow(hwnd, true);
Win32.SetForegroundWindow(hwnd);
Win32.ShowWindow(hwnd, Win32.SW_SHOW);
if (targetThread != curThread && targetThread != fgThread)
{
Win32.AttachThreadInput(curThread, targetThread, false);
}
if (fgThread != curThread)
{
Win32.AttachThreadInput(curThread, fgThread, false);
}
return Win32.GetForegroundWindow() == hwnd;
}
catch
{
return false;
}
}
}