This commit is contained in:
+13
-17
@@ -26,24 +26,20 @@
|
||||
# RetroArch (bare — cores/ROMs added by hand, WebDAV configured in-app).
|
||||
custom.gaming.retroarch = true;
|
||||
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
exec-once = [
|
||||
];
|
||||
wayland.windowManager.hyprland.extraConfig = ''
|
||||
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 = [
|
||||
"1, monitor:DP-3"
|
||||
"2, monitor:eDP-1"
|
||||
"3, monitor:eDP-1"
|
||||
"4, monitor:eDP-1"
|
||||
];
|
||||
|
||||
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-.*"
|
||||
];
|
||||
};
|
||||
hl.window_rule({
|
||||
match = { class = "bevy-.*" },
|
||||
no_initial_focus = true,
|
||||
float = true,
|
||||
size = "800 600",
|
||||
move = "100%-800 100%-600",
|
||||
})
|
||||
'';
|
||||
|
||||
# v4 `bar.density` has no v5 equivalent; spacing is tuned via
|
||||
# bar.main.padding / widget_spacing / thickness if needed.
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
_:
|
||||
{
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
monitor = [
|
||||
"eDP-1, preferred, 0x0, 1, bitdepth, 8"
|
||||
"DP-1, preferred, auto-up, 1"
|
||||
"DP-2, preferred, auto-up, 1"
|
||||
"DP-3, preferred, auto-up, 1"
|
||||
"DP-4, preferred, auto-up, 1"
|
||||
];
|
||||
};
|
||||
# Hyprland 0.55+ is Lua-only (see home/common/programs/hyprland).
|
||||
wayland.windowManager.hyprland.extraConfig = ''
|
||||
hl.monitor({ output = "eDP-1", mode = "preferred", position = "0x0", scale = 1, bitdepth = 8 })
|
||||
hl.monitor({ output = "DP-1", mode = "preferred", position = "auto-up", scale = 1 })
|
||||
hl.monitor({ output = "DP-2", mode = "preferred", position = "auto-up", scale = 1 })
|
||||
hl.monitor({ output = "DP-3", mode = "preferred", position = "auto-up", scale = 1 })
|
||||
hl.monitor({ output = "DP-4", mode = "preferred", position = "auto-up", scale = 1 })
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -66,260 +66,214 @@ in
|
||||
enable = true;
|
||||
package = null;
|
||||
portalPackage = null;
|
||||
# Keep the hyprlang config format (the new default is "lua"); our
|
||||
# settings are written as hyprlang.
|
||||
configType = "hyprlang";
|
||||
# Hyprland 0.55+ removed the hyprlang parser entirely — the config format
|
||||
# is now Lua only (see home/common/programs/hyprland — the whole config
|
||||
# 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 = [ ];
|
||||
settings = {
|
||||
plugin = {
|
||||
overview = {
|
||||
autoDrag = true;
|
||||
exitOnClick = true;
|
||||
exitOnSwitch = true;
|
||||
showNewWorkspace = false;
|
||||
showEmptyWorkspace = false;
|
||||
};
|
||||
};
|
||||
input = {
|
||||
kb_options = "ctrl:nocaps";
|
||||
repeat_delay = 150;
|
||||
extraConfig = ''
|
||||
local mod = "SUPER"
|
||||
|
||||
touchpad = {
|
||||
natural_scroll = "yes";
|
||||
};
|
||||
numlock_by_default = true;
|
||||
};
|
||||
-- look & feel / behaviour
|
||||
hl.config({
|
||||
general = {
|
||||
border_size = 1,
|
||||
gaps_in = 0,
|
||||
gaps_out = 0,
|
||||
snap = {
|
||||
enabled = true,
|
||||
border_overlap = true,
|
||||
},
|
||||
col = {
|
||||
inactive_border = "0x99999999",
|
||||
active_border = "0x99999999",
|
||||
},
|
||||
},
|
||||
|
||||
cursor = {
|
||||
no_hardware_cursors = "yes";
|
||||
# Auto-hide the pointer after 3s of no movement so a parked cursor
|
||||
# doesn't sit on top of a game (visible in the stream too). It
|
||||
# reappears the instant the mouse moves.
|
||||
inactive_timeout = 3;
|
||||
};
|
||||
input = {
|
||||
kb_options = "ctrl:nocaps",
|
||||
repeat_delay = 150,
|
||||
touchpad = {
|
||||
natural_scroll = true,
|
||||
},
|
||||
numlock_by_default = true,
|
||||
},
|
||||
|
||||
general = {
|
||||
border_size = 1;
|
||||
gaps_in = 0;
|
||||
gaps_out = 0;
|
||||
snap = {
|
||||
enabled = true;
|
||||
border_overlap = true;
|
||||
};
|
||||
cursor = {
|
||||
no_hardware_cursors = true,
|
||||
-- Auto-hide the pointer after 3s of no movement so a parked cursor
|
||||
-- doesn't sit on top of a game (visible in the stream too). It
|
||||
-- reappears the instant the mouse moves.
|
||||
inactive_timeout = 3,
|
||||
},
|
||||
|
||||
"col.inactive_border" = "0x99999999";
|
||||
"col.active_border" = "0x99999999";
|
||||
};
|
||||
misc = {
|
||||
disable_hyprland_logo = true,
|
||||
disable_splash_rendering = true,
|
||||
on_focus_under_fullscreen = 2,
|
||||
},
|
||||
|
||||
misc = {
|
||||
disable_hyprland_logo = true;
|
||||
disable_splash_rendering = true;
|
||||
on_focus_under_fullscreen = 2;
|
||||
};
|
||||
ecosystem = {
|
||||
no_update_news = true,
|
||||
},
|
||||
|
||||
ecosystem = {
|
||||
no_update_news = true;
|
||||
};
|
||||
xwayland = {
|
||||
force_zero_scaling = true,
|
||||
},
|
||||
|
||||
xwayland = {
|
||||
force_zero_scaling = true;
|
||||
};
|
||||
decoration = {
|
||||
blur = {
|
||||
enabled = true,
|
||||
popups = false,
|
||||
},
|
||||
shadow = {
|
||||
enabled = false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
decoration = {
|
||||
blur = {
|
||||
enabled = true;
|
||||
popups = false;
|
||||
};
|
||||
shadow = {
|
||||
enabled = false;
|
||||
};
|
||||
};
|
||||
-- keep default animations, but speed them all up
|
||||
hl.animation({ leaf = "global", enabled = true, speed = 2, bezier = "default" })
|
||||
|
||||
# keep default animations, but speed them all up
|
||||
animations = {
|
||||
animation = [
|
||||
"global, 1, 2, default"
|
||||
];
|
||||
};
|
||||
-- environment
|
||||
hl.env("GDK_SCALE", "2.0")
|
||||
hl.env("XCURSOR_SIZE", "${toString cfg.cursorSize}")
|
||||
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 = [
|
||||
"GDK_SCALE, 2.0"
|
||||
"XCURSOR_SIZE, ${toString cfg.cursorSize}"
|
||||
"HYPRCURSOR_THEME, rose-pine-hyprcursor"
|
||||
"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.
|
||||
"QT_QPA_PLATFORMTHEME, qt6ct"
|
||||
];
|
||||
-- autostart
|
||||
hl.on("hyprland.start", function()
|
||||
-- Import the Qt theme var too, so the dbus/systemd-activated
|
||||
-- xdg-desktop-portal-hyprland (and its share-picker) inherit it.
|
||||
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME")
|
||||
hl.exec_cmd("hyprctl setcursor Nordzy-cursors ${toString cfg.cursorSize}")
|
||||
hl.exec_cmd("hyprsunset")
|
||||
hl.exec_cmd("noctalia")
|
||||
hl.exec_cmd("yogurt --tray")
|
||||
end)
|
||||
|
||||
"exec-once" = [
|
||||
# Import the Qt theme var too, so the dbus/systemd-activated
|
||||
# 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"
|
||||
];
|
||||
-- layer rules
|
||||
hl.layer_rule({ match = { namespace = "noctalia-wallpaper" }, blur = true, ignore_alpha = 0.5 })
|
||||
|
||||
layerrule = [
|
||||
"blur 1, match:namespace noctalia-wallpaper"
|
||||
"ignore_alpha 0.5, match:namespace noctalia-wallpaper"
|
||||
];
|
||||
workspace = [
|
||||
# no gaps when only window
|
||||
"w[t1], gapsout:0, gapsin:0"
|
||||
"w[tg1], gapsout:0, gapsin:0"
|
||||
"f[1], gapsout:0, gapsin:0"
|
||||
-- workspace rules
|
||||
hl.workspace_rule({ workspace = "w[t1]", gaps_out = 0, gaps_in = 0 }) -- no gaps when only window
|
||||
hl.workspace_rule({ workspace = "w[tg1]", gaps_out = 0, gaps_in = 0 })
|
||||
hl.workspace_rule({ workspace = "f[1]", gaps_out = 0, gaps_in = 0 })
|
||||
hl.workspace_rule({ workspace = "special:terminal", on_created_empty = "[float; size 1400 1000; center 1] kitty", persistent = false })
|
||||
hl.workspace_rule({ workspace = "special:yazi", on_created_empty = "[float; size ${cfg.yaziSize}; center 1] kitty --session sessions/yazi", persistent = false })
|
||||
|
||||
"special:terminal, on-created-empty:[float; size 1400 1000; center 1] kitty, persistent:false"
|
||||
"special:yazi, on-created-empty:[float; size ${cfg.yaziSize}; center 1] kitty --session sessions/yazi, persistent:false"
|
||||
];
|
||||
-- window rules
|
||||
hl.window_rule({ match = { class = "com.gabm.satty" }, float = true })
|
||||
|
||||
windowrule = [
|
||||
# satty
|
||||
"float on, match:class com.gabm.satty"
|
||||
hl.window_rule({ match = { class = "imv" }, float = true, size = "1920 1080", move = "(cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5))" })
|
||||
hl.window_rule({ match = { class = "mpv" }, float = true, size = "1920 1080", move = "(cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5))" })
|
||||
|
||||
# imv
|
||||
"float on, match:class imv"
|
||||
"size 1920 1080, match:class imv"
|
||||
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class imv"
|
||||
-- webcam overlay (`webcam` command) — small, pinned, bottom-right.
|
||||
-- Declared after the mpv class rule so its title match wins.
|
||||
hl.window_rule({
|
||||
match = { title = "webcam-overlay" },
|
||||
float = true,
|
||||
size = "360 240",
|
||||
move = "100%-380 100%-260",
|
||||
pin = true,
|
||||
no_initial_focus = true,
|
||||
border_size = 0,
|
||||
})
|
||||
|
||||
# mpv
|
||||
"float on, match:class mpv"
|
||||
"size 1920 1080, match:class mpv"
|
||||
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class 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))" })
|
||||
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))" })
|
||||
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))" })
|
||||
|
||||
# webcam overlay (`webcam` command) — small, pinned, bottom-right.
|
||||
# Title rules come after the mpv class rules so they win.
|
||||
"float on, match:title webcam-overlay"
|
||||
"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"
|
||||
-- wine / game installers (Inno Setup temp windows, e.g. Heroic/GOG).
|
||||
-- Their class is the random "setup_*.tmp" filename, so match by suffix.
|
||||
hl.window_rule({ match = { class = [[.*\.tmp]] }, float = true, center = true })
|
||||
|
||||
# thunar
|
||||
"float on, match:class thunar"
|
||||
"size 1800 1200, match:class thunar"
|
||||
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class thunar"
|
||||
-- metamask
|
||||
hl.window_rule({ match = { class = [[chrome-nkbihfbeogaeaoehlefnkodbefgpgknn-.*]] }, float = true })
|
||||
-- bitwarden, chrome
|
||||
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
|
||||
"float on, match:class \\.blueman-manager-wrapped"
|
||||
"size 1200 800, match:class \\.blueman-manager-wrapped"
|
||||
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class \\.blueman-manager-wrapped"
|
||||
-- no gaps when only window
|
||||
hl.window_rule({ match = { float = false, workspace = "w[t1]" }, border_size = 0, rounding = 0 })
|
||||
hl.window_rule({ match = { float = false, workspace = "w[tg1]" }, border_size = 0, rounding = 0 })
|
||||
hl.window_rule({ match = { float = false, workspace = "f[1]" }, border_size = 0, rounding = 0 })
|
||||
|
||||
# pavucontrol
|
||||
"float on, match:class org\\.pulseaudio\\.pavucontrol"
|
||||
"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"
|
||||
hl.window_rule({ match = { workspace = "special:terminal" }, center = true })
|
||||
hl.window_rule({ match = { workspace = "special:yazi" }, center = true })
|
||||
|
||||
# wine / game installers (Inno Setup temp windows, e.g. Heroic/GOG).
|
||||
# Their class is the random "setup_*.tmp" filename, so match by suffix.
|
||||
"float on, match:class .*\\.tmp"
|
||||
"center 1, match:class .*\\.tmp"
|
||||
-- keybinds
|
||||
hl.bind(mod .. " + N", hl.dsp.exec_cmd("noctalia msg panel-toggle control-center"))
|
||||
hl.bind(mod .. " + T", hl.dsp.exec_cmd("kitty"))
|
||||
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
|
||||
"float on, match:class chrome-nkbihfbeogaeaoehlefnkodbefgpgknn-.*"
|
||||
-- lock (routes through logind -> hypridle lock_cmd -> hyprlock)
|
||||
hl.bind(mod .. " + CTRL + L", hl.dsp.exec_cmd("loginctl lock-session"))
|
||||
|
||||
# bitwarden, chrome
|
||||
"float on, match:class chrome-nngceckbapebfimnlniiiahkandclblb-.*"
|
||||
hl.bind(mod .. " + space", hl.dsp.exec_cmd("noctalia msg panel-toggle launcher"))
|
||||
|
||||
# claude for chrome
|
||||
"float on, match:class chrome-fcoeoabgfenejglbffodgkkbkcdhcgfn-.*"
|
||||
"move (cursor_x-(window_w*0.5)) (cursor_y-(window_h*0.5)), match:class chrome-fcoeoabgfenejglbffodgkkbkcdhcgfn-.*"
|
||||
-- printscreen
|
||||
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"))
|
||||
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
|
||||
"border_size 0, match:float 0, match:workspace w[t1]"
|
||||
"rounding 0, match:float 0, match:workspace w[t1]"
|
||||
"border_size 0, match:float 0, match:workspace w[tg1]"
|
||||
"rounding 0, match:float 0, match:workspace w[tg1]"
|
||||
"border_size 0, match:float 0, match:workspace f[1]"
|
||||
"rounding 0, match:float 0, match:workspace f[1]"
|
||||
-- move focus with mod + hjkl
|
||||
hl.bind(mod .. " + H", hl.dsp.focus({ direction = "left" }))
|
||||
hl.bind(mod .. " + J", hl.dsp.focus({ direction = "down" }))
|
||||
hl.bind(mod .. " + K", hl.dsp.focus({ direction = "up" }))
|
||||
hl.bind(mod .. " + L", hl.dsp.focus({ direction = "right" }))
|
||||
|
||||
"center 1, match:workspace special:terminal"
|
||||
"center 1, match:workspace special:yazi"
|
||||
];
|
||||
-- switch / move-to workspaces 1-6
|
||||
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 = [
|
||||
"$mod, n, exec, noctalia msg panel-toggle control-center"
|
||||
"$mod, t, exec, kitty"
|
||||
"$mod, v, togglefloating"
|
||||
"$mod, q, killactive"
|
||||
"$mod, f, fullscreen, 0"
|
||||
"$mod SHIFT, f, fullscreen, 1"
|
||||
-- scroll through workspaces with mod + scroll
|
||||
hl.bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
|
||||
hl.bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
|
||||
|
||||
# lock (routes through logind -> hypridle lock_cmd -> hyprlock)
|
||||
"$mod CTRL, l, exec, loginctl lock-session"
|
||||
-- special workspaces (toggle + recenter in one handler)
|
||||
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
|
||||
", Print, exec, hyprshot -m region --raw | satty --filename - --output-filename ~/downloads/screenshots/$(date +%Y-%m-%d_%H-%M-%S).png"
|
||||
"SHIFT, Print, exec, hyprshot -m window --raw | satty --filename - --output-filename ~/downloads/screenshots/$(date +%Y-%m-%d_%H-%M-%S).png"
|
||||
-- media keys (locked)
|
||||
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("pactl set-sink-mute @DEFAULT_SINK@ toggle"), { locked = true })
|
||||
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
|
||||
"$mod, h, movefocus, l"
|
||||
"$mod, j, movefocus, d"
|
||||
"$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"
|
||||
];
|
||||
};
|
||||
-- move/resize windows with mod + LMB/RMB drag
|
||||
hl.bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
|
||||
hl.bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
|
||||
'';
|
||||
};
|
||||
|
||||
services.hyprpaper.enable = false;
|
||||
|
||||
@@ -90,19 +90,15 @@ in
|
||||
home.file.".face".source = avatar;
|
||||
|
||||
# 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;
|
||||
|
||||
# source noctalia-generated color theme in hyprland (v5 path).
|
||||
# Hyprland globs every source path, so a missing file errors as
|
||||
# "globbing error: found no match" — pre-create an empty placeholder
|
||||
# so the first boot doesn't fail before noctalia has run.
|
||||
home.activation.ensureNoctaliaHyprConf = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
run mkdir -p "$HOME/.config/hypr"
|
||||
run touch "$HOME/.config/hypr/noctalia.conf"
|
||||
'';
|
||||
|
||||
wayland.windowManager.hyprland.settings.source = [
|
||||
"~/.config/hypr/noctalia.conf"
|
||||
];
|
||||
# NOTE: noctalia still writes its color theme to ~/.config/hypr/noctalia.conf
|
||||
# in *hyprlang* syntax ($primary = rgb(...), general { col.active_border = ... }).
|
||||
# Hyprland 0.55+ dropped the hyprlang parser entirely, so that file can no
|
||||
# longer be `source`d — there is no hl.source, and the lua config can't parse
|
||||
# hyprlang. Hyprland therefore uses the static border colors from
|
||||
# ../default.nix instead of noctalia's themed ones. To restore live theming,
|
||||
# noctalia would need to emit a Lua snippet we can require() here.
|
||||
}
|
||||
|
||||
+58
-68
@@ -38,84 +38,74 @@
|
||||
# 4K@1x monitors render the Electron AI apps tiny; scale their UI up. Tune to taste.
|
||||
custom.aiApps.deviceScaleFactor = "1.5";
|
||||
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
exec-once = [
|
||||
# boot-into-lock: paired with SDDM autologin (host config), lock the
|
||||
# session the moment Hyprland starts so a cold boot lands on hyprlock, not
|
||||
# an open desktop. A brief flash before it paints is possible.
|
||||
"hyprlock"
|
||||
"$HOME/.local/bin/hyprpaper-rotate"
|
||||
];
|
||||
wayland.windowManager.hyprland.extraConfig = ''
|
||||
hl.on("hyprland.start", function()
|
||||
-- boot-into-lock: paired with SDDM autologin (host config), lock the
|
||||
-- session the moment Hyprland starts so a cold boot lands on hyprlock, not
|
||||
-- an open desktop. A brief flash before it paints is possible.
|
||||
hl.exec_cmd("hyprlock")
|
||||
hl.exec_cmd("$HOME/.local/bin/hyprpaper-rotate")
|
||||
end)
|
||||
|
||||
windowrule = [
|
||||
# ethui-dev
|
||||
"workspace 2, match:title ethui-dev.*"
|
||||
"no_initial_focus on, match:title ethui-dev.*"
|
||||
"float on, match:title ethui-dev - dialog.*"
|
||||
-- ethui-dev
|
||||
hl.window_rule({ match = { title = "ethui-dev.*" }, workspace = "2", no_initial_focus = true })
|
||||
hl.window_rule({ match = { title = "ethui-dev - dialog.*" }, float = true })
|
||||
|
||||
"workspace 1, match:title ethui.*"
|
||||
"no_initial_focus on, match:title ethui.*"
|
||||
"float on, match:title ethui - dialog.*"
|
||||
hl.window_rule({ match = { title = "ethui.*" }, workspace = "1", no_initial_focus = true })
|
||||
hl.window_rule({ match = { title = "ethui - dialog.*" }, float = true })
|
||||
|
||||
"workspace 1, match:title ^\\[bevy\\].*"
|
||||
hl.window_rule({ match = { title = [[^\[bevy\].*]] }, workspace = "1" })
|
||||
|
||||
"workspace 3 silent, match:class bevy-.*"
|
||||
"no_initial_focus on, match:class bevy-.*"
|
||||
"fullscreen on, match:class bevy-.*"
|
||||
hl.window_rule({ match = { class = "bevy-.*" }, workspace = "3 silent", no_initial_focus = true, fullscreen = true })
|
||||
|
||||
"workspace 1 silent, match:title egui-.*"
|
||||
"border_size 0, match:title egui-.*"
|
||||
"float on, match:title egui-.*"
|
||||
"no_blur on, match:title egui-.*"
|
||||
"move 100%-w-20 100%-h-20, match:title egui-.*"
|
||||
hl.window_rule({
|
||||
match = { title = "egui-.*" },
|
||||
workspace = "1 silent",
|
||||
border_size = 0,
|
||||
float = true,
|
||||
no_blur = true,
|
||||
move = "100%-w-20 100%-h-20",
|
||||
})
|
||||
|
||||
# gaming: Steam/Proton titles run under XWayland with WM_CLASS
|
||||
# steam_app_<appid>. Send them fullscreen to workspace 3 (DP-1) — the same
|
||||
# streamed workspace as Big Picture below — instead of opening tiled on
|
||||
# whatever monitor Steam is on.
|
||||
# Native (non-Proton) games have their own class — grab it with
|
||||
# `hyprctl clients | grep -iE "class|title"` while the game runs and add a
|
||||
# matching line here.
|
||||
"workspace 3, match:class steam_app_.*"
|
||||
"fullscreen on, match:class steam_app_.*"
|
||||
-- gaming: Steam/Proton titles run under XWayland with WM_CLASS
|
||||
-- steam_app_<appid>. Send them fullscreen to workspace 3 (DP-1) — the same
|
||||
-- streamed workspace as Big Picture below — instead of opening tiled on
|
||||
-- whatever monitor Steam is on.
|
||||
-- Native (non-Proton) games have their own class — grab it with
|
||||
-- `hyprctl clients | grep -iE "class|title"` while the game runs and add a
|
||||
-- matching rule here.
|
||||
hl.window_rule({ match = { class = "steam_app_.*" }, workspace = "3", fullscreen = true })
|
||||
|
||||
# Steam Big Picture shares the plain `steam` WM_CLASS with the normal
|
||||
# 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
|
||||
# normal persistent workspace, which is what the stream captures. `.`
|
||||
# stands in for the literal spaces to keep the rule string unambiguous.
|
||||
"workspace 3, match:title Steam.Big.Picture.Mode"
|
||||
"fullscreen on, match:title Steam.Big.Picture.Mode"
|
||||
-- Steam Big Picture shares the plain `steam` WM_CLASS with the normal
|
||||
-- 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
|
||||
-- normal persistent workspace, which is what the stream captures. `.`
|
||||
-- stands in for the literal spaces to keep the rule string unambiguous.
|
||||
hl.window_rule({ match = { title = "Steam.Big.Picture.Mode" }, workspace = "3", fullscreen = true })
|
||||
|
||||
# Heroic (GOG/Epic launcher) streamed via Moonlight — `heroic --console`.
|
||||
# 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
|
||||
# Proton titles → caught by the steam_app_ rules above; native ones need
|
||||
# their own class added here.
|
||||
"workspace 3, match:class heroic"
|
||||
"tile on, match:class heroic"
|
||||
-- Heroic (GOG/Epic launcher) streamed via Moonlight — `heroic --console`.
|
||||
-- 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
|
||||
-- Proton titles → caught by the steam_app_ rules above; native ones need
|
||||
-- their own class added here.
|
||||
hl.window_rule({ match = { class = "heroic" }, workspace = "3", tile = true })
|
||||
|
||||
# RetroArch streamed via Moonlight (also registered as a Sunshine app).
|
||||
# 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`
|
||||
# match misses at map and the window flashes onto the active workspace
|
||||
# first. `silent` sends it to ws3 (DP-1, the captured screen) without
|
||||
# yanking focus off whatever workspace you're driving from.
|
||||
"workspace 3 silent, match:class com.libretro.RetroArch"
|
||||
"fullscreen on, match:class com.libretro.RetroArch"
|
||||
];
|
||||
-- RetroArch streamed via Moonlight (also registered as a Sunshine app).
|
||||
-- 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`
|
||||
-- match misses at map and the window flashes onto the active workspace
|
||||
-- first. `silent` sends it to ws3 (DP-1, the captured screen) without
|
||||
-- yanking focus off whatever workspace you're driving from.
|
||||
hl.window_rule({ match = { class = "com.libretro.RetroArch" }, workspace = "3 silent", fullscreen = true })
|
||||
|
||||
render = {
|
||||
# Experiment: was `true`. With a fullscreen game direct-scanning-out,
|
||||
# Sunshine starting a capture forces the compositor off the direct path,
|
||||
# and that surface/context churn is a suspected trigger for Chromium's
|
||||
# `EGL_CONTEXT_LOST` crash (Big Picture drops to software rendering mid-
|
||||
# stream). Disabled to test whether the crashes stop. If it makes no
|
||||
# difference, flip back to `true` for the local fullscreen latency win.
|
||||
direct_scanout = false;
|
||||
};
|
||||
|
||||
};
|
||||
-- Experiment: was `true`. With a fullscreen game direct-scanning-out,
|
||||
-- Sunshine starting a capture forces the compositor off the direct path,
|
||||
-- and that surface/context churn is a suspected trigger for Chromium's
|
||||
-- `EGL_CONTEXT_LOST` crash (Big Picture drops to software rendering mid-
|
||||
-- stream). Disabled to test whether the crashes stop. If it makes no
|
||||
-- difference, flip back to `true` for the local fullscreen latency win.
|
||||
hl.config({ render = { direct_scanout = false } })
|
||||
'';
|
||||
|
||||
programs.noctalia.settings = {
|
||||
shell.ui_scale = 1.25;
|
||||
|
||||
+16
-17
@@ -1,20 +1,19 @@
|
||||
_:
|
||||
{
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
monitor = [
|
||||
# List HDMI-A-1 first so it becomes the primary monitor (ID 0)
|
||||
"HDMI-A-1, 3840x2160, 0x2160, 1"
|
||||
# Then the other monitors
|
||||
"DP-2, 3840x2160, 3840x180, 1, transform, 1"
|
||||
"DP-1, 3840x2160, 0x0, 1"
|
||||
];
|
||||
workspace = [
|
||||
"1, monitor:HDMI-A-1, default:true, persistent:true" # bottom-left (primary)
|
||||
"2, monitor:DP-2, persistent:true" # right (vertical)
|
||||
"3, monitor:DP-1, persistent:true" # top-left
|
||||
"4, monitor:HDMI-A-1" # bottom-left
|
||||
"5, monitor:DP-2" # right (vertical)
|
||||
"6, monitor:DP-1" # top-left
|
||||
];
|
||||
};
|
||||
# Hyprland 0.55+ is Lua-only (see home/common/programs/hyprland); monitors and
|
||||
# workspace rules are expressed with the hl.* API in raw Lua.
|
||||
wayland.windowManager.hyprland.extraConfig = ''
|
||||
-- monitors (HDMI-A-1 listed first so it becomes the primary, ID 0)
|
||||
hl.monitor({ output = "HDMI-A-1", mode = "3840x2160", position = "0x2160", scale = 1 })
|
||||
hl.monitor({ output = "DP-2", mode = "3840x2160", position = "3840x180", scale = 1, transform = 1 })
|
||||
hl.monitor({ output = "DP-1", mode = "3840x2160", position = "0x0", scale = 1 })
|
||||
|
||||
-- workspace -> monitor bindings
|
||||
hl.workspace_rule({ workspace = "1", monitor = "HDMI-A-1", default = true, persistent = true }) -- bottom-left (primary)
|
||||
hl.workspace_rule({ workspace = "2", monitor = "DP-2", persistent = true }) -- right (vertical)
|
||||
hl.workspace_rule({ workspace = "3", monitor = "DP-1", persistent = true }) -- top-left
|
||||
hl.workspace_rule({ workspace = "4", monitor = "HDMI-A-1" })
|
||||
hl.workspace_rule({ workspace = "5", monitor = "DP-2" })
|
||||
hl.workspace_rule({ workspace = "6", monitor = "DP-1" })
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
settings = {
|
||||
General = {
|
||||
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
|
||||
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 = {
|
||||
# thumbnail support for images
|
||||
tumbler.enable = true;
|
||||
|
||||
@@ -7,5 +7,30 @@
|
||||
alsa.enable = true;
|
||||
alsa.support32Bit = 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"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user