using System.Runtime.InteropServices; namespace DNFLoadout; internal sealed class HotkeyManager : IDisposable { private const int AltBit = 0x1; private const int CtrlBit = 0x2; private const int ShiftBit = 0x4; private readonly System.Windows.Forms.Timer _timer = new() { Interval = 50 }; private readonly HashSet _down = new(); private Config _config; public event Action? Triggered; public HotkeyManager(Config config) { _config = config; _timer.Tick += (_, _) => Poll(); _timer.Start(); } public void UpdateConfig(Config config) => _config = config; private static bool Down(int vk) => (GetAsyncKeyState(vk) & 0x8000) != 0; private static bool ModCtrl => Down((int)Keys.ControlKey); private static bool ModAlt => Down((int)Keys.Menu); private static bool ModShift => Down((int)Keys.ShiftKey); private void Poll() { foreach (var scheme in _config.CurrentPage.Schemes) { if (scheme.KeyCode == 0) continue; bool mods = ((scheme.Modifiers & CtrlBit) == 0 || ModCtrl) && ((scheme.Modifiers & AltBit) == 0 || ModAlt) && ((scheme.Modifiers & ShiftBit) == 0 || ModShift); // Reject extra modifiers so Ctrl+F1 cannot fire plain F1. if ((scheme.Modifiers & CtrlBit) == 0 && ModCtrl) mods = false; if ((scheme.Modifiers & AltBit) == 0 && ModAlt) mods = false; if ((scheme.Modifiers & ShiftBit) == 0 && ModShift) mods = false; bool key = Down(scheme.KeyCode); int id = scheme.KeyCode | (scheme.Modifiers << 16); bool was = _down.Contains(id); if (key && mods && !was) { _down.Add(id); Triggered?.Invoke(scheme); } else if (!key || !mods) { _down.Remove(id); } } } public static (int Modifiers, int KeyCode) Capture(Keys keyData) { var modifiers = 0; if ((keyData & Keys.Control) != 0) modifiers |= CtrlBit; if ((keyData & Keys.Alt) != 0) modifiers |= AltBit; if ((keyData & Keys.Shift) != 0) modifiers |= ShiftBit; var keyCode = (int)(keyData & Keys.KeyCode); if (keyCode is (int)Keys.ControlKey or (int)Keys.Menu or (int)Keys.ShiftKey or (int)Keys.LControlKey or (int)Keys.RControlKey or (int)Keys.LMenu or (int)Keys.RMenu or (int)Keys.LShiftKey or (int)Keys.RShiftKey) return (modifiers, 0); return (modifiers, keyCode); } public void Dispose() { _timer.Stop(); _timer.Dispose(); } [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); }