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

> Explore the global hotkey registration and handling system in Shadowsocks-Windows. Learn how it parses configs, maps keys to actions, and captures input system-wide.

- Repository: [shadowsocks/shadowsocks-windows](https://github.com/shadowsocks/shadowsocks-windows)
- Tags: internals
- Published: 2026-03-05

---

**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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Model/HotKeyConfig.cs) |
| **Registration** | Parses strings, converts to key codes, registers with OS | `HotKeys` static class, `HotKeyManager` (GlobalHotKey NuGet) | [`Controller/System/Hotkeys/Hotkeys.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Controller/System/Hotkeys/Hotkeys.cs) |
| **Execution** | Holds concrete methods invoked when hotkeys fire | `HotkeyCallbacks` singleton with private delegates | [`Controller/System/Hotkeys/HotkeyCallbacks.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Controller/System/Hotkeys/HotkeyCallbacks.cs) |

## Configuration Storage

User-defined shortcuts reside in the `HotkeyConfig` class within [`Model/HotKeyConfig.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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:

```csharp
// 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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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.

```csharp
// 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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Hotkeys.cs)).

To unregister a specific hotkey during runtime without shutting down:

```csharp
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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/HotkeyConfig.cs) to store the shortcut. Then, implement the action method in [`HotkeyCallbacks.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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.