Reviving a Dead Plugin: Porting Hycov to Hyprland v0.53
When member function pointers corrupt your mouse input and GCC 15 breaks your build - lessons from updating a 2-year-old Hyprland plugin.
The Problem
I wanted an overview mode for Hyprland - a grid of all open windows, macOS Mission Control style. The plugin hycov does exactly this. One problem: it hadn't been updated in two years, and Hyprland's plugin API moves fast.
The last supported version was somewhere around v0.30. I'm on v0.53. The source wouldn't even compile.
Time to dig in.
GCC 15 Broke Everything
The first wall: GCC 15 introduced stricter template instantiation rules. The logging macros died immediately:
error: no matching function for call to 'format<...>'Hyprland's internal Debug::log() uses C++20 std::format. GCC 15 is pickier about how you pass template parameters to format strings. The fix was straightforward - constrain the template parameters properly in the log wrapper.
This is C++ life. Compiler updates break things. Moving on.
The Namespace Migration
Hyprland's internals got reorganized. Half the APIs moved into a Desktop:: namespace:
// Old API
g_pCompositor->m_pLastWindow
g_pCompositor->m_pLastMonitor
pWindow->m_iWorkspaceID
pWindow->m_vPosition
// New API
Desktop::focusState()->window()
Desktop::focusState()->monitor()
pWindow->m_workspace->m_iID
pWindow->m_positionThis was tedious but mechanical. Read the compiler errors, find the new symbol names in Hyprland's headers, update. Repeat about 50 times.
The real fun was method renames:
vectorToWindowIdeal()→vectorToWindowUnified()isWorkspaceVisible()→ just gone, check workspace state directly- Various
m_bIsFoobooleans →isFoo()accessors
Two hours of grep, sed, and compiler-error-driven development later, it compiled.
The Silent Corruption
The plugin loaded. No crash. I toggled the overview. It worked!
Then I moved my mouse.
Nothing. The cursor moved, but clicks did nothing. Not in the overview - everywhere. Firefox, terminals, everything. Left-click was completely dead.
I unloaded the plugin. Mouse still broken. Had to restart Hyprland entirely.
The Hunt
This is the worst kind of bug - no crash, no error, just silent corruption. The plugin was poisoning Hyprland's input handling by merely being loaded.
The culprit was function hooks. Hycov hooks into input methods to implement features like "hotarea" (move mouse to screen edge to trigger overview):
// What the old code did
HyprlandAPI::createFunctionHook(
&CInputManager::mouseMoveUnified,
(void*)&hkMouseMoveUnified
);This casts a C++ member function pointer to void*.
This is undefined behavior.
Member function pointers in C++ are not simple addresses. They can be 8, 12, or 16 bytes depending on inheritance. Casting to void* truncates them. The hook corrupts the virtual table. Every mouse event now calls garbage.
GCC even warns about this: -Wpmf-conversions. I ignored the warning. I shouldn't have.
The Fix
Disable the problematic hooks entirely. The hotarea feature isn't worth breaking all mouse input:
// Don't hook mouseMoveUnified - it corrupts input
// Use dynamic callbacks instead where possible
HyprlandAPI::registerCallbackDynamic(
"mouseButton",
&onMouseButtonCallback
);Hyprland provides a callback system for plugin events that's safer than raw function hooks. The callback goes through Hyprland's own dispatch, no sketchy pointer casts required.
Button State Desync
With the hooks disabled, mouse input worked again. But clicking windows in the overview broke applications.
Symptoms: Click a window in overview → overview closes → that window's left-click is stuck "pressed". Firefox thought I was drag-selecting. Terminals thought I was selecting text.
The bug: when intercepting clicks in overview mode, I was consuming the PRESS event but letting the RELEASE through. The application never got the press, but got a phantom release. Or vice versa. Either way, the button state machine got desynced.
The fix: track which button presses you consume, and consume their releases too:
// Track consumed presses
std::set<int> consumedButtons;
void onMouseButton(int button, bool pressed) {
if (inOverviewMode && pressed) {
consumedButtons.insert(button);
handleOverviewClick(button);
return; // Consume press
}
if (consumedButtons.count(button) && !pressed) {
consumedButtons.erase(button);
return; // Consume matching release
}
// Forward to Hyprland
originalHandler(button, pressed);
}Dispatcher Registration Order
One more gotcha. Hyprland shows a red error banner:
Invalid dispatcher, requested 'hycov:toggleoverview' does not existBut I registered it! In PLUGIN_INIT():
APICALL PLUGIN_INIT() {
registerGlobalEventHook(); // Subscribes to events
registerDispatchers(); // Registers keybinds
}The event hook fires immediately. If your config binds a key to hycov:toggleoverview, Hyprland tries to resolve that dispatcher during the hook registration. But I haven't registered the dispatcher yet.
Flip the order:
APICALL PLUGIN_INIT() {
registerDispatchers(); // First - config can reference these
registerGlobalEventHook(); // Second - now dispatchers exist
}Current Status
After a weekend of debugging:
- Compiles with GCC 15
- Loads without crashing or corrupting input
- Overview toggle works via keybind
- Mouse works normally everywhere
- Hotarea feature disabled (and i'm not planning on bringing it back)
- Gestures still need to be implemented again
Good enough to use daily. The hotarea feature would be nice, but not at the cost of reliable input.
Lessons Learned
1. Hyprland's plugin API is unstable by design. Plugins need regular maintenance. A two-year gap is a death sentence.
2. -Wpmf-conversions warnings are real bugs. Member function pointer casts to void* are undefined behavior in C++. Don't do it, even if the API expects it.
3. Prefer callbacks over hooks. Hyprland's callback system is designed for plugins. Function hooks are powerful but fragile.
4. Test input after loading. Input handling corruption is silent. Move your mouse, click things, use your keyboard. Do this before assuming the plugin works.
5. Track event state symmetry. If you consume a press, consume the release. If you consume a key down, consume the key up. Desync bugs are subtle.
The hycov fork lives at github.com/ernestoCruz05/hycov. PRs welcome, especially if you figure out how to make hotarea work safely.