Skip to content

feat: Hyprland 0.54.0 compatibility#223

Open
douglas wants to merge 19 commits intoKZDKM:mainfrom
douglas:support_hyprland_0_54_0
Open

feat: Hyprland 0.54.0 compatibility#223
douglas wants to merge 19 commits intoKZDKM:mainfrom
douglas:support_hyprland_0_54_0

Conversation

@douglas
Copy link

@douglas douglas commented Mar 2, 2026

Summary

Hyprland 0.54.0 introduces several breaking API changes. This PR updates Hyprspace to be compatible with the new APIs.

Breaking changes addressed

  • Event system rewrite: registerCallbackDynamic() is deprecated (now a no-op). Migrated all 15 event hooks to the new typed Event::bus()->m_events.*.listen() system using CHyprSignalListener handles. All handlers now receive typed arguments directly instead of std::any.

  • Layout system rewrite: g_pLayoutManager->getCurrentLayout() was removed. Replaced with g_layoutManager->recalculateMonitor(PHLMONITOR) which now takes a monitor handle instead of a monitor ID.

  • Drag state moved: g_pInputManager->m_currentlyDraggedWindow and m_dragMode no longer exist on InputManager. Replaced with g_layoutManager->dragController()->target()->window() and g_layoutManager->endDragTarget().

  • Renderer signatures: renderWindow/renderLayer now use Time::steady_tp instead of timespec*. renderLayer takes PHLLS instead of PHLLSREF and has a new lockscreen parameter.

  • Keyboard event: No longer packed in std::unordered_map<std::string, std::any>. Handler receives IKeyboard::SKeyEvent directly, keyboard obtained via g_pSeatManager->m_keyboard.

  • Mouse axis event: No longer wrapped in a map. Handler receives IPointer::SAxisEvent directly.

  • Touch events: All touch handlers updated to receive typed event structs directly (SDownEvent, SMotionEvent, SUpEvent).

Commits

Organized as atomic, incremental changes:

  1. Update includes and typedefs — Foundation changes in Globals.hpp
  2. Update render stubs — New time types, remove drag trick hack from Render.cpp
  3. New layout manager APIrecalculateMonitor changes in Layout.cpp
  4. Drag controller API — New drag state access in Input.cpp
  5. Event bus migration — Full event system rewrite in main.cpp

Test plan

  • Build with make all against Hyprland 0.54.0 headers (verified locally)
  • Load plugin in Hyprland 0.54.0 and verify overview opens/closes
  • Verify windows render correctly in overview
  • Verify mouse click, drag, and scroll interactions
  • Verify swipe gestures work
  • Verify keyboard exit works
  • Verify workspace switching from overview
  • Verify multi-monitor support

douglas added 5 commits March 2, 2026 18:38
Replace LayoutManager include path (managers/ -> layout/), add
EventBus and Time headers, and update render function pointer
typedefs to use Time::steady_tp instead of timespec* and PHLLS
instead of PHLLSREF.
Replace timespec*/clock_gettime with Time::steady_tp/steadyNow(),
update renderLayerStub to pass PHLLS directly and add lockscreen
param, and remove the drag window/mode trick from renderWindowStub
since those fields no longer exist on InputManager.
Replace g_pLayoutManager->getCurrentLayout()->recalculateMonitor(ownerID)
with g_layoutManager->recalculateMonitor(pMonitor) which now takes
a PHLMONITOR instead of a MONITORID.
Replace g_pInputManager->m_currentlyDraggedWindow and direct
layout calls with g_layoutManager->dragController()->target()
and g_layoutManager->endDragTarget().
Replace all registerCallbackDynamic() calls with
Event::bus()->m_events.*.listen() using the new typed signal
system. Update all event handler signatures to receive typed
arguments directly instead of std::any. Replace
HOOK_CALLBACK_FN shared pointers with CHyprSignalListener.

Key changes per handler:
- onRender: receives eRenderStage directly
- onMouseButton/Axis: typed event + SCallbackInfo
- onSwipe*: typed event + SCallbackInfo
- onKeyPress: typed event, keyboard from SeatManager
- onTouch*: typed events + SCallbackInfo
- onWorkspaceChange: receives PHLWORKSPACE directly
- Drag window access via g_layoutManager->dragController()
@martijnboers
Copy link

I really tried but couldn't get this working on NixOS with 0.54. Several glitches, making changes to your code I got the stage where it would shortly show the overview. It's mostly pushing down my waybar/windows and then staying stuck here.

Hopefully someone else can also test and maybe find where things go wrong..

@jemabaris
Copy link

jemabaris commented Mar 4, 2026

Kinda same for me. After some hustle I managed to build, load and run the plugin but it also pushes waybar down and leaves it there and it will only work without any other open tiles. As soon as there's only one other tile open, toggling Hyprspace will crash Hyprland and throw me into the fallback config. It also crashes when clicking around on the workspace overlays a bunch.

This is under Arch EndeavourOS btw.

hyprspaceCrash.mp4

@onyx-tty
Copy link

onyx-tty commented Mar 4, 2026

@jemabaris The same thing happened to me yesterday when I enabled it, and it made me wonder if it's something on my side! Thank you for the recording, it's going to help clarify the issue.

@acidclouds
Copy link

A workaround for the effects shown in this video recording:

plugin:overview:affectStrut = false

@jemabaris
Copy link

A workaround for the effects shown in this video recording:

plugin:overview:affectStrut = false

This fixes Waybar being pushed down but the rest is still broken. Toggling Hyprspace with any other app open immediately crashes, so still unusable.

@koku17
Copy link

koku17 commented Mar 11, 2026

I'm on latest version of hyprland and getting the same library errors when compiling

Hyprland details

$ hyprland -v
Hyprland 0.54.1 built from branch v0.54.1 at commit 4b07770b9ef1cceb2e6f56d33538aaffb9186b9c clean ([gha] Nix: update inputs).
Date: Tue Mar 3 21:06:41 2026
Tag: v0.54.1, commits: 6979

Libraries:
Hyprgraphics: built against 0.5.0, system has 0.5.0
Hyprutils: built against 0.11.0, system has 0.11.0
Hyprcursor: built against 0.1.13, system has 0.1.13
Hyprlang: built against 0.6.8, system has 0.6.8
Aquamarine: built against 0.10.0, system has 0.10.0

Version ABI string: 4b07770b9ef1cceb2e6f56d33538aaffb9186b9c_aq_0.10_hu_0.11_hg_0.5_hc_0.1_hlg_0.6
no flags were set

Building Plugin

$ hpyrpm update -v
...
→ Cloning https://github.com/KZDKM/Hyprspace
✔ cloned
✔ found hyprpm manifest
✔ parsed manifest, found 1 plugins:
→ Hyprspace by KZdkm version
→ Manifest has 17 pins, checking
✔ Hyprland headers OK
→ Building Hyprspace
 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╍━━━━━━━━━━━━━━━━━━━  3 / 5  Building plugin(s)[v] shell returned:  -> cd /run/user/1000/hyprpm/user && PKG_CONFIG_PATH="/var/cache/hyprpm/user/headersRoot/share/pkgconfig:$PKG_CONFIG_PATH" make all
g++ -shared -fPIC --no-gnu-unique -Wall -g -DWLR_USE_UNSTABLE -std=c++2b -O2 `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` src/Input.cpp src/Layout.cpp src/main.cpp src/Overview.cpp src/Render.cpp -o Hyprspace.so
In file included from src/Input.cpp:2:
src/Globals.hpp:8:10: fatal error: hyprland/src/managers/LayoutManager.hpp: No such file or directory
    8 | #include <hyprland/src/managers/LayoutManager.hpp>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
In file included from src/Layout.cpp:2:
src/Globals.hpp:8:10: fatal error: hyprland/src/managers/LayoutManager.hpp: No such file or directory
    8 | #include <hyprland/src/managers/LayoutManager.hpp>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
In file included from src/main.cpp:6:
src/Globals.hpp:8:10: fatal error: hyprland/src/managers/LayoutManager.hpp: No such file or directory
    8 | #include <hyprland/src/managers/LayoutManager.hpp>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
In file included from src/Overview.cpp:2:
src/Globals.hpp:8:10: fatal error: hyprland/src/managers/LayoutManager.hpp: No such file or directory
    8 | #include <hyprland/src/managers/LayoutManager.hpp>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
In file included from src/Render.cpp:2:
src/Globals.hpp:8:10: fatal error: hyprland/src/managers/LayoutManager.hpp: No such file or directory
    8 | #include <hyprland/src/managers/LayoutManager.hpp>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
make: *** [Makefile:9: all] Error 1
...

@pascal-fischer
Copy link

I did continue a bit on this one. I made it work for me quite stable. I am running on EnveavourOS as well. main...pascal-fischer:Hyprspace:main
Would be nice to collect some feedback from other platforms.

@0xl30
Copy link

0xl30 commented Mar 13, 2026

I did continue a bit on this one. I made it work for me quite stable. I am running on EnveavourOS as well. main...pascal-fischer:Hyprspace:main Would be nice to collect some feedback from other platforms.

Thanks Bro It's Work 👍
on my arch linux
(Create New PR For Easy To Build And Find Your Commit )

@martijnboers
Copy link

martijnboers commented Mar 13, 2026

I did continue a bit on this one. I made it work for me quite stable. I am running on EnveavourOS as well. main...pascal-fischer:Hyprspace:main Would be nice to collect some feedback from other platforms.

I got it working on NixOS with Hyprland 54.2. 🏆

Now for the weirdness: When I first login to my session the plugin is unusable, windows become opaque, the overview shows 1 workspace glitching every time I :toggle or :open. I tried making a screenshot of this behavior and run grim from the command line, it starts working... Same with recording my screen with Kooha, I can reproduce this behavior by logging in and out..

glitch-small.mp4

Any wizzards that could explain what is going on? Do these recording apps somehow refresh something in the Wayland protocol that makes this start working? Tried rebooting, reloading wayland config. Different amount of applications that are started. Happy for now at least.

douglas added 8 commits March 13, 2026 12:08
The .listen() method on Cancellable<T> signals does not properly thread
SCallbackInfo& back to handlers, so info.cancelled was silently ignored,
causing double-firing and crashes on Hyprland 0.54.x.

Add listenCancellable<EventType>() template in Globals.hpp that uses
registerListenerInternal to unpack void* args as
std::tuple<const EventType&, Event::SCallbackInfo&>*.
Migrate mouse button/axis, touch down/motion/up, swipe begin/update/end,
and keyboard key hooks from .listen() to listenCancellable<EventType>()
so SCallbackInfo.cancelled is properly propagated to Hyprland.

Also pass e.axis to axisEvent and remove the WL_POINTER_AXIS_SOURCE_WHEEL
filter (replaced by per-axis routing in axisEvent itself).

Disable gestures by default (disableGestures = 1) as they are unstable
on Hyprland 0.54.x.
Storing a truncated 32-bit pointer in prevFullscreen and resolving it
via getWindowFromHandle() is fragile and can crash when a window is
destroyed between show() and hide().

Change prevFullscreen type to std::vector<std::tuple<PHLWINDOWREF,
eFullscreenMode>>, store PHLWINDOWREF(w) on open, and call .lock() with
a null check on restore.
CFakeDamageElement was a one-off IPassElement subclass used only to
force blur recomputation via needsLiveBlur(). Replace with a second
damageMonitor() call which achieves the same result cleanly.

Also fix modifs.push_back() calls to use std::make_pair(type, std::any(value))
instead of aggregate {type, value} init which does not correctly construct
the std::any member.

Remove the unused #include "src/helpers/memory/Memory.hpp".
Add wl_pointer_axis axis parameter to axisEvent. Horizontal scroll
always pans the panel; vertical scroll switches workspace or pans the
panel when cursor is over it.

Hardcode swipe fingers to 3 — the gestures:workspace_swipe_fingers
config key does not exist in Hyprland 0.54.x.
Add early return if getOwner() returns null to prevent a crash when
updateLayout() is called on a widget whose monitor has been unplugged.
The KZDKM refactor moved arrangeLayersForMonitor() before the
m_reservedArea assignment, then read pMonitor->m_reservedArea.top()
and added currentHeight to it. This double-counts on every call because
arrangeLayersForMonitor() already applied layer surface reservations.

Restore the correct ordering: set our panel reservation first
(replacing the value entirely), then call arrangeLayersForMonitor()
so it adds LS reservations on top without double-counting.

Also restore the active=false branch to reset m_reservedArea to an
empty CReservedArea() so the panel space is fully released on hide.
@douglas
Copy link
Author

douglas commented Mar 13, 2026

Hello folks! I added a bunch of other commits, but since I'm using hyprtasking I didn't do a very extensive test, so if you can, please test it so we know it is stable.

I also tried to support Hyperland 0.54.0, 0.54.1 and 0.54.2, lets see how it goes =)

@martijnboers
Copy link

Still crashing with the new additions for me, attached the logs.

hyprlandCrashReport56952.txt

findFunctionsByName returns ALL matching symbols — on some systems
Screenshare::CScreenshareFrame::renderWindow() appears first, causing
renderWindowStub to call the wrong function and SEGV.

Use the existing findFunctionBySymbol() helper to filter by demangled
class name (CHyprRenderer::renderWindow / CHyprRenderer::renderLayer),
restoring the behaviour from before the KZDKM refactoring commit.

Fixes: KZDKM#223 (comment)
@douglas
Copy link
Author

douglas commented Mar 13, 2026

@martijnboers Thanks for the detailed backtrace — that was exactly what was needed to track this down.

The crash is caused by findFunctionsByName("renderWindow") returning all symbols with that name across all translation units, including Screenshare::CScreenshareFrame::renderWindow(). On your system that symbol sorts before CHyprRenderer::renderWindow(), so pRenderWindow ends up pointing at the wrong function.

Fix is in commit ec0f45d on the support_hyprland_0_54_0 branch: switched to findFunctionBySymbol() which filters by demangled class name, so only CHyprRenderer::renderWindow is selected.

Could you rebuild from support_hyprland_0_54_0 and let me know if the crash is gone?

@jemabaris
Copy link

jemabaris commented Mar 13, 2026

Thank you for your fixes, Douglas. Now we're definitely getting somewhere. Although it's still not quite working out for me. Pretty much any other app's window, except for the terminal's glitch out shortly after toggling Hyprspace. It's much better though than before, where doing so would immediately result in a crash.
See the video attached. If I can anyhow provide you with any additional information you need for further troubleshooting please let me know.

video_2026-03-14_00-02-17.mp4

@jemabaris
Copy link

Small update: I just realized that, similar to the description of another user on here, the issue with the disappearing windows seems to be gone after taking a screenshot once. It really looks like taking a screenshot or video wakes up something that's needed for hyprspace to function fully.

hide() was missing a damageMonitor() call, causing windows to appear
glitched/invisible after the close animation finished. draw() exits early
once active=false and the animation is done, so no further damage was
ever issued.

Three fixes:
- Add damageMonitor() to hide() before scheduleFrameForMonitor(), matching
  what show() already does.
- Add curYOffset->setCallbackOnEnd callback (remove=false) in constructor
  and updateConfig() to issue a final damage+scheduleFrame once the close
  animation completes.
- Remove duplicate rounding().unset() in renderWindowStub() (Render.cpp).

Fixes the regression reported in PR KZDKM#223 by @jemabaris and @martijnboers.
@jemabaris
Copy link

jemabaris commented Mar 16, 2026

@douglas I just rebuilt with the changes in your latest commit but the problem still persists sadly. Maybe I made a mistake, idk, but I couldn't see any improvement of the disappearing window glitch. I've rebuilt it even a second time to make sure but without success.
Taking a single screenshot with hypershot still seems to completely fix the issue until the next reboot.

@douglas
Copy link
Author

douglas commented Mar 16, 2026

@jemabaris The disappearing windows issue you're describing (fixed by taking a screenshot) is exactly what the latest commit 586b095 targets.

The root cause: hide() wasn't calling damageMonitor(), and once the close animation finished, draw() would stop running (early return when !active && !isBeingAnimated()), leaving the compositor with stale render state. Taking a screenshot forces a full damageMonitor() call via screencopy, which is why that fixed it.

The fix adds two things:

  1. An explicit damageMonitor() + scheduleFrameForMonitor() in hide() (matching what show() already did)
  2. A setCallbackOnEnd callback on the curYOffset animation that fires one final damageMonitor() after the close animation completes

Could you rebuild from the support_hyprland_0_54_0 branch and let me know if the window glitch is gone?

@jemabaris
Copy link

@douglas I wrote my last message after rebuilding with commit 586b095 and wanted to let you know that it sadly didn't fix the issues I've been experiencing with the glitching windows yet.

douglas added 4 commits March 15, 2026 23:16
nearestNeighbor().set() at PRIORITY_SET_PROP was never balanced with
unset() at the same priority level, permanently overriding nearest-neighbor
rendering for every window rendered as a thumbnail.
damageMonitor() alone is insufficient: windows damaged at thumbnail
coordinates during draw() are considered up-to-date by the compositor
at their real positions after the overview closes. Fix by iterating all
mapped windows on the monitor in hide() and the animation-end callback
and calling damageWindow() on each, forcing a repaint at their real
coordinates. Addresses the disappearing window glitch reported in PR KZDKM#223.
Add windowBoxes field (mirrors workspaceBoxes) populated during draw().
Each rendered window thumbnail position is converted to input coordinates
and stored alongside a PHLWINDOWREF. Windows currently being dragged are
skipped so their thumbnail disappears during drag. Extracted common
window rendering into a renderAndTrackWindow lambda to reduce duplication.
Replace the broken CKeybindManager::mouse("1movewindow") approach —
it hit-tests at the real cursor position which fails in the overview panel.

New approach uses draggedWindowRef (PHLWINDOWREF) on the widget:
- Press: hit-test windowBoxes, set draggedWindowRef on match
- Release: if draggedWindowRef is set and cursor is over a workspace,
  moveWindowToWorkspaceSafe + clear draggedWindowRef
- No dependency on Hyprland's internal drag API (CWindowTarget::create
  is not exported, beginDragTarget unusable from plugins)

Also removed dead code: the old targetWindow/pMouseKeybind paths that
could never trigger because targetWorkspace was never null in the panel.
@douglas douglas force-pushed the support_hyprland_0_54_0 branch from 4ef1c4f to db9c78c Compare March 16, 2026 04:07
@jemabaris
Copy link

@douglas I just did another build with the latest changes and the issue still persists. It's interesting though that you don't seem to have these issues yourself. Wonder why that is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants