Files
dotfiles/nixos/hyprland.nix

327 lines
9.9 KiB
Nix

{
config,
pkgs,
lib,
makeEnable,
inputs,
...
}: let
system = pkgs.stdenv.hostPlatform.system;
hyprlandInput = inputs.hyprland;
baseHyprlandPackage = hyprlandInput.packages.${system}.hyprland;
cleanupStaleGraphicalSession = pkgs.writeShellScript "cleanup-stale-graphical-session" ''
set -u
# Only clean targets that are plainly stale. If a compositor is still
# running, let the active session own its own shutdown path.
if ${pkgs.procps}/bin/pgrep -u "$(${pkgs.coreutils}/bin/id -u)" -f '(^|/)(Hyprland|\.Hyprland-wrapped|river|kwin_wayland)( |$)' >/dev/null 2>&1; then
exit 0
fi
${pkgs.systemd}/bin/systemctl --user stop \
hyprland-session.target \
river-xmonad-session.target \
graphical-session.target \
graphical-session-pre.target \
tray.target \
2>/dev/null || true
${pkgs.systemd}/bin/systemctl --user unset-environment \
WAYLAND_DISPLAY \
DISPLAY \
XAUTHORITY \
HYPRLAND_INSTANCE_SIGNATURE \
XDG_CURRENT_DESKTOP \
XDG_SESSION_DESKTOP \
XDG_SESSION_TYPE \
IMALISON_SESSION_TYPE \
IMALISON_WINDOW_MANAGER \
2>/dev/null || true
${pkgs.systemd}/bin/systemctl --user reset-failed 2>/dev/null || true
'';
makeHyprlandLuaPackage = package:
(pkgs.symlinkJoin {
name = "${package.name}-lua-config";
inherit (package) version;
paths = [package];
passthru =
(package.passthru or {})
// {
providedSessions = package.passthru.providedSessions or ["hyprland"];
};
postBuild = ''
mkdir -p "$out/bin" "$out/share/wayland-sessions"
printf '%s\n' \
'#!${pkgs.runtimeShell}' \
'config_path="''${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprland.lua"' \
'exec "${package}/bin/start-hyprland" --path "${package}/bin/Hyprland" -- --config "$config_path" "$@"' \
> "$out/bin/start-hyprland-lua"
chmod +x "$out/bin/start-hyprland-lua"
printf '%s\n' \
'#!${pkgs.runtimeShell}' \
'${cleanupStaleGraphicalSession}' \
'exec ${pkgs.uwsm}/bin/uwsm start -g -1 -e -D Hyprland hyprland.desktop' \
> "$out/bin/start-hyprland-uwsm-clean"
chmod +x "$out/bin/start-hyprland-uwsm-clean"
rm -f "$out/share/wayland-sessions/hyprland.desktop"
substitute \
"${package}/share/wayland-sessions/hyprland.desktop" \
"$out/share/wayland-sessions/hyprland.desktop" \
--replace-fail \
"Exec=${package}/bin/start-hyprland" \
"Exec=$out/bin/start-hyprland-lua"
rm -f "$out/share/wayland-sessions/hyprland-uwsm.desktop"
substitute \
"${package}/share/wayland-sessions/hyprland-uwsm.desktop" \
"$out/share/wayland-sessions/hyprland-uwsm.desktop" \
--replace-fail \
"Exec=uwsm start -e -D Hyprland hyprland.desktop" \
"Exec=$out/bin/start-hyprland-uwsm-clean"
'';
})
// {
override = {enableXWayland ? true, ...} @ args:
makeHyprlandLuaPackage (package.override args);
overrideAttrs = f: makeHyprlandLuaPackage (package.overrideAttrs f);
};
hyprlandPackage = makeHyprlandLuaPackage baseHyprlandPackage;
enableHyprglass = false;
hyprglass = pkgs.callPackage ./packages/hyprglass {
src = inputs.hyprglass;
hyprland = baseHyprlandPackage;
aquamarine = inputs.aquamarine.packages.${system}.aquamarine;
hyprcursor = inputs.hyprcursor.packages.${system}.hyprcursor;
hyprgraphics = inputs.hyprgraphics.packages.${system}.hyprgraphics;
hyprlang = inputs.hyprlang.packages.${system}.hyprlang;
hyprutils = inputs.hyprutils.packages.${system}.hyprutils;
};
hyprwobbly = inputs.hyprwobbly.packages.${system}.hyprwobbly.overrideAttrs (old: {
patches =
(old.patches or [])
++ [
./packages/hyprwobbly-safe-geometry-and-idle-timer.patch
];
});
hyprexpo = inputs.hyprexpo.packages.${system}.hyprexpo;
hyprlandPluginPackages =
[
inputs.hyprNStack.packages.${system}.hyprNStack
hyprexpo
inputs.hyprwinview.packages.${system}.hyprwinview
inputs.hypr-workspace-history.packages.${system}.hypr-workspace-history
hyprwobbly
]
++ lib.optionals enableHyprglass [hyprglass];
hyprRofiWindow = pkgs.writeShellApplication {
name = "hypr_rofi_window";
runtimeInputs = [
pkgs.python3
pkgs.rofi
hyprlandPackage
];
text = ''
exec python3 ${../dotfiles/lib/bin/hypr_rofi_window} "$@"
'';
};
hyprShellUi = pkgs.writeShellApplication {
name = "hypr_shell_ui";
runtimeInputs = [
pkgs.rofi
hyprRofiWindow
];
text = ''
exec ${../dotfiles/lib/bin/hypr_shell_ui} "$@"
'';
};
hyprRofiLayout = pkgs.writeShellApplication {
name = "hypr_rofi_layout";
runtimeInputs = [
pkgs.coreutils
pkgs.findutils
pkgs.gawk
pkgs.rofi
hyprlandPackage
];
text = ''
exec ${../dotfiles/lib/bin/hypr_rofi_layout} "$@"
'';
};
hyprRofiAction = pkgs.writeShellApplication {
name = "hypr_rofi_action";
runtimeInputs = [
pkgs.libnotify
pkgs.python3
pkgs.rofi
hyprlandPackage
];
text = ''
exec python3 ${../dotfiles/lib/bin/hypr_rofi_action} "$@"
'';
};
hyprscratchSettings = {
daemon_options = "clean";
global_options = "";
global_rules = "float;size monitor_w*0.95 monitor_h*0.95;center";
htop = {
command = "alacritty --class htop-scratch --title htop -e htop";
class = "htop-scratch";
};
volume = {
command = "pavucontrol";
class = "org.pulseaudio.pavucontrol";
};
spotify = {
command = "spotify";
class = "spotify";
};
element = {
command = "element-desktop";
class = "Element";
};
slack = {
command = "slack";
class = "Slack";
};
transmission = {
command = "transmission-gtk";
class = "transmission-gtk";
};
dropdown = {
command = "ghostty --config-file=/home/imalison/.config/ghostty/dropdown";
class = "com.mitchellh.ghostty.dropdown";
options = "persist";
rules = "float;size monitor_w monitor_h*0.5;move 0 60;noborder;noshadow;animation slide";
};
};
enabledModule = makeEnable config "myModules.hyprland" true {
myModules.taffybar.enable = lib.mkDefault true;
# Needed for hyprlock authentication without PAM fallback warnings.
security.pam.services.hyprlock = {};
# DDC/CI monitor control for keyboard-driven input switching.
hardware.i2c = {
enable = true;
group = "video";
};
programs.hyprland = {
enable = true;
# Keep Hyprland and plugins on a matched flake input for ABI compatibility.
package = hyprlandPackage;
# Let UWSM manage the Hyprland session targets
withUWSM = true;
};
home-manager.sharedModules = [
inputs.hyprscratch.homeModules.default
(
{
config,
lib,
...
}: {
services.kanshi = {
enable = true;
systemdTarget = "graphical-session.target";
settings = [
{
# USB-C connector names can move between DP-* ports across docks/reboots.
# Match the ultrawide by make/model and allow the serial field to vary.
profile.name = "ultrawide-usbc-desk";
profile.outputs = [
{
criteria = "eDP-1";
status = "enable";
mode = "2560x1600@240Hz";
position = "0,0";
scale = 1.0;
}
{
criteria = "Microstep MPG341CX OLED *";
status = "enable";
mode = "3440x1440@240Hz";
position = "2560,0";
scale = 1.0;
}
];
}
{
# When the laptop panel is unavailable (e.g. lid-closed docked use),
# still drive the ultrawide at its full refresh rate.
profile.name = "ultrawide-only";
profile.outputs = [
{
criteria = "Microstep MPG341CX OLED *";
status = "enable";
mode = "3440x1440@240Hz";
position = "0,0";
scale = 1.0;
}
];
}
];
};
programs.hyprscratch = {
enable = false;
settings = {};
};
xdg.configFile."hyprscratch/config.conf" = lib.mkIf false {
text = lib.hm.generators.toHyprconf {
attrs = hyprscratchSettings;
};
};
xdg.configFile."systemd/user/wayland-wm@hyprland.desktop.service.d/10-cleanup-stale-session.conf".text = ''
[Service]
ExecStopPost=${cleanupStaleGraphicalSession}
'';
}
)
];
# Hyprland-specific packages
environment.systemPackages = with pkgs;
[
# Hyprland utilities
hyprpaper # Wallpaper
neowall # Shader wallpaper
hypridle # Idle daemon
hyprlock # Screen locker
hyprcursor # Cursor themes
wl-clipboard # Clipboard for Wayland
wtype # Wayland input typing
cliphist # Clipboard history
grim # Screenshot utility
slurp # Region selection
swappy # Screenshot annotation
nwg-displays # GUI monitor arrangement
mpvpaper # Layer-shell video screensaver payload
ddcutil # Monitor input switching over DDC/CI
# For scripts
hyprRofiLayout
hyprRofiAction
hyprRofiWindow
hyprShellUi
jq
lua5_4 # lua/luac for validating Hyprland Lua config
]
++ hyprlandPluginPackages;
};
in
enabledModule