Hyprland 0.56.x dropped the hyprlang (.conf) config backend in favor of Lua-only config. Switch configType to "lua" and rewrite monitors, workspace/window rules, binds, env vars, and autostart using the new hl.* dispatcher API. Un-pin the hyprland flake input (back to current, following root nixpkgs) now that the config no longer depends on the old hyprlang backend. Every module that injects Hyprland keybinds (foot, wezterm, bemenu, music, light, sound, screenshot, physlock, hyprlock) is converted to the same Lua bind syntax, since the config is all-or-nothing per configType. hyprlock's swayidle dpms toggle and the hyprland-monitors hotplug script's hyprctl dispatch calls are updated to the new hl.dsp.* expression syntax the same way. One behavior change: hl.workspace_rule keeps only the last monitor set per workspace (unlike hyprlang's rule list), so workspaces that used to declare two candidate monitors now declare one static default; the hyprland-monitors service still re-dispatches the correct monitor on startup and hotplug.
453 lines
13 KiB
Nix
453 lines
13 KiB
Nix
{
|
|
inputs,
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
enabled = config.mod.hyprland.enable;
|
|
|
|
mod = "SUPER";
|
|
|
|
monitorScript = pkgs.writeShellScript "hyprland-monitor-handler" ''
|
|
INTERNAL="eDP-1"
|
|
EXTERNAL_MONITORS="HDMI-A-1 DP-3"
|
|
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
|
|
JQ="${pkgs.jq}/bin/jq"
|
|
|
|
get_active_external() {
|
|
# Return the first connected external monitor
|
|
for mon in $EXTERNAL_MONITORS; do
|
|
if $HYPRCTL monitors -j | $JQ -e ".[] | select(.name == \"$mon\")" > /dev/null 2>&1; then
|
|
echo "$mon"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
bind_workspaces() {
|
|
local external batch=""
|
|
|
|
if external=$(get_active_external); then
|
|
# External monitor connected: move workspaces 1-5 to external, 6-10 to internal
|
|
for ws in 1 2 3 4 5; do
|
|
batch="$batch dispatch hl.dsp.workspace.move({ monitor = \"$external\", workspace = $ws });"
|
|
done
|
|
for ws in 6 7 8 9 10; do
|
|
batch="$batch dispatch hl.dsp.workspace.move({ monitor = \"$INTERNAL\", workspace = $ws });"
|
|
done
|
|
else
|
|
# No external monitor: move all workspaces to internal
|
|
for ws in 1 2 3 4 5 6 7 8 9 10; do
|
|
batch="$batch dispatch hl.dsp.workspace.move({ monitor = \"$INTERNAL\", workspace = $ws });"
|
|
done
|
|
fi
|
|
|
|
$HYPRCTL --batch "$batch"
|
|
}
|
|
|
|
handle_event() {
|
|
case $1 in
|
|
monitoradded*|monitorremoved*)
|
|
sleep 0.5
|
|
bind_workspaces
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Start the event listener first so monitoradded events emitted during
|
|
# session startup are not lost in the gap before we begin reading them.
|
|
${pkgs.socat}/bin/socat -U - UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do
|
|
handle_event "$line"
|
|
done &
|
|
LISTENER_PID=$!
|
|
|
|
# Give socat a moment to actually connect before the initial bind.
|
|
sleep 0.2
|
|
bind_workspaces
|
|
|
|
# Re-bind once more after the DRM subsystem has had time to enumerate
|
|
# external connectors, in case they were not yet present at session start.
|
|
(sleep 3; bind_workspaces) &
|
|
|
|
wait $LISTENER_PID
|
|
'';
|
|
in
|
|
{
|
|
options = {
|
|
mod.hyprland = {
|
|
enable = lib.mkEnableOption "enable hyprland module";
|
|
};
|
|
};
|
|
|
|
config = lib.mkIf enabled {
|
|
programs.hyprland = {
|
|
enable = true;
|
|
withUWSM = true;
|
|
package = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.hyprland;
|
|
portalPackage =
|
|
inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.xdg-desktop-portal-hyprland;
|
|
|
|
xwayland = {
|
|
enable = true;
|
|
};
|
|
};
|
|
|
|
home-manager.users.alex = {
|
|
wayland.windowManager.hyprland = {
|
|
enable = true;
|
|
systemd.enable = false;
|
|
configType = "lua";
|
|
|
|
settings = {
|
|
config = {
|
|
xwayland.force_zero_scaling = true;
|
|
|
|
input = {
|
|
kb_layout = "se";
|
|
|
|
# 2 - Cursor focus will be detached from keyboard focus. Clicking on a window will move keyboard focus to that window.
|
|
follow_mouse = 2;
|
|
|
|
sensitivity = 0.3;
|
|
accel_profile = "flat";
|
|
touchpad = {
|
|
natural_scroll = false;
|
|
tap_and_drag = false;
|
|
};
|
|
};
|
|
|
|
cursor = {
|
|
zoom_factor = 1;
|
|
zoom_rigid = true;
|
|
};
|
|
|
|
general = {
|
|
layout = "dwindle";
|
|
|
|
gaps_in = 0; # gaps between windows
|
|
gaps_out = 0; # gaps between windows and monitor edges
|
|
|
|
col = {
|
|
active_border = "rgba(${config.lib.colors.foreground}ff)";
|
|
inactive_border = "rgba(${config.lib.colors.background}ff)";
|
|
};
|
|
};
|
|
|
|
dwindle.force_split = 2;
|
|
|
|
misc = {
|
|
disable_hyprland_logo = true;
|
|
disable_splash_rendering = true;
|
|
};
|
|
|
|
animations.enabled = false;
|
|
};
|
|
|
|
monitor = [
|
|
{
|
|
output = "eDP-1";
|
|
mode = "1920x1200";
|
|
position = "auto-center-down";
|
|
scale = "1";
|
|
}
|
|
{
|
|
output = "HDMI-A-1";
|
|
mode = "2560x1440@100";
|
|
position = "auto-center-up";
|
|
scale = "1";
|
|
}
|
|
{
|
|
output = "DP-3";
|
|
mode = "2560x1440@60";
|
|
position = "auto-center-up";
|
|
scale = "1";
|
|
}
|
|
];
|
|
|
|
# Static defaults only: a workspace can only have one `monitor` in
|
|
# hl.workspace_rule (later calls overwrite it, unlike hyprlang's
|
|
# rule list). The hyprland-monitors service re-dispatches these to
|
|
# whichever external monitor is actually connected at startup and
|
|
# on hotplug, so this only matters for the brief window before it
|
|
# runs.
|
|
workspace_rule = [
|
|
{
|
|
workspace = "1";
|
|
monitor = "HDMI-A-1";
|
|
default = true;
|
|
}
|
|
{
|
|
workspace = "2";
|
|
monitor = "HDMI-A-1";
|
|
}
|
|
{
|
|
workspace = "3";
|
|
monitor = "HDMI-A-1";
|
|
}
|
|
{
|
|
workspace = "4";
|
|
monitor = "HDMI-A-1";
|
|
}
|
|
{
|
|
workspace = "5";
|
|
monitor = "HDMI-A-1";
|
|
}
|
|
|
|
{
|
|
workspace = "6";
|
|
monitor = "eDP-1";
|
|
default = true;
|
|
}
|
|
{
|
|
workspace = "7";
|
|
monitor = "eDP-1";
|
|
}
|
|
{
|
|
workspace = "8";
|
|
monitor = "eDP-1";
|
|
}
|
|
{
|
|
workspace = "9";
|
|
monitor = "eDP-1";
|
|
}
|
|
{
|
|
workspace = "10";
|
|
monitor = "eDP-1";
|
|
}
|
|
|
|
{
|
|
workspace = "w[tv1]";
|
|
gaps_out = 0;
|
|
gaps_in = 0;
|
|
}
|
|
{
|
|
workspace = "f[1]";
|
|
gaps_out = 0;
|
|
gaps_in = 0;
|
|
}
|
|
];
|
|
|
|
window_rule = [
|
|
{
|
|
name = "no-gaps-wtv1";
|
|
match = {
|
|
float = false;
|
|
workspace = "w[tv1]";
|
|
};
|
|
border_size = 0;
|
|
rounding = 0;
|
|
}
|
|
{
|
|
name = "no-gaps-f1";
|
|
match = {
|
|
float = false;
|
|
workspace = "f[1]";
|
|
};
|
|
border_size = 0;
|
|
rounding = 0;
|
|
}
|
|
{
|
|
# https://wiki.archlinux.org/title/Hyprland#Jetbrains_apps_focus_issues
|
|
name = "xwayland-no-initial-focus";
|
|
match.xwayland = true;
|
|
no_initial_focus = true;
|
|
}
|
|
];
|
|
|
|
env = [
|
|
{ _args = [ "GDK_DPI_SCALE" "1.5" ]; }
|
|
{ _args = [ "HYPRCURSOR_THEME" "Adwaita" ]; }
|
|
{ _args = [ "HYPRCURSOR_SIZE" "24" ]; }
|
|
];
|
|
|
|
on = {
|
|
_args = [
|
|
"hyprland.start"
|
|
(lib.generators.mkLuaInline ''
|
|
function()
|
|
hl.exec_cmd("uwsm app -- waybar")
|
|
hl.exec_cmd("uwsm app -- hyprctl setcursor Adwaita 24")
|
|
end
|
|
'')
|
|
];
|
|
};
|
|
|
|
bind =
|
|
let
|
|
ws =
|
|
x:
|
|
let
|
|
n = if (x + 1) < 10 then (x + 1) else 0;
|
|
in
|
|
builtins.toString n;
|
|
|
|
focusWs = x: {
|
|
_args = [
|
|
"${mod} + ${ws x}"
|
|
(lib.generators.mkLuaInline "hl.dsp.focus({ workspace = ${builtins.toString (x + 1)} })")
|
|
];
|
|
};
|
|
moveToWs = x: {
|
|
_args = [
|
|
"${mod} + SHIFT + ${ws x}"
|
|
(lib.generators.mkLuaInline
|
|
"hl.dsp.window.move({ workspace = ${builtins.toString (x + 1)}, follow = false })"
|
|
)
|
|
];
|
|
};
|
|
|
|
select = builtins.genList focusWs 10;
|
|
move = builtins.genList moveToWs 10;
|
|
|
|
magnifier = pkgs.writeShellScript "magnifier" ''
|
|
CURRENT=$(${pkgs.hyprland}/bin/hyprctl getoption cursor:zoom_factor -j | ${pkgs.jq}/bin/jq .float)
|
|
DELTA=0.1
|
|
|
|
UPDATED=1
|
|
case $1 in
|
|
--increase)
|
|
UPDATED=$(echo $CURRENT + $DELTA | ${pkgs.bc}/bin/bc) ;;
|
|
--decrease)
|
|
UPDATED=$(echo $CURRENT - $DELTA | ${pkgs.bc}/bin/bc) ;;
|
|
--reset)
|
|
UPDATED=1
|
|
esac
|
|
|
|
if (( $(echo "$UPDATED < 1" | bc) )); then UPDATED=1; fi
|
|
${pkgs.hyprland}/bin/hyprctl keyword cursor:zoom_factor $UPDATED
|
|
'';
|
|
in
|
|
select
|
|
++ move
|
|
++ [
|
|
{
|
|
_args = [
|
|
"${mod} + ESCAPE"
|
|
(lib.generators.mkLuaInline "hl.dsp.window.close()")
|
|
];
|
|
}
|
|
|
|
{
|
|
_args = [
|
|
"${mod} + f"
|
|
(lib.generators.mkLuaInline ''hl.dsp.window.fullscreen({ mode = "maximized" })'')
|
|
];
|
|
}
|
|
{
|
|
_args = [
|
|
"${mod} + SHIFT + f"
|
|
(lib.generators.mkLuaInline ''hl.dsp.window.float({ action = "toggle" })'')
|
|
];
|
|
}
|
|
|
|
{
|
|
_args = [
|
|
"${mod} + h"
|
|
(lib.generators.mkLuaInline ''hl.dsp.focus({ direction = "left" })'')
|
|
];
|
|
}
|
|
{
|
|
_args = [
|
|
"${mod} + j"
|
|
(lib.generators.mkLuaInline ''hl.dsp.focus({ direction = "down" })'')
|
|
];
|
|
}
|
|
{
|
|
_args = [
|
|
"${mod} + k"
|
|
(lib.generators.mkLuaInline ''hl.dsp.focus({ direction = "up" })'')
|
|
];
|
|
}
|
|
{
|
|
_args = [
|
|
"${mod} + l"
|
|
(lib.generators.mkLuaInline ''hl.dsp.focus({ direction = "right" })'')
|
|
];
|
|
}
|
|
|
|
{
|
|
_args = [
|
|
"${mod} + CONTROL + 1"
|
|
(lib.generators.mkLuaInline ''hl.dsp.exec_cmd("${magnifier} --increase")'')
|
|
];
|
|
}
|
|
{
|
|
_args = [
|
|
"${mod} + CONTROL + 2"
|
|
(lib.generators.mkLuaInline ''hl.dsp.exec_cmd("${magnifier} --decrease")'')
|
|
];
|
|
}
|
|
{
|
|
_args = [
|
|
"${mod} + CONTROL + 3"
|
|
(lib.generators.mkLuaInline ''hl.dsp.exec_cmd("${magnifier} --reset")'')
|
|
];
|
|
}
|
|
];
|
|
|
|
bindm = [
|
|
# mouse movements
|
|
{
|
|
_args = [
|
|
"${mod} + mouse:272" # left click
|
|
(lib.generators.mkLuaInline "hl.dsp.window.drag()")
|
|
{ mouse = true; }
|
|
];
|
|
}
|
|
{
|
|
_args = [
|
|
"${mod} + mouse:273" # right click
|
|
(lib.generators.mkLuaInline "hl.dsp.window.resize()")
|
|
{ mouse = true; }
|
|
];
|
|
}
|
|
];
|
|
};
|
|
};
|
|
|
|
home.packages = [
|
|
pkgs.wdisplays
|
|
pkgs.bc
|
|
];
|
|
|
|
systemd.user.services.hyprland-monitors = {
|
|
Unit = {
|
|
Description = "Hyprland monitor hotplug handler";
|
|
PartOf = [ "graphical-session.target" ];
|
|
After = [ "graphical-session.target" ];
|
|
};
|
|
Service = {
|
|
Type = "simple";
|
|
ExecStart = "${monitorScript}";
|
|
Restart = "on-failure";
|
|
RestartSec = 5;
|
|
};
|
|
Install = {
|
|
WantedBy = [ "graphical-session.target" ];
|
|
};
|
|
};
|
|
};
|
|
|
|
# To start electron apps like `chromium` with wayland support
|
|
environment.sessionVariables.NIXOS_OZONE_WL = "1";
|
|
|
|
# The XDG portal is needed for screen sharing
|
|
xdg.portal = {
|
|
enable = true;
|
|
|
|
# override "trace: warning: xdg-desktop-portal 1.17 reworked how portal implementations are loaded ..."
|
|
config.common.default = "*";
|
|
|
|
extraPortals = [ pkgs.xdg-desktop-portal-wlr ];
|
|
};
|
|
|
|
# openGL is needed for wayland/hyprland
|
|
hardware.graphics.enable = true;
|
|
|
|
boot.kernelParams = [ "i915.enable_psr=0" ];
|
|
};
|
|
}
|