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 Q or ENTER resolve via xkb_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.

  1. Raw Event Reception: InputManager::onKeyEvent receives an IKeyboard::SKeyEvent from libinput.

  2. State Validation: The manager checks if the session is active and whether the keyboard has allowBinds disabled. It lazily initializes the XKB translation state via updateXKBTranslationState.

  3. Translation: xkb_state_key_get_one_sym converts the hardware scancode to a keysym, while getModsFromAllKBs() aggregates modifier states across all connected keyboards.

  4. Internal Shortcuts: handleInternalKeybinds intercepts critical system shortcuts like VT switching (CTRL+ALT+F1) before evaluating user binds.

  5. Bind Matching: The system calls handleKeybinds(modmask, pressedKey, pressed, ...), which iterates over m_keybinds checking:

    • Mod mask equality (unless ignoreMods is set)
    • Submap matching (k->submap.name)
    • Device and tag inclusion (k->devices)
    • Multi-key chord status (mkBindMatches)
    • Release-only, long-press, and repeat flags
  6. Dispatch: Upon match, callBindDispatcher(k) invokes the bound action, typically through Config::Lua::mgr()->callLuaFnBind. This returns an SDispatchResult indicating whether to suppress the original event.

  7. Client Forwarding: onKeyEvent returns !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) and InputManager (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.conf using keysyms or keycodes, with optional Lua function integration via lua: prefixes.
  • Debugging tools: Use hyprctl devices -j for inspection, hyprctl reload for updates, and debug:log_level = 2 for 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:

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 →