Files
Shigure/Input/KeySender.cs

151 lines
4.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace Shigure;
public sealed class KeySender : IRuntimeKeyOutput
{
private readonly WowProcessLocator _processLocator;
internal KeySender(WowProcessLocator processLocator)
{
_processLocator = processLocator;
}
public string? LastFailureReason { get; private set; }
public bool Send(string hotkey)
{
var result = SendCore(hotkey, expectedWindow: 0);
LastFailureReason = result.FailureReason;
return result.Succeeded;
}
public static int? GetVk(string keyName) => WindowsVirtualKeyMap.Resolve(keyName);
KeySendResult IRuntimeKeyOutput.Send(string hotkey, nint expectedWindow)
=> SendCore(hotkey, expectedWindow);
private KeySendResult SendCore(string hotkey, nint expectedWindow)
{
var (mods, mainKey) = ParseHotkey(hotkey);
if (mainKey is null)
{
return Fail($"无法解析按键“{hotkey}”");
}
var vkMain = WindowsVirtualKeyMap.Resolve(mainKey);
if (vkMain is null)
{
return Fail($"无法识别主键“{mainKey}”");
}
var hwnd = _processLocator.FindFrontmostWindow();
if (hwnd == 0)
{
return Fail($"未找到目标进程的可见窗口wow_process.txt: {_processLocator.DescribeConfiguredProcesses()}");
}
if (expectedWindow != 0 && hwnd != expectedWindow)
{
return Fail("目标窗口已切换,等待重新扫描后再发送按键");
}
// ParseHotkey 只产出去重后的 CTRL/ALT/SHIFT, 三者都在虚拟键表里且映射到互异 VK,
// 故 Resolve 不会为 null、结果天然去重。
var modVks = mods.Select(m => WindowsVirtualKeyMap.Resolve(m)!.Value).ToList();
var succeeded = true;
var firstError = 0;
void SendMessage(int vk, bool keyUp)
{
if (!Post(hwnd, vk, keyUp, out var error))
{
succeeded = false;
if (firstError == 0)
{
firstError = error;
}
}
}
foreach (var vk in modVks)
{
SendMessage(vk, keyUp: false);
}
SendMessage(vkMain.Value, keyUp: false);
SendMessage(vkMain.Value, keyUp: true);
for (var i = modVks.Count - 1; i >= 0; i--)
{
SendMessage(modVks[i], keyUp: true);
}
if (succeeded)
{
return KeySendResult.Success;
}
return Fail(firstError == 5
? "权限不足Win32 错误码 5请确认 Shigure 与魔兽世界使用相同的管理员权限运行"
: $"向目标窗口发送按键失败Win32 错误码: {firstError}");
}
private static (List<string> Mods, string? MainKey) ParseHotkey(string hotkey)
{
if (string.IsNullOrWhiteSpace(hotkey))
{
return (new List<string>(), null);
}
var rawParts = hotkey.Trim().Split('-', StringSplitOptions.RemoveEmptyEntries);
if (rawParts.Length == 0)
{
return (new List<string>(), null);
}
var mainKey = rawParts[^1];
var mods = new List<string>();
foreach (var raw in rawParts[..^1])
{
var part = raw.Trim().ToUpperInvariant();
part = part switch
{
"CONTROL" => "CTRL",
"MENU" => "ALT",
_ => part
};
if (part is "CTRL" or "ALT" or "SHIFT" && !mods.Contains(part))
{
mods.Add(part);
}
}
return (mods, mainKey);
}
private static KeySendResult Fail(string reason) => KeySendResult.Failure(reason);
private static bool Post(nint hwnd, int keyCode, bool keyUp, out int error)
{
var scanCode = NativeMethods.MapVirtualKeyW((uint)keyCode, 0) & 0xFF;
var value = 1u | (scanCode << 16);
if (keyCode == 0x6F) // VK_DIVIDE 是扩展键。
{
value |= 1u << 24;
}
if (keyUp)
{
value |= (1u << 30) | (1u << 31);
}
var posted = NativeMethods.PostMessageW(
hwnd,
keyUp ? NativeMethods.WmKeyUp : NativeMethods.WmKeyDown,
(nint)keyCode,
unchecked((nint)(int)value));
error = posted ? 0 : System.Runtime.InteropServices.Marshal.GetLastWin32Error();
return posted;
}
}