Hyprland's lua API only registers hl.bind; there is no separate
hl.bindm. The settings.bindm key rendered as a literal hl.bindm(...)
call, which errored with "attempt to call a nil value" at runtime.
Fold the mouse binds into settings.bind with { mouse = true }, per
the wiki's own example config.
452 lines
13 KiB
Nix
452 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")'')
|
|
];
|
|
}
|
|
|
|
# mouse movements: hyprland's lua API has no separate `bindm`,
|
|
# mouse binds are plain `hl.bind` with `{ mouse = true }`.
|
|
{
|
|
_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" ];
|
|
};
|
|
}
|