How YimMenuV2 HotkeySystem Registers and Handles Keyboard Shortcuts

YimMenuV2 binds every menu command to arbitrary key combinations through a centralized HotkeySystem that registers commands on startup, polls for key states in a dedicated script loop, and persists bindings via JSON serialization.

YimMenuV2 implements a flexible input layer through its HotkeySystem, allowing users to bind commands to complex key chains like Ctrl + F. According to the YimMenu/YimMenuV2 source code, the system is encapsulated in src/core/commands/HotkeySystem.{hpp,cpp} and operates through three distinct stages: registration, runtime listening, and persistence.

Command Registration at Startup

During initialization, main.cpp invokes g_HotkeySystem.RegisterCommands() to populate the internal command map. As implemented in src/core/commands/HotkeySystem.cpp (lines 21-33), this method iterates over the global registry returned by Commands::GetCommands() and instantiates a CommandLink structure for each entry. These links are stored in the m_CommandHotkeys map, keyed by command hash.

The system pre-binds the special "chathelper" command to the T key (virtual-key code 0x54) to ensure the chat overlay remains accessible regardless of user configuration.

Runtime Key State Listening

Once registered, the hotkey engine executes within its own script fiber added via ScriptMgr::AddScript(&HotkeySystem::RunScript). The RunScriptImpl() method, found in src/core/commands/HotkeySystem.cpp (lines 95-132), runs continuously and performs the following checks each tick:

  • Verifies the game window is foreground, not paused, and that the UI is not currently capturing keyboard input.
  • Confirms the system is not in edit mode (!m_BeingModified).
  • Iterates through each registered CommandLink and validates its m_Chain (a std::vector<int> of virtual-key codes) using GetAsyncKeyState.

Only when all keys in a chain report down-state and at least 100 ms have elapsed since the previous trigger does the system fetch the command via Commands::GetCommand(hash) and execute it. Chat commands specifically run on a separate fiber to prevent blocking the main loop.

Creating and Editing Hotkeys

When users open the hotkey editor UI (DrawHotkey.cpp), the system calls HotkeySystem::SetBeingModifed(true) to pause the listening loop and prevent accidental triggers during configuration.

The CreateHotkey() method (lines 71-92 in HotkeySystem.cpp) drives the capture process:

  1. Invokes ListenAndApply(pressed_key, chain), which scans all virtual keys from 0 to VK_OEM_CLEAR.
  2. Applies a blacklist filter (defaulting to key 0) to ignore unintended inputs.
  3. Uses an is_key_unique lambda to prevent duplicate keys within the same chain.
  4. Appends valid new keys to the m_Chain vector and marks the state dirty for persistence.

Once editing completes, SetBeingModifed(false) resumes normal listening.

Persistence Layer

Hotkey bindings survive game restarts through the IStateSerializer interface. In src/core/commands/HotkeySystem.cpp (lines 43-67), SaveStateImpl() serializes each non-empty m_Chain to a JSON object keyed by command hash, while LoadStateImpl() restores these bindings during startup. This ensures user configurations persist across sessions without manual file editing.

Implementation Example

The following snippet demonstrates the integration points in main.cpp and the UI layer:

// src/main.cpp - Initialization
g_HotkeySystem.RegisterCommands();
ScriptMgr::AddScript(std::make_unique<Script>(&HotkeySystem::RunScript));
// src/game/frontend/items/DrawHotkey.cpp - Editing mode
HotkeySystem::SetBeingModifed(true);        // Pause detection
g_HotkeySystem.CreateHotkey(link->m_Chain); // Capture user input
HotkeySystem::SetBeingModifed(false);       // Resume detection

Summary

  • Registration: RegisterCommands() populates m_CommandHotkeys with CommandLink entries for every available command, including the default chathelper binding.
  • Runtime Detection: RunScriptImpl() uses GetAsyncKeyState to validate complete key chains stored in m_Chain, enforcing a 100ms cooldown between triggers.
  • Edit Mode: SetBeingModifed() toggles a boolean flag that pauses key listening while users configure new bindings via CreateHotkey().
  • Persistence: The IStateSerializer implementation saves and loads key chains as JSON, keyed by command hash, ensuring bindings persist across game sessions.

Frequently Asked Questions

Where is the HotkeySystem implemented in YimMenuV2?

The core logic resides in src/core/commands/HotkeySystem.hpp and src/core/commands/HotkeySystem.cpp, with UI integration in src/game/frontend/items/DrawHotkey.cpp and initialization in src/main.cpp.

How does the system prevent hotkeys from firing while editing bindings?

When the hotkey editor opens, SetBeingModifed(true) sets the m_BeingModified flag to true, causing RunScriptImpl() to skip command execution until editing completes.

What is the cooldown period between hotkey triggers?

The system enforces a 100 millisecond debounce timer to prevent rapid-fire activation when holding a key combination.

How are multi-key combinations stored internally?

Key chains are stored as std::vector<int> in the m_Chain member of CommandLink, where each integer represents a Windows virtual-key code (e.g., 0x11 for Ctrl, 0x46 for F).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →