wip
CI / lint (push) Successful in 33s
CI / eval (push) Failing after 1m41s

This commit is contained in:
Miguel Palhas
2026-07-30 13:18:47 +01:00
parent 8cfa920ea8
commit 3d9c273fd3
9 changed files with 317 additions and 345 deletions
+13 -17
View File
@@ -26,24 +26,20 @@
# RetroArch (bare — cores/ROMs added by hand, WebDAV configured in-app). # RetroArch (bare — cores/ROMs added by hand, WebDAV configured in-app).
custom.gaming.retroarch = true; custom.gaming.retroarch = true;
wayland.windowManager.hyprland.settings = { wayland.windowManager.hyprland.extraConfig = ''
exec-once = [ hl.workspace_rule({ workspace = "1", monitor = "DP-3" })
]; hl.workspace_rule({ workspace = "2", monitor = "eDP-1" })
hl.workspace_rule({ workspace = "3", monitor = "eDP-1" })
hl.workspace_rule({ workspace = "4", monitor = "eDP-1" })
workspace = [ hl.window_rule({
"1, monitor:DP-3" match = { class = "bevy-.*" },
"2, monitor:eDP-1" no_initial_focus = true,
"3, monitor:eDP-1" float = true,
"4, monitor:eDP-1" size = "800 600",
]; move = "100%-800 100%-600",
})
windowrule = [ '';
"no_initial_focus on, match:class bevy-.*"
"float on, match:class bevy-.*"
"size 800 600, match:class bevy-.*"
"move 100%-800 100%-600, match:class bevy-.*"
];
};
# v4 `bar.density` has no v5 equivalent; spacing is tuned via # v4 `bar.density` has no v5 equivalent; spacing is tuned via
# bar.main.padding / widget_spacing / thickness if needed. # bar.main.padding / widget_spacing / thickness if needed.
+8 -9
View File
@@ -1,12 +1,11 @@
_: _:
{ {
wayland.windowManager.hyprland.settings = { # Hyprland 0.55+ is Lua-only (see home/common/programs/hyprland).
monitor = [ wayland.windowManager.hyprland.extraConfig = ''
"eDP-1, preferred, 0x0, 1, bitdepth, 8" hl.monitor({ output = "eDP-1", mode = "preferred", position = "0x0", scale = 1, bitdepth = 8 })
"DP-1, preferred, auto-up, 1" hl.monitor({ output = "DP-1", mode = "preferred", position = "auto-up", scale = 1 })
"DP-2, preferred, auto-up, 1" hl.monitor({ output = "DP-2", mode = "preferred", position = "auto-up", scale = 1 })
"DP-3, preferred, auto-up, 1" hl.monitor({ output = "DP-3", mode = "preferred", position = "auto-up", scale = 1 })
"DP-4, preferred, auto-up, 1" hl.monitor({ output = "DP-4", mode = "preferred", position = "auto-up", scale = 1 })
]; '';
};
} }
+175 -221
View File
@@ -66,260 +66,214 @@ in
enable = true; enable = true;
package = null; package = null;
portalPackage = null; portalPackage = null;
# Keep the hyprlang config format (the new default is "lua"); our # Hyprland 0.55+ removed the hyprlang parser entirely — the config format
# settings are written as hyprlang. # is now Lua only (see home/common/programs/hyprland — the whole config
configType = "hyprlang"; # below is raw Lua using the hl.* API). home-manager still emits its own
# systemd-session start hook, so we don't duplicate that here.
configType = "lua";
plugins = [ ]; plugins = [ ];
settings = { extraConfig = ''
plugin = { local mod = "SUPER"
overview = {
autoDrag = true;
exitOnClick = true;
exitOnSwitch = true;
showNewWorkspace = false;
showEmptyWorkspace = false;
};
};
input = {
kb_options = "ctrl:nocaps";
repeat_delay = 150;
touchpad = { -- look & feel / behaviour
natural_scroll = "yes"; hl.config({
}; general = {
numlock_by_default = true; border_size = 1,
}; gaps_in = 0,
gaps_out = 0,
snap = {
enabled = true,
border_overlap = true,
},
col = {
inactive_border = "0x99999999",
active_border = "0x99999999",
},
},
cursor = { input = {
no_hardware_cursors = "yes"; kb_options = "ctrl:nocaps",
# Auto-hide the pointer after 3s of no movement so a parked cursor repeat_delay = 150,
# doesn't sit on top of a game (visible in the stream too). It touchpad = {
# reappears the instant the mouse moves. natural_scroll = true,
inactive_timeout = 3; },
}; numlock_by_default = true,
},
general = { cursor = {
border_size = 1; no_hardware_cursors = true,
gaps_in = 0; -- Auto-hide the pointer after 3s of no movement so a parked cursor
gaps_out = 0; -- doesn't sit on top of a game (visible in the stream too). It
snap = { -- reappears the instant the mouse moves.
enabled = true; inactive_timeout = 3,
border_overlap = true; },
};
"col.inactive_border" = "0x99999999"; misc = {
"col.active_border" = "0x99999999"; disable_hyprland_logo = true,
}; disable_splash_rendering = true,
on_focus_under_fullscreen = 2,
},
misc = { ecosystem = {
disable_hyprland_logo = true; no_update_news = true,
disable_splash_rendering = true; },
on_focus_under_fullscreen = 2;
};
ecosystem = { xwayland = {
no_update_news = true; force_zero_scaling = true,
}; },
xwayland = { decoration = {
force_zero_scaling = true; blur = {
}; enabled = true,
popups = false,
},
shadow = {
enabled = false,
},
},
})
decoration = { -- keep default animations, but speed them all up
blur = { hl.animation({ leaf = "global", enabled = true, speed = 2, bezier = "default" })
enabled = true;
popups = false;
};
shadow = {
enabled = false;
};
};
# keep default animations, but speed them all up -- environment
animations = { hl.env("GDK_SCALE", "2.0")
animation = [ hl.env("XCURSOR_SIZE", "${toString cfg.cursorSize}")
"global, 1, 2, default" hl.env("HYPRCURSOR_THEME", "rose-pine-hyprcursor")
]; hl.env("HYPRCURSOR_SIZE", "${toString cfg.cursorSize}")
}; -- Route Qt6 apps (incl. the xdph screen-share picker) through qt6ct so
-- the noctalia-generated color scheme actually applies. Without this
-- var the qt6ct.conf is never read.
hl.env("QT_QPA_PLATFORMTHEME", "qt6ct")
env = [ -- autostart
"GDK_SCALE, 2.0" hl.on("hyprland.start", function()
"XCURSOR_SIZE, ${toString cfg.cursorSize}" -- Import the Qt theme var too, so the dbus/systemd-activated
"HYPRCURSOR_THEME, rose-pine-hyprcursor" -- xdg-desktop-portal-hyprland (and its share-picker) inherit it.
"HYPRCURSOR_SIZE, ${toString cfg.cursorSize}" hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME")
# Route Qt6 apps (incl. the xdph screen-share picker) through qt6ct so hl.exec_cmd("hyprctl setcursor Nordzy-cursors ${toString cfg.cursorSize}")
# the noctalia-generated color scheme actually applies. Without this hl.exec_cmd("hyprsunset")
# var the qt6ct.conf is never read. hl.exec_cmd("noctalia")
"QT_QPA_PLATFORMTHEME, qt6ct" hl.exec_cmd("yogurt --tray")
]; end)
"exec-once" = [ -- layer rules
# Import the Qt theme var too, so the dbus/systemd-activated hl.layer_rule({ match = { namespace = "noctalia-wallpaper" }, blur = true, ignore_alpha = 0.5 })
# xdg-desktop-portal-hyprland (and its share-picker) inherit it.
"dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME"
"hyprctl setcursor Nordzy-cursors ${toString cfg.cursorSize}"
"hyprsunset"
"noctalia"
"yogurt --tray"
];
layerrule = [ -- workspace rules
"blur 1, match:namespace noctalia-wallpaper" hl.workspace_rule({ workspace = "w[t1]", gaps_out = 0, gaps_in = 0 }) -- no gaps when only window
"ignore_alpha 0.5, match:namespace noctalia-wallpaper" hl.workspace_rule({ workspace = "w[tg1]", gaps_out = 0, gaps_in = 0 })
]; hl.workspace_rule({ workspace = "f[1]", gaps_out = 0, gaps_in = 0 })
workspace = [ hl.workspace_rule({ workspace = "special:terminal", on_created_empty = "[float; size 1400 1000; center 1] kitty", persistent = false })
# no gaps when only window hl.workspace_rule({ workspace = "special:yazi", on_created_empty = "[float; size ${cfg.yaziSize}; center 1] kitty --session sessions/yazi", persistent = false })
"w[t1], gapsout:0, gapsin:0"
"w[tg1], gapsout:0, gapsin:0"
"f[1], gapsout:0, gapsin:0"
"special:terminal, on-created-empty:[float; size 1400 1000; center 1] kitty, persistent:false" -- window rules
"special:yazi, on-created-empty:[float; size ${cfg.yaziSize}; center 1] kitty --session sessions/yazi, persistent:false" hl.window_rule({ match = { class = "com.gabm.satty" }, float = true })
];
windowrule = [ hl.window_rule({ match = { class = "imv" }, float = true, size = "1920 1080", move = "(cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5))" })
# satty hl.window_rule({ match = { class = "mpv" }, float = true, size = "1920 1080", move = "(cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5))" })
"float on, match:class com.gabm.satty"
# imv -- webcam overlay (`webcam` command) small, pinned, bottom-right.
"float on, match:class imv" -- Declared after the mpv class rule so its title match wins.
"size 1920 1080, match:class imv" hl.window_rule({
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class imv" match = { title = "webcam-overlay" },
float = true,
size = "360 240",
move = "100%-380 100%-260",
pin = true,
no_initial_focus = true,
border_size = 0,
})
# mpv hl.window_rule({ match = { class = "thunar" }, float = true, size = "1800 1200", move = "(cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5))" })
"float on, match:class mpv" hl.window_rule({ match = { class = [[\.blueman-manager-wrapped]] }, float = true, size = "1200 800", move = "(cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5))" })
"size 1920 1080, match:class mpv" hl.window_rule({ match = { class = [[org\.pulseaudio\.pavucontrol]] }, float = true, size = "1200 1200", move = "(cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5))" })
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class mpv"
# webcam overlay (`webcam` command) — small, pinned, bottom-right. -- wine / game installers (Inno Setup temp windows, e.g. Heroic/GOG).
# Title rules come after the mpv class rules so they win. -- Their class is the random "setup_*.tmp" filename, so match by suffix.
"float on, match:title webcam-overlay" hl.window_rule({ match = { class = [[.*\.tmp]] }, float = true, center = true })
"size 360 240, match:title webcam-overlay"
"move 100%-380 100%-260, match:title webcam-overlay"
"pin on, match:title webcam-overlay"
"no_initial_focus on, match:title webcam-overlay"
"border_size 0, match:title webcam-overlay"
# thunar -- metamask
"float on, match:class thunar" hl.window_rule({ match = { class = [[chrome-nkbihfbeogaeaoehlefnkodbefgpgknn-.*]] }, float = true })
"size 1800 1200, match:class thunar" -- bitwarden, chrome
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class thunar" hl.window_rule({ match = { class = [[chrome-nngceckbapebfimnlniiiahkandclblb-.*]] }, float = true })
-- claude for chrome
hl.window_rule({ match = { class = [[chrome-fcoeoabgfenejglbffodgkkbkcdhcgfn-.*]] }, float = true, move = "(cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5))" })
# bluetooth -- no gaps when only window
"float on, match:class \\.blueman-manager-wrapped" hl.window_rule({ match = { float = false, workspace = "w[t1]" }, border_size = 0, rounding = 0 })
"size 1200 800, match:class \\.blueman-manager-wrapped" hl.window_rule({ match = { float = false, workspace = "w[tg1]" }, border_size = 0, rounding = 0 })
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class \\.blueman-manager-wrapped" hl.window_rule({ match = { float = false, workspace = "f[1]" }, border_size = 0, rounding = 0 })
# pavucontrol hl.window_rule({ match = { workspace = "special:terminal" }, center = true })
"float on, match:class org\\.pulseaudio\\.pavucontrol" hl.window_rule({ match = { workspace = "special:yazi" }, center = true })
"size 1200 1200, match:class org\\.pulseaudio\\.pavucontrol"
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class org\\.pulseaudio\\.pavucontrol"
# wine / game installers (Inno Setup temp windows, e.g. Heroic/GOG). -- keybinds
# Their class is the random "setup_*.tmp" filename, so match by suffix. hl.bind(mod .. " + N", hl.dsp.exec_cmd("noctalia msg panel-toggle control-center"))
"float on, match:class .*\\.tmp" hl.bind(mod .. " + T", hl.dsp.exec_cmd("kitty"))
"center 1, match:class .*\\.tmp" hl.bind(mod .. " + V", hl.dsp.window.float({ action = "toggle" }))
hl.bind(mod .. " + Q", hl.dsp.window.close())
hl.bind(mod .. " + F", hl.dsp.window.fullscreen({ mode = "fullscreen" }))
hl.bind(mod .. " + SHIFT + F", hl.dsp.window.fullscreen({ mode = "maximized" }))
# metamask -- lock (routes through logind -> hypridle lock_cmd -> hyprlock)
"float on, match:class chrome-nkbihfbeogaeaoehlefnkodbefgpgknn-.*" hl.bind(mod .. " + CTRL + L", hl.dsp.exec_cmd("loginctl lock-session"))
# bitwarden, chrome hl.bind(mod .. " + space", hl.dsp.exec_cmd("noctalia msg panel-toggle launcher"))
"float on, match:class chrome-nngceckbapebfimnlniiiahkandclblb-.*"
# claude for chrome -- printscreen
"float on, match:class chrome-fcoeoabgfenejglbffodgkkbkcdhcgfn-.*" hl.bind("Print", hl.dsp.exec_cmd("hyprshot -m region --raw | satty --filename - --output-filename ~/downloads/screenshots/$(date +%Y-%m-%d_%H-%M-%S).png"))
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class chrome-fcoeoabgfenejglbffodgkkbkcdhcgfn-.*" hl.bind("SHIFT + Print", hl.dsp.exec_cmd("hyprshot -m window --raw | satty --filename - --output-filename ~/downloads/screenshots/$(date +%Y-%m-%d_%H-%M-%S).png"))
# no gaps when only window -- move focus with mod + hjkl
"border_size 0, match:float 0, match:workspace w[t1]" hl.bind(mod .. " + H", hl.dsp.focus({ direction = "left" }))
"rounding 0, match:float 0, match:workspace w[t1]" hl.bind(mod .. " + J", hl.dsp.focus({ direction = "down" }))
"border_size 0, match:float 0, match:workspace w[tg1]" hl.bind(mod .. " + K", hl.dsp.focus({ direction = "up" }))
"rounding 0, match:float 0, match:workspace w[tg1]" hl.bind(mod .. " + L", hl.dsp.focus({ direction = "right" }))
"border_size 0, match:float 0, match:workspace f[1]"
"rounding 0, match:float 0, match:workspace f[1]"
"center 1, match:workspace special:terminal" -- switch / move-to workspaces 1-6
"center 1, match:workspace special:yazi" for i = 1, 6 do
]; hl.bind(mod .. " + " .. i, hl.dsp.focus({ workspace = i }))
hl.bind(mod .. " + SHIFT + " .. i, hl.dsp.window.move({ workspace = i }))
end
"$mod" = "SUPER"; -- move active window inside workspace
hl.bind(mod .. " + SHIFT + H", hl.dsp.window.move({ direction = "left" }))
hl.bind(mod .. " + SHIFT + J", hl.dsp.window.move({ direction = "down" }))
hl.bind(mod .. " + SHIFT + K", hl.dsp.window.move({ direction = "up" }))
hl.bind(mod .. " + SHIFT + L", hl.dsp.window.move({ direction = "right" }))
bind = [ -- scroll through workspaces with mod + scroll
"$mod, n, exec, noctalia msg panel-toggle control-center" hl.bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
"$mod, t, exec, kitty" hl.bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
"$mod, v, togglefloating"
"$mod, q, killactive"
"$mod, f, fullscreen, 0"
"$mod SHIFT, f, fullscreen, 1"
# lock (routes through logind -> hypridle lock_cmd -> hyprlock) -- special workspaces (toggle + recenter in one handler)
"$mod CTRL, l, exec, loginctl lock-session" hl.bind(mod .. " + X", function()
hl.dispatch(hl.dsp.workspace.toggle_special("terminal"))
hl.dispatch(hl.dsp.window.center())
end)
hl.bind(mod .. " + E", function()
hl.dispatch(hl.dsp.workspace.toggle_special("yazi"))
hl.dispatch(hl.dsp.window.center())
end)
"$mod, space, exec, noctalia msg panel-toggle launcher" -- volume / brightness (locked so they work on the lock screen; repeat on hold)
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true, repeating = true })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl set 5%+"), { locked = true, repeating = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl set 5%-"), { locked = true, repeating = true })
# printscreen -- media keys (locked)
", Print, exec, hyprshot -m region --raw | satty --filename - --output-filename ~/downloads/screenshots/$(date +%Y-%m-%d_%H-%M-%S).png" hl.bind("XF86AudioMute", hl.dsp.exec_cmd("pactl set-sink-mute @DEFAULT_SINK@ toggle"), { locked = true })
"SHIFT, Print, exec, hyprshot -m window --raw | satty --filename - --output-filename ~/downloads/screenshots/$(date +%Y-%m-%d_%H-%M-%S).png" hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("volumectl -m toggle-mute"), { locked = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
# move focus with mod + arrows -- move/resize windows with mod + LMB/RMB drag
"$mod, h, movefocus, l" hl.bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
"$mod, j, movefocus, d" hl.bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
"$mod, k, movefocus, u" '';
"$mod, l, movefocus, r"
# Switch workspaces with mod + [0-9]
"$mod, 1, workspace, 1"
"$mod, 2, workspace, 2"
"$mod, 3, workspace, 3"
"$mod, 4, workspace, 4"
"$mod, 5, workspace, 5"
"$mod, 6, workspace, 6"
# Move active window to a workspace with mod + SHIFT + [0-9]
"$mod SHIFT, 1, movetoworkspace, 1"
"$mod SHIFT, 2, movetoworkspace, 2"
"$mod SHIFT, 3, movetoworkspace, 3"
"$mod SHIFT, 4, movetoworkspace, 4"
"$mod SHIFT, 5, movetoworkspace, 5"
"$mod SHIFT, 6, movetoworkspace, 6"
# Move active window inside workspace
"$mod SHIFT, h, movewindow, l"
"$mod SHIFT, j, movewindow, d"
"$mod SHIFT, k, movewindow, u"
"$mod SHIFT, l, movewindow, r"
# Scroll through existing workspaces with mod + scroll
"$mod, mouse_down, workspace, e+1"
"$mod, mouse_up, workspace, e-1"
# special workspaces
"$mod, x, togglespecialworkspace, terminal"
"$mod, x, centerwindow"
"$mod, e, togglespecialworkspace, yazi"
"$mod, e, centerwindow"
];
bindel = [
", XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"
", XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"
", XF86MonBrightnessUp, exec, brightnessctl set 5%+"
", XF86MonBrightnessDown, exec, brightnessctl set 5%-"
];
bindl = [
", XF86AudioMute, exec, pactl set-sink-mute @DEFAULT_SINK@ toggle"
", XF86AudioMicMute, exec, volumectl -m toggle-mute"
", XF86AudioNext, exec, playerctl next"
", XF86AudioPrev, exec, playerctl previous"
", XF86AudioPlay, exec, playerctl play-pause"
];
bindm = [
# Move/resize windows with mod + LMB/RMB and dragging
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
];
};
}; };
services.hyprpaper.enable = false; services.hyprpaper.enable = false;
@@ -90,19 +90,15 @@ in
home.file.".face".source = avatar; home.file.".face".source = avatar;
# force overwrite files that noctalia may have turned into regular files # force overwrite files that noctalia may have turned into regular files
xdg.configFile."hypr/hyprland.conf".force = true; # (Hyprland 0.55+ is Lua-only, so the managed file is now hyprland.lua)
xdg.configFile."hypr/hyprland.lua".force = true;
xdg.configFile."gtk-3.0/gtk.css".force = true; xdg.configFile."gtk-3.0/gtk.css".force = true;
# source noctalia-generated color theme in hyprland (v5 path). # NOTE: noctalia still writes its color theme to ~/.config/hypr/noctalia.conf
# Hyprland globs every source path, so a missing file errors as # in *hyprlang* syntax ($primary = rgb(...), general { col.active_border = ... }).
# "globbing error: found no match" — pre-create an empty placeholder # Hyprland 0.55+ dropped the hyprlang parser entirely, so that file can no
# so the first boot doesn't fail before noctalia has run. # longer be `source`d — there is no hl.source, and the lua config can't parse
home.activation.ensureNoctaliaHyprConf = lib.hm.dag.entryAfter [ "writeBoundary" ] '' # hyprlang. Hyprland therefore uses the static border colors from
run mkdir -p "$HOME/.config/hypr" # ../default.nix instead of noctalia's themed ones. To restore live theming,
run touch "$HOME/.config/hypr/noctalia.conf" # noctalia would need to emit a Lua snippet we can require() here.
'';
wayland.windowManager.hyprland.settings.source = [
"~/.config/hypr/noctalia.conf"
];
} }
+58 -68
View File
@@ -38,84 +38,74 @@
# 4K@1x monitors render the Electron AI apps tiny; scale their UI up. Tune to taste. # 4K@1x monitors render the Electron AI apps tiny; scale their UI up. Tune to taste.
custom.aiApps.deviceScaleFactor = "1.5"; custom.aiApps.deviceScaleFactor = "1.5";
wayland.windowManager.hyprland.settings = { wayland.windowManager.hyprland.extraConfig = ''
exec-once = [ hl.on("hyprland.start", function()
# boot-into-lock: paired with SDDM autologin (host config), lock the -- boot-into-lock: paired with SDDM autologin (host config), lock the
# session the moment Hyprland starts so a cold boot lands on hyprlock, not -- session the moment Hyprland starts so a cold boot lands on hyprlock, not
# an open desktop. A brief flash before it paints is possible. -- an open desktop. A brief flash before it paints is possible.
"hyprlock" hl.exec_cmd("hyprlock")
"$HOME/.local/bin/hyprpaper-rotate" hl.exec_cmd("$HOME/.local/bin/hyprpaper-rotate")
]; end)
windowrule = [ -- ethui-dev
# ethui-dev hl.window_rule({ match = { title = "ethui-dev.*" }, workspace = "2", no_initial_focus = true })
"workspace 2, match:title ethui-dev.*" hl.window_rule({ match = { title = "ethui-dev - dialog.*" }, float = true })
"no_initial_focus on, match:title ethui-dev.*"
"float on, match:title ethui-dev - dialog.*"
"workspace 1, match:title ethui.*" hl.window_rule({ match = { title = "ethui.*" }, workspace = "1", no_initial_focus = true })
"no_initial_focus on, match:title ethui.*" hl.window_rule({ match = { title = "ethui - dialog.*" }, float = true })
"float on, match:title ethui - dialog.*"
"workspace 1, match:title ^\\[bevy\\].*" hl.window_rule({ match = { title = [[^\[bevy\].*]] }, workspace = "1" })
"workspace 3 silent, match:class bevy-.*" hl.window_rule({ match = { class = "bevy-.*" }, workspace = "3 silent", no_initial_focus = true, fullscreen = true })
"no_initial_focus on, match:class bevy-.*"
"fullscreen on, match:class bevy-.*"
"workspace 1 silent, match:title egui-.*" hl.window_rule({
"border_size 0, match:title egui-.*" match = { title = "egui-.*" },
"float on, match:title egui-.*" workspace = "1 silent",
"no_blur on, match:title egui-.*" border_size = 0,
"move 100%-w-20 100%-h-20, match:title egui-.*" float = true,
no_blur = true,
move = "100%-w-20 100%-h-20",
})
# gaming: Steam/Proton titles run under XWayland with WM_CLASS -- gaming: Steam/Proton titles run under XWayland with WM_CLASS
# steam_app_<appid>. Send them fullscreen to workspace 3 (DP-1) — the same -- steam_app_<appid>. Send them fullscreen to workspace 3 (DP-1) the same
# streamed workspace as Big Picture below — instead of opening tiled on -- streamed workspace as Big Picture below instead of opening tiled on
# whatever monitor Steam is on. -- whatever monitor Steam is on.
# Native (non-Proton) games have their own class — grab it with -- Native (non-Proton) games have their own class grab it with
# `hyprctl clients | grep -iE "class|title"` while the game runs and add a -- `hyprctl clients | grep -iE "class|title"` while the game runs and add a
# matching line here. -- matching rule here.
"workspace 3, match:class steam_app_.*" hl.window_rule({ match = { class = "steam_app_.*" }, workspace = "3", fullscreen = true })
"fullscreen on, match:class steam_app_.*"
# Steam Big Picture shares the plain `steam` WM_CLASS with the normal -- Steam Big Picture shares the plain `steam` WM_CLASS with the normal
# client, so match its title instead. Without this it opens on whatever -- client, so match its title instead. Without this it opens on whatever
# monitor the cursor is on, not the streamed one. Pin it to ws3 — DP-1's -- monitor the cursor is on, not the streamed one. Pin it to ws3 DP-1's
# normal persistent workspace, which is what the stream captures. `.` -- normal persistent workspace, which is what the stream captures. `.`
# stands in for the literal spaces to keep the rule string unambiguous. -- stands in for the literal spaces to keep the rule string unambiguous.
"workspace 3, match:title Steam.Big.Picture.Mode" hl.window_rule({ match = { title = "Steam.Big.Picture.Mode" }, workspace = "3", fullscreen = true })
"fullscreen on, match:title Steam.Big.Picture.Mode"
# Heroic (GOG/Epic launcher) streamed via Moonlight — `heroic --console`. -- Heroic (GOG/Epic launcher) streamed via Moonlight `heroic --console`.
# Tile it on ws3 (DP-1, the captured screen) rather than letting it float -- Tile it on ws3 (DP-1, the captured screen) rather than letting it float
# or land on whatever monitor the cursor is on. Games it launches are -- or land on whatever monitor the cursor is on. Games it launches are
# Proton titles → caught by the steam_app_ rules above; native ones need -- Proton titles caught by the steam_app_ rules above; native ones need
# their own class added here. -- their own class added here.
"workspace 3, match:class heroic" hl.window_rule({ match = { class = "heroic" }, workspace = "3", tile = true })
"tile on, match:class heroic"
# RetroArch streamed via Moonlight (also registered as a Sunshine app). -- RetroArch streamed via Moonlight (also registered as a Sunshine app).
# Native Wayland app_id is `com.libretro.RetroArch` (set at map time) — the -- Native Wayland app_id is `com.libretro.RetroArch` (set at map time) the
# exact string is needed so the rule applies on spawn; a loose `retroarch` -- exact string is needed so the rule applies on spawn; a loose `retroarch`
# match misses at map and the window flashes onto the active workspace -- match misses at map and the window flashes onto the active workspace
# first. `silent` sends it to ws3 (DP-1, the captured screen) without -- first. `silent` sends it to ws3 (DP-1, the captured screen) without
# yanking focus off whatever workspace you're driving from. -- yanking focus off whatever workspace you're driving from.
"workspace 3 silent, match:class com.libretro.RetroArch" hl.window_rule({ match = { class = "com.libretro.RetroArch" }, workspace = "3 silent", fullscreen = true })
"fullscreen on, match:class com.libretro.RetroArch"
];
render = { -- Experiment: was `true`. With a fullscreen game direct-scanning-out,
# Experiment: was `true`. With a fullscreen game direct-scanning-out, -- Sunshine starting a capture forces the compositor off the direct path,
# Sunshine starting a capture forces the compositor off the direct path, -- and that surface/context churn is a suspected trigger for Chromium's
# and that surface/context churn is a suspected trigger for Chromium's -- `EGL_CONTEXT_LOST` crash (Big Picture drops to software rendering mid-
# `EGL_CONTEXT_LOST` crash (Big Picture drops to software rendering mid- -- stream). Disabled to test whether the crashes stop. If it makes no
# stream). Disabled to test whether the crashes stop. If it makes no -- difference, flip back to `true` for the local fullscreen latency win.
# difference, flip back to `true` for the local fullscreen latency win. hl.config({ render = { direct_scanout = false } })
direct_scanout = false; '';
};
};
programs.noctalia.settings = { programs.noctalia.settings = {
shell.ui_scale = 1.25; shell.ui_scale = 1.25;
+16 -17
View File
@@ -1,20 +1,19 @@
_: _:
{ {
wayland.windowManager.hyprland.settings = { # Hyprland 0.55+ is Lua-only (see home/common/programs/hyprland); monitors and
monitor = [ # workspace rules are expressed with the hl.* API in raw Lua.
# List HDMI-A-1 first so it becomes the primary monitor (ID 0) wayland.windowManager.hyprland.extraConfig = ''
"HDMI-A-1, 3840x2160, 0x2160, 1" -- monitors (HDMI-A-1 listed first so it becomes the primary, ID 0)
# Then the other monitors hl.monitor({ output = "HDMI-A-1", mode = "3840x2160", position = "0x2160", scale = 1 })
"DP-2, 3840x2160, 3840x180, 1, transform, 1" hl.monitor({ output = "DP-2", mode = "3840x2160", position = "3840x180", scale = 1, transform = 1 })
"DP-1, 3840x2160, 0x0, 1" hl.monitor({ output = "DP-1", mode = "3840x2160", position = "0x0", scale = 1 })
];
workspace = [ -- workspace -> monitor bindings
"1, monitor:HDMI-A-1, default:true, persistent:true" # bottom-left (primary) hl.workspace_rule({ workspace = "1", monitor = "HDMI-A-1", default = true, persistent = true }) -- bottom-left (primary)
"2, monitor:DP-2, persistent:true" # right (vertical) hl.workspace_rule({ workspace = "2", monitor = "DP-2", persistent = true }) -- right (vertical)
"3, monitor:DP-1, persistent:true" # top-left hl.workspace_rule({ workspace = "3", monitor = "DP-1", persistent = true }) -- top-left
"4, monitor:HDMI-A-1" # bottom-left hl.workspace_rule({ workspace = "4", monitor = "HDMI-A-1" })
"5, monitor:DP-2" # right (vertical) hl.workspace_rule({ workspace = "5", monitor = "DP-2" })
"6, monitor:DP-1" # top-left hl.workspace_rule({ workspace = "6", monitor = "DP-1" })
]; '';
};
} }
+5
View File
@@ -6,6 +6,11 @@
settings = { settings = {
General = { General = {
Enable = "Source,Sink,Media,Socket"; Enable = "Source,Sink,Media,Socket";
# Required for LE Audio (BAP): ISO sockets / CIS live behind
# BlueZ's experimental gate. Without this the XM6 falls back to
# classic A2DP, and mic use forces the HSP/HFP downgrade.
Experimental = true;
KernelExperimental = true;
}; };
}; };
}; };
@@ -13,6 +13,14 @@
# GTK/GNOME services still useful on Wayland # GTK/GNOME services still useful on Wayland
programs.dconf.enable = true; programs.dconf.enable = true;
# Route the Secret portal (org.freedesktop.portal.Secret) to gnome-keyring.
# Hyprland's portal doesn't implement Secret, and the preferred backend list
# (hyprland;gtk) has no Secret provider — so without this, the portal exposes
# no Secret interface at all. Electron apps (e.g. Claude Desktop) fetch their
# token-encryption key through this portal; when it's missing, os_crypt init
# fails (prev_init_success:false) and they can't persist a login across boots.
xdg.portal.config.common."org.freedesktop.impl.portal.Secret" = "gnome-keyring";
services = { services = {
# thumbnail support for images # thumbnail support for images
tumbler.enable = true; tumbler.enable = true;
+25
View File
@@ -7,5 +7,30 @@
alsa.enable = true; alsa.enable = true;
alsa.support32Bit = true; alsa.support32Bit = true;
pulse.enable = true; pulse.enable = true;
# Disable the LC3-SWB HFP codec. BlueZ 5.86's experimental LC3 super-wideband
# headset path (lit up by hardware.bluetooth Experimental=true) is broken for
# the Sony WF-1000XM6: when a call opens the mic, WirePlumber auto-switches to
# the highest-priority headset profile (LC3-SWB), the audio link fails to set
# up ("Hands-Free Voice gateway: Transport endpoint is not connected"), and the
# buds reset with no call audio/mic. Omitting lc3_swb from the codec allow-list
# makes autoswitch fall back to mSBC, which is stable wideband voice. Everything
# else (A2DP LDAC/AAC/SBC, and lc3 for any future LE Audio) stays enabled.
wireplumber.extraConfig."51-disable-lc3swb-hfp" = {
"monitor.bluez.properties" = {
"bluez5.codecs" = [
"sbc"
"sbc_xq"
"aac"
"aptx"
"aptx_hd"
"aptx_ll"
"ldac"
"lc3"
"cvsd"
"msbc"
];
};
};
}; };
} }