How the Global Hotkey Registration and Handling System Works in Shadowsocks-Windows

Shadowsocks-Windows registers system-wide keyboard shortcuts by parsing configuration strings into Key/ModifierKeys objects, mapping them to callback delegates via reflection, and dispatching actions through the HotKeyManager singleton that captures input across all application windows.

The shadowsocks/shadowsocks-windows repository implements a comprehensive global hotkey subsystem that enables users to trigger proxy toggles, server switches, and UI actions from any window context. This system operates through a three-layer architecture that cleanly separates user configuration, OS-level registration, and command execution. Understanding this flow reveals how the application maintains low-level keyboard hooks without interfering with other software.

Architecture Overview

The global hotkey implementation spans three distinct layers, each handled by specific components in the codebase:

Layer Responsibility Core Component Source File
Configuration Stores textual hotkey definitions and startup flags HotkeyConfig class (strings like "Ctrl+Alt+S") Model/HotKeyConfig.cs
Registration Parses strings, converts to key codes, registers with OS HotKeys static class, HotKeyManager (GlobalHotKey NuGet) Controller/System/Hotkeys/Hotkeys.cs
Execution Holds concrete methods invoked when hotkeys fire HotkeyCallbacks singleton with private delegates Controller/System/Hotkeys/HotkeyCallbacks.cs

Configuration Storage

User-defined shortcuts reside in the HotkeyConfig class within Model/HotKeyConfig.cs. Each property maps to a specific action, such as SwitchSystemProxy or ShowLogs, storing the shortcut as a human-readable string (e.g., "Ctrl+Shift+L"). An empty string indicates no binding.

The configuration also includes the RegHotkeysAtStartup boolean flag. When enabled, the application attempts to register all configured shortcuts during initialization. Default values for hotkey properties are empty strings (lines 14–30 of HotKeyConfig.cs), meaning no system-wide keys are captured until the user explicitly configures them.

Registration Pipeline

Boot-Time Registration Flow

During application startup, HotkeyReg.RegAllHotkeys() (defined in Controller/HotkeyReg.cs) orchestrates the registration process. If RegHotkeysAtStartup is true, the method iterates over each configured hotkey property and invokes RegHotkeyFromString (lines 20–27).

This method performs three critical operations:

  1. Callback Lookup: It calls HotkeyCallbacks.GetCallback(callbackName) to locate the target method via reflection. This returns a HotKeyCallBackHandler delegate pointing to private methods in the HotkeyCallbacks singleton (lines 24–30 of HotkeyCallbacks.cs).

  2. String Parsing: The shortcut string is converted to system key codes via HotKeys.Str2HotKey. This method splits the string on the last "+" delimiter to separate modifiers (Ctrl, Alt, Shift) from the primary key, then uses .NET KeyConverter and ModifierKeysConverter to generate the final objects (lines 9–26 of Hotkeys.cs).

  3. OS Registration: HotKeys.RegHotkey first removes any existing registration for the same callback via UnregExistingHotkey, then calls the private Register method to bind the key combination to the Windows hotkey API (lines 40–44 and 39–50 of Hotkeys.cs).

If the OS reports that a key combination is already reserved by another application, RegHotkeyFromString returns false, triggering a message box alert: "Register hotkey failed".

Manual Registration Example

While the application handles this automatically, you can manually register a hotkey programmatically:

// Initialize the manager with the main controller instance
HotKeys.Init(controller);

// Parse the shortcut and retrieve the callback delegate
var hotKey = HotKeys.Str2HotKey("Ctrl+Alt+S");
var callback = HotkeyCallbacks.GetCallback("SwitchSystemProxyCallback") 
               as HotKeys.HotKeyCallBackHandler;

// Register with the OS
bool success = HotKeys.RegHotkey(hotKey, callback);

Runtime Event Handling

Global Input Capture

Once registered, the HotKeyManager from the GlobalHotKey NuGet package monitors system-wide keyboard input. When a user presses a registered combination, the manager raises the KeyPressed event. The static HotKeys.HotKeyManagerPressed handler receives this event, looks up the corresponding delegate in the internal _keymap dictionary, and synchronously invokes the callback (lines 32–38 of Hotkeys.cs).

Callback Resolution and Execution

The HotkeyCallbacks class serves as a singleton repository for all hotkey actions. It exposes private methods such as:

  • SwitchSystemProxyCallback(): Toggles the enabled state via _controller.ToggleEnable()
  • ShowLogsCallback(): Opens the logging window
  • ServerMoveUpCallback() / ServerMoveDownCallback(): Cycles through the server configuration list

These methods operate on the ShadowsocksController instance stored within the singleton, granting them full access to the application’s model and configuration state.

// Example callback implementation from HotkeyCallbacks.cs
private void SwitchSystemProxyCallback()
{
    bool enabled = _controller.GetCurrentConfiguration().enabled;
    _controller.ToggleEnable(!enabled);
}

Cleanup and Resource Management

On application shutdown, HotKeys.Destroy() removes the event subscription to HotKeyManagerPressed and disposes the underlying HotKeyManager instance. This ensures the Windows OS releases all registered global shortcuts, preventing "ghost" hotkeys that persist after the application closes (lines 26–30 of Hotkeys.cs).

To unregister a specific hotkey during runtime without shutting down:

var callback = HotkeyCallbacks.GetCallback("ShowLogsCallback") 
               as HotKeys.HotKeyCallBackHandler;
HotKeys.UnregExistingHotkey(callback);

Summary

  • Configuration: User shortcuts are stored as strings in HotkeyConfig within Model/HotKeyConfig.cs, controlled by the RegHotkeysAtStartup flag.
  • Parsing: The HotKeys.Str2HotKey method converts textual shortcuts like "Ctrl+Alt+S" into Key and ModifierKeys objects by splitting on the last "+" delimiter.
  • Registration: HotkeyReg.RegAllHotkeys() iterates through configuration properties, uses reflection to bind callbacks, and registers combinations with the OS via HotKeys.RegHotkey.
  • Dispatch: The HotKeyManager captures global keypresses and routes them through HotKeys.HotKeyManagerPressed, which invokes delegates stored in the _keymap dictionary.
  • Cleanup: Calling HotKeys.Destroy() on exit unregisters all hotkeys and disposes the manager to free OS resources.

Frequently Asked Questions

How does shadowsocks-windows parse hotkey strings into system key codes?

The HotKeys.Str2HotKey method in Controller/System/Hotkeys/Hotkeys.cs handles parsing by finding the last occurrence of "+" in the string. It treats everything before as modifiers (Ctrl, Alt, Shift) and everything after as the primary key. The method uses .NET's KeyConverter and ModifierKeysConverter classes to transform these substrings into the appropriate System.Windows.Input enumeration values.

What happens if a hotkey is already registered by another application?

During the registration loop in Controller/HotkeyReg.cs, the RegHotkeyFromString method returns a boolean status. If the underlying Windows API reports that the key combination is already in use, the method returns false, and RegAllHotkeys aggregates these failures to display a single error dialog stating "Register hotkey failed". The application continues starting up, but the conflicting hotkey remains inactive.

How can I add a custom hotkey action to the codebase?

First, add a new string property to HotkeyConfig.cs to store the shortcut. Then, implement the action method in HotkeyCallbacks.cs as a private void method. Finally, update HotkeyCallbacks.GetCallback to map a string name to your new method using reflection. The registration system will automatically pick up the new hotkey during the next startup if configured in the settings.

Where does the application store hotkey configurations between sessions?

The HotkeyConfig object is serialized as part of the main configuration file (typically gui-config.json in the application directory). When Program.MainController.GetCurrentConfiguration() loads, it deserializes this file into a configuration object containing the hotkey property, which persists all shortcut strings and the RegHotkeysAtStartup boolean.

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 →