Global Hotkey Registration and Handling with obs_hotkey Functions in OBS Studio
OBS Studio implements a centralized hotkey subsystem in libobs/obs-hotkey.c that enables global hotkey registration via obs_hotkey_register_frontend, persists bindings to JSON, and processes input through a dedicated 40 Hz polling thread.
The obsproject/obs-studio repository provides a robust global hotkey framework that allows both the core application and third-party plugins to register system-wide keyboard shortcuts. Understanding how to leverage the obs_hotkey API is essential for developers extending OBS Studio's functionality.
How the OBS Hotkey Subsystem Works
The architecture divides responsibilities into three distinct layers: registration, binding persistence, and runtime handling. The public API surface is declared in libobs/obs-hotkey.h, while the core implementation resides in libobs/obs-hotkey.c.
| Layer | Responsibility | Key API | Core Implementation |
|---|---|---|---|
| Registration | Create a hotkey object and associate it with a registerer (frontend, encoder, source, etc.) | obs_hotkey_register_frontend, obs_hotkey_register_source |
obs_hotkey_register_internal in libobs/obs-hotkey.c (lines 189-206) |
| Binding Persistence | Load and save key-combination bindings to OBS-style JSON | obs_hotkey_load_bindings, obs_hotkey_save |
Loading helpers in obs-hotkey.c (lines 88-100) |
| Runtime Handling | Detect key presses, match against registered bindings, and invoke callbacks | obs_hotkey_inject_event, obs_hotkey_thread |
Polling thread and dispatch helpers in obs-hotkey.c (lines 1555-1580) |
Registering Global Hotkeys with obs_hotkey_register_frontend
The Registration API
To expose a global shortcut to users, call obs_hotkey_register_frontend with a unique identifier, human-readable description, and callback function:
obs_hotkey_id id = obs_hotkey_register_frontend(
"OBSBasic.Screenshot", // internal identifier
"Take Screenshot", // description shown in UI
screenshot_cb, // callback invoked on press/release
nullptr); // user data passed to callback
The returned obs_hotkey_id serves as a handle for subsequent operations such as loading bindings or unregistering the hotkey.
Internal Implementation in obs-hotkey.c
Under the hood, obs_hotkey_register_frontend forwards to obs_hotkey_register_internal with the OBS_HOTKEY_REGISTERER_FRONTEND type. This function, located at lines 189-206 in libobs/obs-hotkey.c, allocates a new obs_hotkey_t structure, populates it with the provided metadata, and inserts it into the global hash table obs->hotkeys.hotkeys. If saved bindings exist in the current profile context, they are automatically loaded and associated with the new hotkey instance.
Persisting Hotkey Bindings to JSON
Loading Bindings with obs_hotkey_load_bindings
The UI widget OBSHotkeyWidget calls the atomic update helper to replace bindings safely:
obs_hotkey_load_bindings(id,
combinations.data(), combinations.size());
This function acquires the global hotkey lock, removes existing bindings via remove_bindings, creates new obs_hotkey_binding_t entries through create_binding, and emits a hotkey_bindings_changed signal. The implementation resides in libobs/obs-hotkey.c around lines 88-112.
Saving Configuration with obs_hotkey_save
Saving operates in reverse: obs_hotkey_save iterates over all bindings, builds a JSON array via save_bindings_helper, and returns the serialized data to the caller (lines 290-314 in obs-hotkey.c). The frontend then writes this JSON to the user’s profile configuration.
Runtime Handling and the Hotkey Thread
The 40Hz Polling Loop
OBS runs a dedicated hotkey thread that polls the physical keyboard state at approximately 40 Hz (25 ms intervals) and dispatches events to registered hotkeys:
void *obs_hotkey_thread(void *arg)
{
while (os_event_timedwait(obs->hotkeys.stop_event, 25) == ETIMEDOUT) {
if (!lock())
continue;
query_hotkeys(); // check current modifier state + pressed keys
unlock();
}
return NULL;
}
The query_hotkeys function builds a modifier mask (INTERACT_SHIFT_KEY, etc.), enumerates every binding via enum_bindings, and calls handle_binding. This handler decides whether to fire (press_released_binding) or release (release_pressed_binding) based on current key state and strict modifier settings (lines 1555-1580 in obs-hotkey.c).
Event Injection with obs_hotkey_inject_event
Plugins can inject synthetic hotkey events programmatically—useful for remote controllers or automation:
obs_hotkey_inject_event(combination, pressed);
This walks all bindings and forces them into the pressed state if the supplied combination matches (lines 969-987 in obs-hotkey.c).
Advanced Callback Routing
OBS can reroute all hotkey callbacks through a central router function, enabling scripts to intercept hotkeys globally:
obs_hotkey_set_callback_routing_func(router_cb, nullptr);
obs_hotkey_enable_callback_rerouting(true);
All callbacks now forward to router_cb instead of their original handlers. The router receives the hotkey ID and pressed state, allowing the caller to decide whether to forward to the original callback via obs_hotkey_trigger_routed_callback (lines 1262-1270 in obs-hotkey.c).
Complete Code Example: Registering a Screenshot Hotkey
The following pattern demonstrates registering a frontend hotkey that triggers an existing Qt action:
/*-------------------------------------------------------------
* 1. Define the callback that will be executed on press/release
*------------------------------------------------------------*/
static void screenshot_cb(void *data, obs_hotkey_id id,
obs_hotkey_t *hotkey, bool pressed)
{
if (!pressed) // act only on key-down
return;
/* Take a screenshot – same as the menu action */
QAction *act = (QAction *)data;
QMetaObject::invokeMethod(act, "trigger", Qt::QueuedConnection);
}
/*-------------------------------------------------------------
* 2. Register the hotkey during application start-up
*------------------------------------------------------------*/
void register_screenshot_hotkey(void)
{
QAction *screenshotAction = /* obtain the existing QAction */;
obs_hotkey_id hk = obs_hotkey_register_frontend(
"OBSBasic.Screenshot", // internal name
"Take Screenshot", // description shown in UI
screenshot_cb, // callback
screenshotAction); // user data (Qt action)
/* Optional: expose the hotkey ID to the UI so it can be edited */
hotkey_widget->SetId(hk);
}
This implementation creates a persistent global shortcut that appears in the OBS Settings → Hotkeys panel, stores its bindings in the user profile, and executes the screenshot action when triggered.
Key Source Files for Reference
| Path | Role |
|---|---|
libobs/obs-hotkey.h |
Public declarations (obs_hotkey_id, obs_key_combination_t, registration APIs). |
libobs/obs-hotkey.c |
Core implementation: registration, binding storage, hotkey thread, injection, routing. |
frontend/widgets/OBSBasic_Hotkeys.cpp |
Example of frontend hotkey registration for built-in actions (e.g., screenshot, transition). |
frontend/settings/OBSHotkeyWidget.cpp |
UI widget that lets the user view/edit bindings; uses obs_hotkey_load_bindings & obs_hotkey_update_atomic. |
frontend/settings/OBSHotkeyEdit.cpp |
Handles individual key-combination editing, duplicate detection, and UI feedback. |
Summary
- Registration: Use
obs_hotkey_register_frontend(or source/encoder variants) to create hotkeys stored in the globalobs->hotkeys.hotkeyshash table insidelibobs/obs-hotkey.c. - Persistence: Bindings are serialized to JSON via
obs_hotkey_saveand restored viaobs_hotkey_load_bindings, enabling configuration across OBS sessions. - Runtime: A dedicated thread (
obs_hotkey_thread) polls the keyboard at 40 Hz, invokingquery_hotkeysandhandle_bindingto match pressed keys against registered combinations. - Injection: Synthetic events can be triggered programmatically using
obs_hotkey_inject_eventfor automation or remote control scenarios. - Routing: Advanced use cases can intercept all hotkey traffic via
obs_hotkey_set_callback_routing_functo implement custom filtering or scripting layers.
Frequently Asked Questions
How do I register a global hotkey for a frontend action in OBS Studio?
Call obs_hotkey_register_frontend with a unique identifier, human-readable description, callback function, and optional user data. This function, defined in libobs/obs-hotkey.c, allocates an obs_hotkey_t structure and inserts it into the global registry, making the shortcut available in Settings → Hotkeys.
What is the difference between obs_hotkey_register_frontend and obs_hotkey_register_source?
obs_hotkey_register_frontend creates hotkeys associated with the main application UI (stored with OBS_HOTKEY_REGISTERER_FRONTEND), while obs_hotkey_register_source attaches hotkeys to specific OBS sources (scenes, audio inputs, etc.). Both ultimately call obs_hotkey_register_internal but tag the hotkey with different registerer types for organizational and cleanup purposes.
How are hotkey bindings saved and loaded across OBS Studio sessions?
Bindings persist through obs_hotkey_save, which serializes active combinations to JSON, and obs_hotkey_load_bindings, which restores them from the profile configuration. The UI layer in frontend/settings/OBSHotkeyWidget.cpp invokes these functions when the user modifies key combinations in the settings dialog, ensuring changes survive application restarts.
Can plugins inject synthetic hotkey events programmatically?
Yes, plugins can call obs_hotkey_inject_event to simulate key presses without physical keyboard input. This function iterates through all registered bindings and forces matching combinations into the pressed state, enabling automation, remote control integrations, or scripted testing of hotkey callbacks.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →