{ pkgs , inputs , system , lib , ... }: let quickshell_bar = inputs.quickshell_bar.packages.${system}.default; att_wm = inputs.att_wm.packages.${system}.default; cliphist_river = pkgs.writeShellScriptBin "cliphist_river" '' ${lib.getExe pkgs.cliphist} list | \ ${lib.getExe pkgs.wofi} --dmenu --insensitive | \ ${lib.getExe pkgs.cliphist} decode | \ ${pkgs.wl-clipboard}/bin/wl-copy ''; screenshot_clip = pkgs.writeShellScriptBin "screenshot_clip" '' GEOM="$(${lib.getExe pkgs.slurp} -d)" ${lib.getExe pkgs.grim} -g "$GEOM" - | ${pkgs.wl-clipboard}/bin/wl-copy ''; in { # att_wm is configured at compile time, dwm-style — there is no config file to # generate here, and the bindings live in its own src/config.zig (Alt as mod, # nine tags, master/monocle/tabbed). To override them without forking, build # with a replacement config: # inputs.att_wm.packages.${system}.default.override { configFile = ./config.zig; } # # It spawns `foot` and `wmenu-run` by bare name, so both have to be on PATH; # cliphist_river and screenshot_clip are here for a future config.zig to spawn # the same way. The package also carries att_wmctl, the CLI that drives the # window manager over its IPC socket. home.packages = [ att_wm pkgs.wmenu cliphist_river screenshot_clip ]; # river runs $XDG_CONFIG_HOME/river/init at startup if it is executable, and # that is the whole of river's configuration surface — it implements no # window management itself, that is att_wm over river-window-management-v1. xdg.configFile."river/init" = { executable = true; text = '' #!${pkgs.runtimeShell} # greetd discards the session's stdio, and river/att_wm log to stderr only # — without this a failing window manager is a silent blank screen. log="''${XDG_STATE_HOME:-$HOME/.local/state}/river.log" mkdir -p "$(dirname "$log")" exec >>"$log" 2>&1 echo "=== river init $(date -Is) ===" # river's session file sets no DesktopNames, so the portal backend # (xdg.portal.config.river in fuji/configuration.nix) needs this. export XDG_CURRENT_DESKTOP=river export XDG_SESSION_TYPE=wayland # Hand the session env to systemd/D-Bus, then bring up the target the # user services hang off — same dance the home-manager sway module does. ${pkgs.dbus}/bin/dbus-update-activation-environment --systemd \ DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE \ NIXOS_OZONE_WL XCURSOR_THEME XCURSOR_SIZE systemctl --user reset-failed systemctl --user start river-session.target # The bar (same one sway and hyprland use) picks its backend from the # environment alone: with no $HYPRLAND_INSTANCE_SIGNATURE and no # $SWAYSOCK/$I3SOCK it falls through to att_wm, so nothing needs setting # here. It retries the connection every second, which is why starting it # before att_wm is fine — and why it must start after river exported # WAYLAND_DISPLAY, since that name is part of the socket path. Both sides # derive $XDG_RUNTIME_DIR/att_wm-$WAYLAND_DISPLAY.sock independently, so # nothing here has to agree on it. ${quickshell_bar}/bin/quickshell-bar & bar=$! # att_wm comes from the flake input (git.project-cloud.net/asmir/att_wm), # so the session runs exactly the revision flake.lock pins. Iterating on # it now means `nix flake update att_wm` + rebuild; to test a working tree # without that, run it under a nested river via the repo's run-nested.sh. # # Not exec'd: when att_wm exits we still want to tear the target down. # att_wm's Alt+Ctrl+Shift+q (exit_session) ends river itself. echo "starting ${lib.getExe att_wm}" ${lib.getExe att_wm} echo "=== att_wm exited with $? at $(date -Is) ===" # The bar is a child of this script rather than of att_wm, so it outlives # it: reap it here so an att_wm exit doesn't leave a bar floating over a # session with no window manager. kill "$bar" 2>/dev/null || true systemctl --user stop river-session.target ''; }; systemd.user.targets.river-session = { Unit = { Description = "river compositor session"; Documentation = [ "man:systemd.special(7)" ]; BindsTo = [ "graphical-session.target" ]; Wants = [ "graphical-session-pre.target" ]; After = [ "graphical-session-pre.target" ]; }; }; }