Configuring Hyprland Keybinds and Input Events: A Complete Technical Guide
Hyprland’s input system splits configuration parsing and event handling between the KeybindManager, which stores and dispatches binds from hyprland.conf, and the InputManager, which translates raw libinput events into compositor actions through a coordinated pipeline in src/managers/KeybindManager.cpp and src/managers/input/InputManager.cpp.
Configuring Hyprland keybinds and input events requires understanding how the compositor bridges user configuration with low-level hardware input. This guide examines the actual source code implementation in the hyprwm/Hyprland repository, revealing how keyboard shortcuts, mouse bindings, and special input features are parsed, stored, and executed.
Architecture Overview
Hyprland delegates input responsibilities between two cooperating managers that handle distinct phases of the input lifecycle.
The Keybind Manager
The KeybindManager (src/managers/KeybindManager.cpp) serves as the configuration authority. It parses bind… entries from the user’s config file, stores them as SKeybind objects in m_keybinds, tracks modifier state across all keyboards, resolves binding conflicts, and dispatches Lua callbacks when matches occur.
The Input Manager
The InputManager (src/managers/input/InputManager.cpp) acts as the hardware interface. It receives raw libinput and Wayland events from keyboards, mice, touchscreens, and tablets, translates them into Hyprland-friendly structures, updates global modifier masks, and forwards processed events to the KeybindManager for action resolution.
How Keybinds Are Created
The journey from configuration text to executable bind follows a four-stage pipeline.
Configuration Parsing
When Hyprland loads, CConfigValue reads the user’s hyprland.conf and identifies lines beginning with bind.
// From ConfigValue.hpp
static auto P = CConfigValue<std::string>("bind");
Bind Registration
For each valid bind line, CKeybindManager::addKeybind constructs a SKeybind struct, wraps it in a std::shared_ptr, and appends it to the m_keybinds vector.
SP<SKeybind> CKeybindManager::addKeybind(SKeybind kb) {
const auto KEYBIND = makeShared<SKeybind>(kb);
m_keybinds.emplace_back(KEYBIND);
// ...
}
Modifier Mask Resolution
Textual modifiers like SUPER, CTRL, ALT, and SHIFT convert to bit-masks via CKeybindManager::stringToModMask. This mask is later compared against the current modifier state retrieved from g_pInputManager->getModsFromAllKBs().
uint32_t CKeybindManager::stringToModMask(std::string mods) {
// Returns XKB modifier mask
}
Key Symbol Resolution
Hyprland supports two key identification methods:
- Keysym binds: Names like
QorENTERresolve viaxkb_keysym_from_name - Keycode binds: Raw XKB keycodes prefixed with
code:(e.g.,code:30) store the numeric value directly
Keyboard Event Flow
When you press a key, the compositor processes it through a seven-stage pipeline before deciding whether to consume the event or forward it to the focused client.
-
Raw Event Reception:
InputManager::onKeyEventreceives anIKeyboard::SKeyEventfrom libinput. -
State Validation: The manager checks if the session is active and whether the keyboard has
allowBindsdisabled. It lazily initializes the XKB translation state viaupdateXKBTranslationState. -
Translation:
xkb_state_key_get_one_symconverts the hardware scancode to a keysym, whilegetModsFromAllKBs()aggregates modifier states across all connected keyboards. -
Internal Shortcuts:
handleInternalKeybindsintercepts critical system shortcuts like VT switching (CTRL+ALT+F1) before evaluating user binds. -
Bind Matching: The system calls
handleKeybinds(modmask, pressedKey, pressed, ...), which iterates overm_keybindschecking:- Mod mask equality (unless
ignoreModsis set) - Submap matching (
k->submap.name) - Device and tag inclusion (
k->devices) - Multi-key chord status (
mkBindMatches) - Release-only, long-press, and repeat flags
- Mod mask equality (unless
-
Dispatch: Upon match,
callBindDispatcher(k)invokes the bound action, typically throughConfig::Lua::mgr()->callLuaFnBind. This returns anSDispatchResultindicating whether to suppress the original event. -
Client Forwarding:
onKeyEventreturns!suppressEvent && !mouseBindWasActive, determining if the key event propagates to the Wayland client.
Mouse and Axis Handling
Pointer input follows a separate but related path through the InputManager.
Mouse Movement
CInputManager::onMouseMoved updates the cursor position, forwards relative motion via the relative-pointer protocol (PROTO::relativePointer->sendRelativeMotion), and calls mouseMoveUnified. The unified handler performs hit-testing across layer-shell surfaces and windows, updating pointer focus through g_pSeatManager->setPointerFocus.
Mouse Buttons
CInputManager::onMouseButton routes button events to g_pKeybindManager->onMouseEvent. If no bind consumes the click, the event forwards to the focused client via g_pSeatManager->sendPointerButton.
Scroll Wheel
CInputManager::onMouseWheel normalizes scroll deltas, applies the user-defined input:scroll_factor, synthesizes discrete steps when necessary, and dispatches axis events through g_pSeatManager->sendPointerAxis.
Special Bind Features
Hyprland extends basic keybinding with several advanced features implemented directly in the KeybindManager.
Long-Press Binds
When a key presses, m_longPressTimer arms with a configurable timeout. If the timer expires before release, callBindDispatcher triggers the stored m_lastLongPressKeybind.
Repeat Handling
After initial press detection, m_repeatKeyTimer starts with a period based on the keyboard’s repeatRate. Each tick re-invokes handleKeybinds for the active bind, enabling volume or brightness adjustments while holding keys.
Multi-Key Chords
For binds requiring simultaneous keys (binds: s), mkKeysymSetMatches constructs a bipartite-matching graph between currently pressed keys and the bind’s required set. The system distinguishes between MK_FULL_MATCH, partial matches, and no match to determine trigger eligibility.
Bind Shadowing
Once a bind fires, shadowKeybinds disables conflicting binds sharing the same modifier/key combination, preventing accidental double-triggers. Global and transparent handlers remain active during shadowing.
VT Switching
handleVT in KeybindManager.cpp intercepts CTRL+ALT+F[1-12] combinations (XKB codes XKB_KEY_XF86Switch_VT_...) and requests backend terminal switches before user binds are evaluated.
Configuration Syntax
Hyprland configuration resides at $XDG_CONFIG_HOME/hypr/hyprland.conf. A bind declaration follows this pattern:
bind = <MODIFIER>, <KEY>, <HANDLER>, <ARGUMENT>
- MODIFIER: Space or comma-separated list (
SUPER,CTRL,ALT,SHIFT,META,CAPS) - KEY: Keysym name (
Q,ENTER) or raw keycode (code:30) - HANDLER: Action type (
exec,workspace,submap,global,pass,mouse) - ARGUMENT: Handler-specific data (command, workspace ID, or Lua function reference)
Practical Examples
# Launch terminal
bind = SUPER, ENTER, exec, alacritty
# Switch to workspace 3
bind = SUPER, 3, workspace, 3
# Multi-key bind (requires both modifiers)
bind = SUPER+SHIFT, Q, exec, hyprctl dispatch killactive
# Long-press notification (hold Super for 800ms)
bind = SUPER, SUPER, longpress, exec, notify-send "Super held!"
# Repeat bind for volume control
bind = NONE, VOLUME_UP, repeat, exec, pactl set-sink-volume @DEFAULT_SINK@ +5%
Lua-Based Binds
Advanced configurations can bind directly to Lua functions:
# In hyprland.conf.lua
local myFn = function()
hyprland.notify("Hello from Lua!")
end
hyprland.registerLuaFunction("notifyFn", myFn)
# In hyprland.conf
bind = SUPER, H, exec, lua:notifyFn
The callBindDispatcher function resolves the lua: prefix and invokes Config::Lua::mgr()->callLuaFnBind with the stored function index.
Extending and Debugging Keybinds
Runtime Inspection
Retrieve current bind states via hyprctl:
# JSON output of all binds and devices
hyprctl devices -j
# Force configuration reload
hyprctl reload
Debug Logging
Enable verbose logging with debug:log_level = 2 to trace bind execution:
[DEBUG] Keybind triggered, calling dispatcher (modmask, keyName, keysym)
[DEBUG] Long press timeout passed, calling dispatcher.
Programmatic Registration
Plugins can register binds at runtime:
extern "C" void register_my_bind() {
SKeybind kb;
kb.modmask = CKeybindManager::stringToModMask("SUPER");
kb.key = "F";
kb.handler = "exec";
kb.arg = "notify-send \"Plugin active!\"";
kb.enabled = true;
g_pKeybindManager->addKeybind(kb);
}
Summary
- Input architecture: Hyprland separates concerns between
KeybindManager(configuration and dispatch) andInputManager(hardware abstraction) to handle configuring Hyprland keybinds and input events efficiently. - Event pipeline: Raw libinput events undergo translation (XKB), modifier aggregation, internal shortcut checking, bind matching against
m_keybinds, and potential suppression before reaching Wayland clients. - Advanced features: Long-press timers, repeat intervals, multi-key matching graphs, and shadowing logic extend basic keybinding capabilities.
- Configuration methods: Users define binds in
hyprland.confusing keysyms or keycodes, with optional Lua function integration vialua:prefixes. - Debugging tools: Use
hyprctl devices -jfor inspection,hyprctl reloadfor updates, anddebug:log_level = 2for detailed event tracing.
Frequently Asked Questions
How does Hyprland resolve conflicts when multiple keybinds match?
Hyprland evaluates binds in order using handleKeybinds, which checks mod masks, submaps, device constraints, and multi-key states. Once a bind triggers, the shadowKeybinds mechanism disables subsequent matches for the same key combination unless they are marked as global or transparent, ensuring deterministic behavior.
What is the difference between keycode and keysym binds in Hyprland?
Keysym binds (e.g., Q, ENTER) use xkb_keysym_from_name and resolve based on the current keyboard layout, making them layout-dependent. Keycode binds prefixed with code: (e.g., code:30) use raw XKB scancodes and remain consistent regardless of layout changes, ideal for gaming or layout-switching scenarios.
How can I debug why a keybind is not working?
Enable debug logging with debug:log_level = 2 to trace the event flow through onKeyEvent and handleKeybinds. Verify your bind appears in hyprctl devices -j output, check that the correct submap is active, and ensure no shadowed binds are consuming the input before your target bind is evaluated.
Can I use Lua to create custom keybind handlers?
Yes. Define functions in your Lua configuration using hyprland.registerLuaFunction, then reference them in binds with the lua: prefix (e.g., bind = SUPER, X, exec, lua:myFunction). When triggered, callBindDispatcher resolves the function index and executes it through Config::Lua::mgr()->callLuaFnBind, allowing complex conditional logic beyond standard dispatchers.
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 →