How to Debug Hyprland Window Rule Matching: A Complete Guide
Enable trace logging with HYPRLAND_LOG_LEVEL=trace and monitor the CWindowRuleApplicator logs to see exactly why a rule matches or skips during static and dynamic evaluation cycles.
Hyprland applies window-specific configuration through a sophisticated multi-stage rule system defined in the hyprwm/Hyprland source code. When a window rule fails to affect a target application, the issue typically lies in the selector matching logic, the distinction between static and dynamic evaluation, or the rule's enabled state. This guide traces the execution flow from config parsing in Config::windowRuleMgr() through the CWindowRuleApplicator engine to help you pinpoint why a rule is not taking effect.
Understanding the Window Rule Architecture
Before debugging, you must understand how Hyprland stores and applies rules. The system separates rule definitions from rule execution, using distinct classes for storage and application.
Rule Storage and the Window Rule Manager
All window rules are parsed from your configuration file and stored as CWindowRule objects inside Config::windowRuleMgr(). This manager maintains the master list of rules, but it does not execute them directly. Each rule carries metadata including an enabled flag, selector criteria, and effect definitions that determine when and how the rule triggers.
The Rule Applicator Lifecycle
Every CWindow receives a dedicated CWindowRuleApplicator instance (accessible via m_ruleApplicator) during its construction in src/desktop/view/Window.cpp (lines 108‑146). According to the Hyprland source code, this applicator acts as the evaluation engine, responsible for testing window attributes against stored rules and applying the resulting effects. The applicator persists for the window's lifetime, allowing rules to be re-evaluated when properties change.
Static vs. Dynamic Rule Evaluation
Hyprland evaluates window rules in two distinct phases. Misunderstanding this distinction is a common source of debugging frustration.
Static Rules and Initial Window Creation
Static rules are evaluated once, typically during window creation, and affect immutable properties such as initial workspace assignment, monitor placement, size, or opacity. The evaluation path resides in CWindowRuleApplicator::applyStaticRule() in src/desktop/rule/windowRule/WindowRuleApplicator.cpp (lines 383‑390). If a rule effect is marked as dynamic-only, the static evaluator skips it and emits a TRACE log entry.
Dynamic Rules and Runtime Events
Dynamic rules are evaluated repeatedly on relevant events such as focus changes, title updates, or workspace switches. The method CWindowRuleApplicator::applyDynamicRule() (lines 81‑92) handles this continuous evaluation. Effects like noanim or renderUnfocused require dynamic evaluation because they depend on transient window states rather than initial placement.
How Rule Matching Works Internally
Rule matching relies on selector engines that compare window attributes against rule criteria.
Selector Engines and Match Logic
Each rule contains a selector that tests strings (class, title, workspace name, etc.) using specific match engines. The source code implements several engines:
- Tag engine: Matches exact tags prefixed with
#, tested intests/desktop/rule/matchEngine/TagMatchEngine.cpp - Regex engine: Matches patterns prefixed with
~, tested intests/desktop/rule/matchEngine/RegexMatchEngine.cpp - Integer and Boolean engines: Handle numeric and true/false comparisons
The selector logic is invoked through CWindow::matchesStaticSelector(), which the CWindowPlacementController::ensurePersistentWorkspacesPresent method calls at line 54 of src/state/WorkspacePlacementController.cpp to resolve workspace assignments.
Where Matches Are Triggered
When a window spawns or updates, the applicator iterates through all stored rules in windowRuleMgr() and calls the appropriate selector. If the selector returns true, the applicator checks whether the effect is valid for the current evaluation context (static or dynamic) before applying it.
Enabling Debug Logging for Rule Matching
Hyprland's built-in logger (Log::logger defined in src/debug/log/Logger.cpp) provides the visibility needed to debug rule evaluation.
Trace Level Logging
Start Hyprland with the environment variable HYPRLAND_LOG_LEVEL=trace (alternatively HF_LOG_LEVEL=trace in some builds) to capture detailed TRACE messages from the rule applicator. At this level, the system logs every rule evaluation attempt, including skipped effects and selector mismatches.
Key Log Messages to Watch
Monitor the log output for specific patterns originating from src/desktop/rule/windowRule/WindowRuleApplicator.cpp:
- "Skipping effect … not dynamic": Emitted at line 92 when
applyDynamicRule()encounters an effect that cannot be applied dynamically, indicating a static/dynamic mismatch. - "BUG THIS: WINDOW_RULE_EFFECT_NONE??": An ERR level message at line 114 indicating an unhandled effect type in the rule definition.
- Selector resolution errors:
CWorkspacePlacementController::ensurePersistentWorkspacesPresentlogs errors at lines 66‑68 when a rule references a non-existent monitor or workspace that cannot be resolved.
Step-by-Step Debugging Workflow
Follow this systematic approach to identify why a window rule fails to apply:
-
Verify config parsing: Run
hyprctl reloadand check logs for "Parsing window rule" messages. If absent, the rule syntax is invalid or the config file is not loading. -
Enable trace logging: Launch Hyprland with
HYPRLAND_LOG_LEVEL=traceor set the variable before starting the compositor to captureCWindowRuleApplicatoractivity. -
Inspect window state: For a specific window ID, run
hyprctl getwindowproperty <winid> ruleApplicatorto view active flags such asrenderUnfocused,opaque, ordimAround. This confirms which rule properties are currently applied. -
Force rule re-evaluation: Execute
hyprctl reloador trigger a workspace change (e.g.,hyprctl dispatch exec "[workspace 1]") to invokeCWindowRuleApplicator::recheckStaticRules()at line 531, re-evaluating rules without restarting the session. -
Isolate selector issues: Create a temporary rule with a unique tag (e.g.,
windowrulev2 = float, tag:debugtest) and watch for TRACE logs indicating whether the selector matches. If no logs appear, the selector engine is rejecting the syntax. -
Check monitor resolution: Ensure rules referencing specific monitors use valid identifiers. The placement controller logs errors when
ensurePersistentWorkspacesPresentcannot resolve a monitor name.
Common Pitfalls and Misconfigurations
- Disabled rules: Each
CWindowRulehas anenabledflag; disabled rules are silently ignored during evaluation. - Missing persistent flag: Workspace rules require
m_isPersistentto be set; otherwise,ensurePersistentWorkspacesPresentskips them during placement resolution. - Incorrect selector syntax: Tags must use
#prefix, regex requires~, and exact matches use no prefix. Syntax errors cause immediate rejection by the match engine. - Static/dynamic effect mismatch: Applying dynamic-only effects (like
noanim) during static evaluation results in silent skipping with TRACE logs but no error.
Summary
- Rule storage: All rules exist as
CWindowRuleobjects inConfig::windowRuleMgr(). - Evaluation engine: Each window owns a
CWindowRuleApplicatorcreated insrc/desktop/view/Window.cppthat handles static and dynamic rule passes. - Debugging method: Use
HYPRLAND_LOG_LEVEL=traceto viewapplyStaticRule()andapplyDynamicRule()execution insrc/desktop/rule/windowRule/WindowRuleApplicator.cpp. - Selector testing: Verify tag (
#), regex (~), and other selectors against the unit tests intests/desktop/rule/matchEngine/. - Common fixes: Check the
enabledflag, ensure persistent workspace flags are set, and confirm monitor names resolve inWorkspacePlacementController.
Frequently Asked Questions
How do I know if my window rule is being parsed correctly?
Run hyprctl reload and check the Hyprland logs for entries containing "Parsing window rule". If the rule appears in the logs with its selector and effects listed, the config parser accepted it. Absence of this message indicates a syntax error preventing the rule from reaching windowRuleMgr().
Why does my rule work for some windows but not others?
This usually indicates a selector mismatch or a static/dynamic phase error. Check whether the target window's class or title matches your selector exactly using hyprctl clients. If the selector uses regex, verify the pattern in tests/desktop/rule/matchEngine/RegexMatchEngine.cpp. Also confirm the effect is valid for the evaluation phase—some effects only work during dynamic evaluation and are skipped during static application.
How can I force Hyprland to re-evaluate rules without restarting?
Execute hyprctl reload to trigger a full config reload, or change the window's workspace to invoke CWindowRuleApplicator::recheckStaticRules() at line 531 of WindowRuleApplicator.cpp. This method re-evaluates static rules against the current window state without requiring a compositor restart.
What does "BUG THIS: WINDOW_RULE_EFFECT_NONE??" mean in the logs?
This ERR level message appears in src/desktop/rule/windowRule/WindowRuleApplicator.cpp at line 114 when the rule applicator encounters an effect type it does not recognize. This indicates either a corrupted rule object in memory or a rule referencing an effect that exists in the config parser but lacks implementation in the applicator engine. Check your config for typos in effect names and ensure you are running a Hyprland version where the effect is implemented.
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 →