





Noctalia v5 on NixOS with Hyprland
Hi,
I had been running Arch for a long time with MangoWM + the usual Waybar/Fuzzel/Sww etc stack, I got a new Thinkpad and decided to try NixOS this time.
After much testing, I decided to stabilise on Hyprland with Noctalia v5. I like the various layouts in Hyprland (and eye candy!) and love how much you can customise it in lua - the wiki is very detailed and easy to follow.
I use the master layout for my workspaces but scrolling layout for my scratchpad - I had fun customising all the graphics, beziers, etc, but ironically in the end decided to use plain white borders for selected window and disabled all blur and shadows (I found them distracting!).
Similarly I found Noctalia v5 incredibly easy to configure following their wiki (yes Arch users have a thing for wikis, as any question is guaranteed to get a RTFM response!). Surprisingly I found Noctalia v5 pretty much uses the same RAM as my usual stack of programs (it replaces bar, launcher, notification daemon, wallpaper manager, lock screen, polkit, among others) and it is definitely easier to configure and more cohesive.
I use greetd and trigger noctalia's lock screen on startup to act as a login screen (no real display manager!)
There are so many cool features in NixOS, for instance when making this post, I wanted to screenshot my lockscreen but Noctalia's native screenshot tool didn't allow me to. So I decided to use my trusted grim and thanks to NixOS - I didn't even have to install it! Just did this:
nix-shell -p grim --run "sleep 5 && grim lockscreen.png"
and locked my screen within the 5 seconds to get the screenshot!
I guess the point of this post was that I just wanted to say a big thank you to all the development team for Noctalia and all the other developers out there - your efforts make all this possible - keep up the great work!
If anyone is interested as to what hyprland and noctalia config looks like in NixOS home manager, I typed my hyprland config into a file I created called hyprland.nix, which I then imported into home.nix. For Noctalia I configured everything using the settings UI then read .local/state/noctalia/settings.toml to create my declarative Noctalia NixOS home manager config in a file called noctalia.nix, which I also imported into home.nix (see second code box below).
Hope its of some use to someone.
{ config, pkgs, ... }:
{
wayland.windowManager.hyprland = {
enable = true;
xwayland.enable = true;
configType = "lua";
settings = {};
extraConfig = ''
hl.config({
input = {
kb_layout = "gb",
},
xwayland = {
force_zero_scaling = true,
},
general = {
layout = "master",
border_size = 2,
gaps_in = 0,
gaps_out = 0,
col = {
active_border = "rgba(ffffffff)",
inactive_border = "rgba(1a1a1aff)",
},
},
master = {
orientation = "left",
allow_small_split = true,
mfact = 0.5,
},
decoration = {
rounding = 5,
rounding_power = 10,
active_opacity = 1.0,
inactive_opacity = 1.0,
dim_special = 0.5,
blur = {
enabled = false,
},
shadow = {
enabled = false,
},
},
misc = {
disable_hyprland_logo = true,
disable_splash_rendering = true,
render_unfocused_fps = 1,
},
})
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = 1 })
hl.on("hyprland.start", function()
hl.exec_cmd("noctalia --daemon")
hl.exec_cmd("sleep 1 && noctalia msg session lock")
end)
hl.window_rule({ match = { class = "^(thunar|Thunar)$", title = "Preferences" }, float = true, center = true, size = { 550, 500 } })
hl.window_rule({ match = { class = "^(thunar|Thunar)$", title = "^(Rename.*)$" }, float = true, move = {"cursor_x-(window_w*0.5)", "cursor_y-(window_h*0.5)"} , size = { 300, 100 } })
hl.window_rule({ match = { class = "firefox", title = ".*Save.*" }, float = true, center = true })
hl.window_rule({ match = { class = "firefox", title = ".*Open.*" }, float = true, center = true })
hl.window_rule({ match = { class = "DesktopEditors", float = true }, float = true, center = true })
hl.window_rule({ match = { class = "xdg-desktop-portal-gtk", title = ".*Open.*" }, size = {"monitor_w * 0.5", "monitor_h * 0.5"}, float = true, center = true })
hl.window_rule({ match = { class = "xdg-desktop-portal-gtk", title = ".*Open.*" }, size = {"monitor_w * 0.5", "monitor_h * 0.5"}, float = true, center = true })
hl.workspace_rule({ workspace = "special:scratchpad", layout = "scrolling", gaps_out = 40, gaps_in = 20})
hl.curve("fast", { type = "bezier", points = { {0, 1}, {0, 1} } })
hl.curve("out", { type = "bezier", points = { {1, 1}, {1, 1} } })
hl.curve("in", { type = "bezier", points = { {0, 1}, {1, 1} } })
hl.animation({ leaf = "global", enabled = true, speed = 5, bezier = "fast" })
hl.animation({ leaf = "windows", enabled = true, speed = 5, bezier = "fast", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 5, bezier = "fast", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "out", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 5, bezier = "fast" })
hl.animation({ leaf = "fade", enabled = false })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "in", style = "slide" })
hl.animation({ leaf = "layers", enabled = true, speed = 5, bezier = "fast", style = "slide" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 5, bezier = "in", style = "slidefadevert 100%"})
hl.bind("SUPER + RETURN", hl.dsp.exec_cmd("kitty"))
hl.bind("SUPER + Q", hl.dsp.window.close())
hl.bind("SUPER + SPACE", hl.dsp.exec_cmd("noctalia msg panel-toggle launcher"))
hl.bind("ALT + TAB", hl.dsp.exec_cmd("noctalia msg window-switcher"))
hl.bind("SUPER + E", hl.dsp.exec_cmd("thunar"))
hl.bind("SUPER + D", hl.dsp.focus({ workspace = "empty" }))
hl.bind("SUPER + SHIFT + D", hl.dsp.window.move({ workspace = "empty" }))
hl.bind("SUPER + L", hl.dsp.exec_cmd("noctalia msg session lock"))
hl.bind("SUPER + V", hl.dsp.exec_cmd("noctalia msg panel-toggle clipboard"))
hl.bind("SUPER + F", hl.dsp.window.fullscreen({ mode = "maximized", action = "toggle" }))
hl.bind("SUPER + SHIFT + F", hl.dsp.window.fullscreen({ mode = "fullscreen", action = "toggle" }))
hl.bind("SUPER + Page_Up", hl.dsp.focus({ workspace = "+1" }))
hl.bind("SUPER + Page_Down", hl.dsp.focus({ workspace = "-1" }))
hl.bind("SUPER + SHIFT + Page_Up", hl.dsp.window.move({ workspace = "+1" }))
hl.bind("SUPER + SHIFT + Page_Down", hl.dsp.window.move({ workspace = "-1" }))
hl.bind("SUPER + S", hl.dsp.workspace.toggle_special("scratchpad"))
hl.bind("SUPER + SHIFT +S", hl.dsp.window.move({ workspace = "special:scratchpad" }))
local function layout_bind(bind_table)
return function ()
local workspace = hl.get_active_special_workspace() or
hl.get_active_workspace()
if not workspace then
return
end
local layout = workspace.tiled_layout
if bind_table[layout] then
hl.dispatch(bind_table[layout])
end
end
end
hl.bind("SUPER + comma", layout_bind({
master = hl.dsp.layout("mfact -0.01"),
scrolling = hl.dsp.layout("colresize -0.01"),
}), { repeating = true })
hl.bind("SUPER + period", layout_bind({
master = hl.dsp.layout("mfact +0.01"),
scrolling = hl.dsp.layout("colresize +0.01"),
}), { repeating = true })
hl.bind("SUPER + SHIFT + comma", layout_bind({
master = hl.dsp.layout("orientationprev"),
scrolling = hl.dsp.layout("swapcol l"),
}))
hl.bind("SUPER + SHIFT + period", layout_bind({
master = hl.dsp.layout("orientationnext"),
scrolling = hl.dsp.layout("swapcol r"),
}))
hl.bind("SUPER + slash", layout_bind({
master = hl.dsp.layout("addmaster"),
scrolling = hl.dsp.layout("consume"),
}))
hl.bind("SUPER + SHIFT + slash", layout_bind({
master = hl.dsp.layout("removemaster"),
scrolling = hl.dsp.layout("expel"),
}))
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("noctalia msg volume-mute"))
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("noctalia msg volume-down"))
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("noctalia msg volume-up"))
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("noctalia msg mic-mute"))
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("noctalia msg brightness-down"))
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("noctalia msg brightness-up"))
hl.bind("XF86Display", hl.dsp.exec_cmd("noctalia msg panel-toggle control-center"))
hl.bind("XF86NotificationCenter", hl.dsp.exec_cmd("noctalia msg power-cycle"))
hl.bind("XF86PickupPhone", hl.dsp.exec_cmd("noctalia msg bluetooth-toggle"))
hl.bind("XF86HangupPhone", hl.dsp.exec_cmd("noctalia msg panel-toggle session"))
hl.bind("XF86Favorites", hl.dsp.exec_cmd("noctalia msg settings-toggle"))
hl.bind("Print", hl.dsp.exec_cmd("noctalia msg screenshot-fullscreen"))
hl.bind("XF86SelectiveScreenshot", hl.dsp.exec_cmd("noctalia msg screenshot-region"))
hl.bind("SUPER + 1", hl.dsp.focus({ workspace = "1" }))
hl.bind("SUPER + 2", hl.dsp.focus({ workspace = "2" }))
hl.bind("SUPER + 3", hl.dsp.focus({ workspace = "3" }))
hl.bind("SUPER + 4", hl.dsp.focus({ workspace = "4" }))
hl.bind("SUPER + 5", hl.dsp.focus({ workspace = "5" }))
hl.bind("SUPER + 6", hl.dsp.focus({ workspace = "6" }))
hl.bind("SUPER + 7", hl.dsp.focus({ workspace = "7" }))
hl.bind("SUPER + 8", hl.dsp.focus({ workspace = "8" }))
hl.bind("SUPER + 9", hl.dsp.focus({ workspace = "9" }))
hl.bind("SUPER + SHIFT + 1", hl.dsp.window.move({ workspace = "1" }))
hl.bind("SUPER + SHIFT + 2", hl.dsp.window.move({ workspace = "2" }))
hl.bind("SUPER + SHIFT + 3", hl.dsp.window.move({ workspace = "3" }))
hl.bind("SUPER + SHIFT + 4", hl.dsp.window.move({ workspace = "4" }))
hl.bind("SUPER + SHIFT + 5", hl.dsp.window.move({ workspace = "5" }))
hl.bind("SUPER + SHIFT + 6", hl.dsp.window.move({ workspace = "6" }))
hl.bind("SUPER + SHIFT + 7", hl.dsp.window.move({ workspace = "7" }))
hl.bind("SUPER + SHIFT + 8", hl.dsp.window.move({ workspace = "8" }))
hl.bind("SUPER + SHIFT + 9", hl.dsp.window.move({ workspace = "9" }))
hl.bind("SUPER + left", hl.dsp.focus({ direction = "left" }))
hl.bind("SUPER + right", hl.dsp.focus({ direction = "right" }))
hl.bind("SUPER + up", hl.dsp.focus({ direction = "up" }))
hl.bind("SUPER + down", hl.dsp.focus({ direction = "down" }))
hl.bind("SUPER + SHIFT + left", hl.dsp.window.move({ direction = "left" }))
hl.bind("SUPER + SHIFT + right", hl.dsp.window.move({ direction = "right" }))
hl.bind("SUPER + SHIFT + up", hl.dsp.window.move({ direction = "up" }))
hl.bind("SUPER + SHIFT + down", hl.dsp.window.move({ direction = "down" }))
'';
};
}
{ inputs, ... }:
{
imports = [ inputs.noctalia.homeModules.default ];
programs.noctalia = {
enable = true;
settings = {
config_version = 12;
bar.default = {
color = "primary";
concave_edge_corners = false;
end = [ "tray" "bluetooth" "network" "volume" "battery" "session" ];
font_family = "MonaspiceNe Nerd Font";
icon_color = "primary";
margin_ends = 0;
padding = 10;
radius = 0;
start = [ "workspaces" ];
thickness = 20;
widget_spacing = 10;
};
dock.show_dots = true;
location.auto_locate = true;
lockscreen = {
blur_intensity = 0.0;
wallpaper = "/etc/nixos/Wallpapers/Anime Wisteria.png";
};
lockscreen_widgets = {
enabled = true;
schema_version = 2;
widget_order = [
"lockscreen-login-box@eDP-1"
"lockscreen-widget-0000000000000001"
"lockscreen-widget-0000000000000002"
];
grid = {
cell_size = 24;
major_interval = 4;
visible = true;
};
widget."lockscreen-login-box@eDP-1" = {
box_height = 70.0;
box_width = 336.0;
cx = 960.0;
cy = 695.0;
output = "eDP-1";
rotation = 0.0;
type = "login_box";
settings = {
background_color = "surface_variant";
background_opacity = 0.0;
background_radius = 10.0;
center_password_text = true;
input_opacity = 1.0;
input_radius = 6.0;
layout = "compact";
show_caps_lock = true;
show_keyboard_layout = false;
show_login_button = false;
show_media = true;
show_session_buttons = true;
show_unlock_hint = true;
show_weather = true;
};
};
widget."lockscreen-widget-0000000000000001" = {
box_height = 56.0;
box_width = 176.0;
cx = 960.0;
cy = 444.0;
output = "eDP-1";
rotation = 0.0;
type = "clock";
settings = {
background_opacity = 0.0;
background_padding = 3;
background_radius = 10;
clock_style = "digital";
font_family = "MonaspiceKr Nerd Font";
format = "{:%-I:%M %p}";
};
};
widget."lockscreen-widget-0000000000000002" = {
box_height = 64.0;
box_width = 648.0;
cx = 960.0;
cy = 372.0;
output = "eDP-1";
rotation = -0.0;
type = "clock";
settings = {
background_opacity = 0.0;
background_padding = 3;
background_radius = 10;
font_family = "MonaspiceKr Nerd Font";
format = "{:%A %d %B %Y}";
};
};
};
shell = {
app_icon_color = "error";
font_family = "MonaspiceNe NF";
polkit_agent = true;
launcher.categories = false;
};
theme = {
mode = "dark";
source = "wallpaper";
wallpaper_scheme = "m3-rainbow";
};
wallpaper = {
directory = "/etc/nixos/Wallpapers/";
fill_mode = "span";
transition_on_startup = true;
automation.recursive = false;
default.path = "/etc/nixos/Wallpapers/Anime Moon.png";
last.path = "/etc/nixos/Wallpapers/Anime Moon.png";
monitors."eDP-1".path = "/etc/nixos/Wallpapers/Anime Moon.png";
};
widget.clock.format = "{:%A %-e %B %Y %-I:%M %P}";
};
};
}