using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Decal.Interop.Core; using Decal.Interop.Input; namespace Decal.DecalInput { [ComVisible(true)] [Guid("B33307BA-706D-474A-80B9-70BB8D13EF3E")] [ClassInterface(ClassInterfaceType.None)] [ProgId("DecalInput.InputService")] public class InputServiceImpl : IInputService, IDecalService { internal static InputServiceImpl Instance { get; private set; } private IDecalCore _decalCore; private readonly List _hotkeys = new List(); private readonly List _timers = new List(); private readonly List _msgHooks = new List(); private readonly Dictionary _commands = new Dictionary(StringComparer.OrdinalIgnoreCase); private WndMsgImpl _message; internal InputBufferImpl ActiveBuffer { get; set; } public DecalCore Decal => (DecalCore)_decalCore; // Parameterized property - KeyByName takes a string parameter via IDispatch public int KeyByName => 0; internal int GetKeyByName(string name) { return KeyNames.VKFromName(name); } // IDecalService public void Initialize(DecalCore pDecal) { _decalCore = (IDecalCore)pDecal; Instance = this; _message = new WndMsgImpl(); } public void BeforePlugins() { // In native code: subclasses game window and loads keymap // Window hooking requires being in-process, handled by Inject.DLL } public void AfterPlugins() { _commands.Clear(); // Native code restores original window procedure here } public void Terminate() { _hotkeys.Clear(); _timers.Clear(); _msgHooks.Clear(); _commands.Clear(); _message = null; ActiveBuffer = null; if (Instance == this) Instance = null; } // Registration methods for child objects internal void RegisterHotkey(HotkeyImpl hotkey) { if (!_hotkeys.Contains(hotkey)) _hotkeys.Add(hotkey); } internal void UnregisterHotkey(HotkeyImpl hotkey) { _hotkeys.Remove(hotkey); } internal void RegisterTimer(TimerImpl timer) { if (!_timers.Contains(timer)) _timers.Add(timer); } internal void UnregisterTimer(TimerImpl timer) { _timers.Remove(timer); } internal void RegisterMsgHook(WinMsgHookImpl hook) { if (!_msgHooks.Contains(hook)) _msgHooks.Add(hook); } internal void UnregisterMsgHook(WinMsgHookImpl hook) { _msgHooks.Remove(hook); } /// /// Called from the window hook procedure for each window message. /// Returns true if the message should be eaten (suppressed). /// internal bool ProcessWindowMessage(int hwnd, int msg, int wParam, int lParam) { _message.Set(hwnd, msg, wParam, lParam); // Process message hooks foreach (var hook in _msgHooks) { hook.FireMessage(hook, _message); if (_message.Eat) return true; } // Process hotkeys on WM_KEYUP (0x0101), only if no active buffer if (msg == 0x0101 && ActiveBuffer == null) { foreach (var hotkey in _hotkeys) { if (hotkey.Enabled && hotkey.Key.Length > 0) { // Match virtual key int vk = wParam; string keyName = hotkey.Key; int hotkeyVk; if (keyName.StartsWith("{") && keyName.EndsWith("}")) { string name = keyName.Substring(1, keyName.Length - 2); hotkeyVk = KeyNames.VKFromName(name); } else { hotkeyVk = char.ToUpper(keyName[0]); } if (vk == hotkeyVk) { hotkey.FireHotkey(); break; // Only one hotkey per key press } } } } return false; } /// /// Called from the render loop to process timers and input buffer delays. /// internal void ProcessTimers() { uint now = GetTickCount(); // Copy list to avoid modification during iteration var timersCopy = new List(_timers); foreach (var timer in timersCopy) { timer.CheckTimeout(now); } } [DllImport("kernel32.dll")] private static extern uint GetTickCount(); } }