Migrating from Sway/Waybar to Hyprland/Noctalia
I spent a few weeks moving from Sway + Waybar to Hyprland with Noctalia (a Quickshell-based desktop shell). The migration surface is bigger than it looks — here’s what I wish I’d known upfront.
Why Noctalia?
Noctalia replaces your bar, control center, and launcher with proper Qt6/QML widgets. Instead of a dozen shell scripts with return-type: json, you get native CPU/network/bluetooth widgets, a side panel for settings, and a plugin system for custom indicators. The bar is a real QML app, not a hack.
The tradeoff: Noctalia intercepts Hyprland’s config and replaces it with Lua. Every hyprctl dispatch call gets rerouted through hl.dsp.*. Classic dispatcher strings silently fail.
The Lua Dispatch Trap
This is the biggest gotcha. In standard Hyprland you write:
bind = SUPER, G, exec, hyprctl dispatch togglegroupIn Noctalia’s Lua config:
hl.keybind({ mods = "SUPER", key = "G" }, function()
hl.dsp.group.toggle()
end)The problem is every script and alias that calls hyprctl dispatch <classic string> breaks. Classic dispatchers like togglespecialworkspace, moveintoworkspace, or togglegroup error out because the Lua parser intercepts the dispatch and doesn’t know what to do with a plain string.
The worst one I hit: group-all. The old dispatcher was movegroupwindow — which only reorders windows already in a group. The real merge dispatcher is:
hl.dsp.window.move({ into_group = "l" })Not hl.dsp.group.move_window() (reorder-only). Took me two sessions to find the right API.
Rule of thumb: rg 'hyprctl dispatch' across your scripts and configs before you start. Every single one needs the hl.dsp.* treatment.
What Maps Where
Waybar had ~20 modules. Here’s where they landed:
| Destination | Count | Examples |
|---|---|---|
| Native Noctalia widget | ~10 | workspaces, clock, CPU, memory, disk |
| Control center side panel | ~6 | network, bluetooth, power profile, DND, volume |
| CustomButton (polling) | ~7 | swap monitor, transcribe toggle, recording indicator |
The control center handles things like network and bluetooth better than a bar widget ever did — you get the full connection UI on click, not a dropdown script.
CustomButton Limits
CustomButton is Noctalia’s escape hatch for anything without a native widget. But it has sharp edges:
- No
return-type: json— you can’t return structured data like Waybar’s"text": "...", "class": "...". Just plain text. - No signal-based refresh — only polling via
textIntervalMs(interval in ms) or atextStreamscript (continuous stdout). - No per-instance name — all CustomButtons are identical in the widget tree, so you can’t style them individually without workarounds.
Want an animated recording pulse? Can’t do it in a CustomButton — you need a proper QML plugin.
Bar as a Systemd Service
By default Noctalia launches from autostart.lua as a one-shot. If it crashes, it stays dead until next login. Move it to a systemd user service:
[Unit]
Description=Noctalia Shell
After=graphical-session.target
[Service]
ExecStart=/usr/bin/qs -c noctalia-shell
Restart=always
RestartSec=1
StartLimitIntervalSec=0Gotcha: if you had an old qs process from autostart, it holds a single-instance lock. The new service fails, restarts, fails again — infinite loop until you kill the orphan. Kill it first, then enable the service.
Plugin Gotchas
Noctalia plugins (QML widgets) have a few non-obvious rules:
- Plugin dir must be a real directory, not a symlink. Noctalia won’t load it.
- Enable separately in
~/.config/noctalia/plugins.json— adding it tosettings.jsonisn’t enough. - Unloaded plugins in the layout get auto-deleted from
settings.json. If your plugin fails to load, the layout entry disappears and you have to re-add it.
Lua API Quirks
A few things that tripped me up:
%is broken inhl.size— use fixed px ormonitor_w * 0.xinstead.- Monitor selector only accepts
"+1"/"-1"(or name/id), not directionall/r. - Window rules apply at creation only — reloading config won’t resize open windows.
hyprctl keyword animations:enabled falseis rejected — usehyprctl eval 'hl.animation(...)'instead.
Closing Thoughts
Noctalia is a real upgrade from Sway+Waybar. The native widgets, control center, and QML plugin system are genuinely better than the shell-script bar approach. But budget time for the dispatch migration — every hyprctl dispatch in your configs and scripts needs the hl.dsp.* treatment, and the API isn’t always where you expect it.