Files
dotfiles/nixos/river-xmonad.nix

326 lines
12 KiB
Nix

{
config,
inputs,
lib,
makeEnable,
pkgs,
...
}: let
session = import ./session-variables.nix;
riverXmonadPkgs = pkgs.extend (
lib.composeManyExtensions [
inputs.xmonad-river.overlay
inputs.xmonad-contrib.overlay
(import ../dotfiles/config/river-xmonad/overlay.nix)
]
);
riverXmonadPackage = riverXmonadPkgs.haskellPackages.imalison-river-xmonad;
riverRofi = pkgs.writeShellScriptBin "rofi" ''
exec ${pkgs.rofi}/bin/rofi -normal-window "$@"
'';
cleanupStaleGraphicalSession = ''
if ! ${pkgs.procps}/bin/pgrep -u "$(${pkgs.coreutils}/bin/id -u)" -f '(^|/)(Hyprland|\.Hyprland-wrapped|river|kwin_wayland)( |$)' >/dev/null 2>&1; then
systemctl --user stop \
hyprland-session.target \
river-xmonad-session.target \
graphical-session.target \
graphical-session-pre.target \
tray.target \
2>/dev/null || true
systemctl --user unset-environment \
WAYLAND_DISPLAY \
DISPLAY \
XAUTHORITY \
HYPRLAND_INSTANCE_SIGNATURE \
XDG_CURRENT_DESKTOP \
XDG_SESSION_DESKTOP \
XDG_SESSION_TYPE \
${session.sessionType} \
${session.windowManager} \
2>/dev/null || true
systemctl --user reset-failed 2>/dev/null || true
fi
'';
riverInit = pkgs.writeShellScript "river-xmonad-init" ''
log_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/river-xmonad"
mkdir -p "$log_dir"
echo "[$(${pkgs.coreutils}/bin/date --iso-8601=seconds)] river init start"
export PATH=${lib.makeBinPath [riverRofi]}:$PATH
export XDG_CURRENT_DESKTOP=river
export XDG_SESSION_DESKTOP=river-xmonad
export XDG_SESSION_TYPE=wayland
export ${session.sessionType}=wayland
export ${session.windowManager}=river-xmonad
systemctl --user stop hyprland-session.target || true
systemctl --user unset-environment HYPRLAND_INSTANCE_SIGNATURE || true
${pkgs.dbus}/bin/dbus-update-activation-environment --systemd \
WAYLAND_DISPLAY DISPLAY XAUTHORITY XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE \
${session.sessionType} ${session.windowManager} DBUS_SESSION_BUS_ADDRESS PATH || true
systemctl --user set-environment \
"WAYLAND_DISPLAY=''${WAYLAND_DISPLAY:-}" \
"DISPLAY=''${DISPLAY:-}" \
"XAUTHORITY=''${XAUTHORITY:-}" \
XDG_CURRENT_DESKTOP=river \
XDG_SESSION_DESKTOP=river-xmonad \
XDG_SESSION_TYPE=wayland \
${session.sessionType}=wayland \
${session.windowManager}=river-xmonad \
"DBUS_SESSION_BUS_ADDRESS=''${DBUS_SESSION_BUS_ADDRESS:-}" \
"PATH=$PATH" || true
systemctl --user import-environment \
WAYLAND_DISPLAY DISPLAY XAUTHORITY XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE \
${session.sessionType} ${session.windowManager} DBUS_SESSION_BUS_ADDRESS PATH || true
systemctl --user start river-xmonad-session.target || true
echo "$$" > "$log_dir/runner.pid"
while true; do
wm_bin="${riverXmonadPackage}/bin/imalison-river-xmonad"
if [ -f "$log_dir/wm-bin" ]; then
configured_wm_bin="$(${pkgs.coreutils}/bin/cat "$log_dir/wm-bin" || true)"
if [ -n "$configured_wm_bin" ] && [ -x "$configured_wm_bin" ]; then
wm_bin="$configured_wm_bin"
else
echo "[$(${pkgs.coreutils}/bin/date --iso-8601=seconds)] ignoring invalid wm-bin: $configured_wm_bin"
fi
fi
echo "[$(${pkgs.coreutils}/bin/date --iso-8601=seconds)] exec imalison-river-xmonad: $wm_bin"
"$wm_bin"
status=$?
echo "[$(${pkgs.coreutils}/bin/date --iso-8601=seconds)] imalison-river-xmonad exited with status $status"
if [ -e "$log_dir/stop-runner" ]; then
echo "[$(${pkgs.coreutils}/bin/date --iso-8601=seconds)] stop-runner present, exiting runner"
exit "$status"
fi
${pkgs.coreutils}/bin/sleep 0.25
done
'';
riverSession = pkgs.writeShellScriptBin "river-xmonad-session" ''
log_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/river-xmonad"
mkdir -p "$log_dir"
log_file="$log_dir/session.log"
exec >>"$log_file" 2>&1
echo
echo "===== river-xmonad session start: $(${pkgs.coreutils}/bin/date --iso-8601=seconds) ====="
export XDG_CURRENT_DESKTOP=river
export XDG_SESSION_DESKTOP=river-xmonad
export XDG_SESSION_TYPE=wayland
export ${session.sessionType}=wayland
export ${session.windowManager}=river-xmonad
export PATH=${lib.makeBinPath [riverRofi]}:$PATH
echo "river-xmonad: environment before river"
env | ${pkgs.coreutils}/bin/sort
${cleanupStaleGraphicalSession}
systemctl --user stop hyprland-session.target || true
systemctl --user unset-environment HYPRLAND_INSTANCE_SIGNATURE || true
${pkgs.river}/bin/river -c ${lib.escapeShellArg "${riverInit}"}
status=$?
echo "river-xmonad: river exited with status $status at $(${pkgs.coreutils}/bin/date --iso-8601=seconds)"
systemctl --user stop river-xmonad-session.target graphical-session.target graphical-session-pre.target tray.target || true
systemctl --user unset-environment WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_DESKTOP XDG_SESSION_TYPE ${session.sessionType} ${session.windowManager} || true
exit "$status"
'';
riverDiagnostics = pkgs.writeShellScriptBin "river-xmonad-diagnostics" ''
set -u
log_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/river-xmonad"
echo "river-xmonad diagnostics: $(${pkgs.coreutils}/bin/date --iso-8601=seconds)"
echo
echo "== processes =="
${pkgs.procps}/bin/pgrep -a 'river|imalison-river-xmonad|rofi|ghostty|hyprpaper|xsettingsd|picom|autorandr' || true
echo
echo "== user manager environment =="
systemctl --user show-environment | ${pkgs.coreutils}/bin/sort | ${pkgs.gnugrep}/bin/grep -E '^(HYPR|IMALISON|XDG_CURRENT_DESKTOP|XDG_SESSION_DESKTOP|XDG_SESSION_TYPE|WAYLAND_DISPLAY|DISPLAY)=' || true
echo
echo "== session unit guards =="
systemctl --user cat river-xmonad-session.target dunst.service hyprpaper.service hyprland-session.target xsettingsd.service picom.service autorandr.service 2>/dev/null \
| ${pkgs.gnugrep}/bin/grep -E '^(# |\\[Unit\\]|Description=|ConditionEnvironment=|PartOf=|After=|WantedBy=|ExecStart=|\\[Install\\])' || true
echo
echo "== recent user journal =="
journalctl --user -b --since '10 minutes ago' --no-pager \
| ${pkgs.gnugrep}/bin/grep -Ei 'river|xmonad|rofi|ghostty|hyprpaper|xsettingsd|picom|autorandr|failed|error|segfault|core-dump' || true
echo
if [ -f "$log_dir/session.log" ]; then
echo "== $log_dir/session.log tail =="
${pkgs.coreutils}/bin/tail -n 250 "$log_dir/session.log"
else
echo "no session log at $log_dir/session.log"
fi
'';
riverRestart = pkgs.writeShellScriptBin "river-xmonad-restart" ''
set -u
wm_bin="''${1:-${riverXmonadPackage}/bin/imalison-river-xmonad}"
log_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/river-xmonad"
log_file="$log_dir/session.log"
mkdir -p "$log_dir"
if [ ! -x "$wm_bin" ]; then
echo "river-xmonad-restart: WM binary is not executable: $wm_bin" >&2
exit 1
fi
if ! ${pkgs.procps}/bin/pgrep -x river >/dev/null 2>&1; then
echo "river-xmonad-restart: river is not running" >&2
exit 1
fi
systemd_env="$(systemctl --user show-environment 2>/dev/null || true)"
env_value() {
printf '%s\n' "$systemd_env" | ${pkgs.gnused}/bin/sed -n "s/^$1=//p" | ${pkgs.coreutils}/bin/head -n 1
}
export XDG_RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}"
systemd_wayland_display="$(env_value WAYLAND_DISPLAY)"
if [ -n "$systemd_wayland_display" ]; then
export WAYLAND_DISPLAY="$systemd_wayland_display"
else
export WAYLAND_DISPLAY="''${WAYLAND_DISPLAY:-}"
fi
if [ -z "''${WAYLAND_DISPLAY:-}" ]; then
for socket in "$XDG_RUNTIME_DIR"/wayland-*; do
[ -S "$socket" ] || continue
export WAYLAND_DISPLAY="$(${pkgs.coreutils}/bin/basename "$socket")"
break
done
fi
export WAYLAND_DISPLAY="''${WAYLAND_DISPLAY:-wayland-1}"
systemd_display="$(env_value DISPLAY)"
if [ -n "$systemd_display" ]; then
export DISPLAY="$systemd_display"
else
export DISPLAY="''${DISPLAY:-}"
fi
export DBUS_SESSION_BUS_ADDRESS="''${DBUS_SESSION_BUS_ADDRESS:-$(env_value DBUS_SESSION_BUS_ADDRESS)}"
export XDG_CURRENT_DESKTOP=river
export XDG_SESSION_DESKTOP=river-xmonad
export XDG_SESSION_TYPE=wayland
export ${session.sessionType}=wayland
export ${session.windowManager}=river-xmonad
export PATH=${lib.makeBinPath [riverRofi]}:$PATH
wm_process_pattern='[b]in/imalison-river-xmonad($| )'
old_pids="$(${pkgs.procps}/bin/pgrep -f "$wm_process_pattern" || true)"
runner_pid="$(${pkgs.coreutils}/bin/cat "$log_dir/runner.pid" 2>/dev/null || true)"
if [ -z "$runner_pid" ] || ! ${pkgs.coreutils}/bin/kill -0 "$runner_pid" 2>/dev/null; then
echo "river-xmonad-restart: river-xmonad runner is not active; restart the river-xmonad session once to enable dynamic WM restarts" >&2
exit 2
fi
{
echo
echo "===== river-xmonad restart: $(${pkgs.coreutils}/bin/date --iso-8601=seconds) ====="
echo "river-xmonad-restart: binary=$wm_bin"
echo "$wm_bin" > "$log_dir/wm-bin"
echo "river-xmonad-restart: WAYLAND_DISPLAY=$WAYLAND_DISPLAY DISPLAY=''${DISPLAY:-}"
echo "river-xmonad-restart: runner pid: $runner_pid"
if [ -n "$old_pids" ]; then
echo "river-xmonad-restart: stopping old pids: $old_pids"
else
echo "river-xmonad-restart: no old imalison-river-xmonad process found; runner should start $wm_bin if idle"
fi
} >>"$log_file"
if [ -n "$old_pids" ]; then
for pid in $old_pids; do
${pkgs.coreutils}/bin/kill -TERM "$pid" 2>/dev/null || true
done
i=0
while ${pkgs.procps}/bin/pgrep -f "$wm_process_pattern" >/dev/null 2>&1 && [ "$i" -lt 30 ]; do
${pkgs.coreutils}/bin/sleep 0.1
i=$((i + 1))
done
if ${pkgs.procps}/bin/pgrep -f "$wm_process_pattern" >/dev/null 2>&1; then
${pkgs.procps}/bin/pkill -KILL -f "$wm_process_pattern" || true
fi
fi
i=0
while [ "$i" -lt 50 ]; do
new_pids="$(${pkgs.procps}/bin/pgrep -f "$wm_process_pattern" || true)"
if [ -n "$new_pids" ] && [ "$new_pids" != "$old_pids" ]; then
echo "$new_pids" | ${pkgs.coreutils}/bin/head -n 1 > "$log_dir/wm.pid"
echo "river-xmonad-restart: active pid(s): $new_pids"
exit 0
fi
${pkgs.coreutils}/bin/sleep 0.1
i=$((i + 1))
done
echo "river-xmonad-restart: timed out waiting for runner to start $wm_bin" >&2
exit 1
'';
riverSessionPackage =
(pkgs.writeTextFile {
name = "river-xmonad-session";
destination = "/share/wayland-sessions/river-xmonad.desktop";
text = ''
[Desktop Entry]
Name=river-xmonad
Comment=river with xmonad as its external window manager
Exec=${riverSession}/bin/river-xmonad-session
Type=Application
DesktopNames=river
'';
}).overrideAttrs (_old: {
passthru.providedSessions = ["river-xmonad"];
});
in
makeEnable config "myModules.riverXmonad" false {
services.displayManager.sessionPackages = [
riverSessionPackage
];
home-manager.sharedModules = [
{
systemd.user.targets.river-xmonad-session = {
Unit = {
Description = "river-xmonad session";
ConditionEnvironment = session.riverXmonad;
BindsTo = ["graphical-session.target"];
Wants = ["graphical-session-pre.target"];
After = ["graphical-session-pre.target"];
Before = ["graphical-session.target"];
};
};
}
];
environment.systemPackages = with pkgs; [
brightnessctl
river
riverDiagnostics
riverRestart
riverXmonadPackage
wl-clipboard
wtype
];
}