diff --git a/dotfiles/config/river-xmonad/Main.hs b/dotfiles/config/river-xmonad/Main.hs new file mode 100644 index 00000000..ce0d26ca --- /dev/null +++ b/dotfiles/config/river-xmonad/Main.hs @@ -0,0 +1,322 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE RecordWildCards #-} + +module Main where + +import Control.Concurrent (forkIO) +import Data.Bits ((.&.), complement) +import Data.Function (on) +import Data.List (minimumBy) +import qualified Data.Map.Strict as M +import Data.Maybe (mapMaybe) +import Graphics.X11.ExtraTypes.XF86 +import System.IO (hFlush, stdout) +import System.Process (spawnCommand, waitForProcess) +import XMonad +import XMonad.Layout.Accordion +import XMonad.Layout.Cross +import XMonad.Layout.Grid +import XMonad.Layout.MultiColumns +import qualified XMonad.Layout.Renamed as RN +import XMonad.River.WindowManager +import XMonad.River.WindowManager.Wayland +import qualified XMonad.StackSet as W + +data Direction = DirectionUp | DirectionDown | DirectionLeft | DirectionRight + deriving (Eq, Show) + +main :: IO () +main = do + let bindings = keyBindings + configLog $ "starting imalison-river-xmonad with keybindings=" ++ show (length bindings) + initialState <- initialRiverWMState (defaultRiverWMConfig riverLayouts) + runRiverWMWaylandConfig + RiverWMWaylandConfig + { riverWMWaylandInitialState = initialState + , riverWMWaylandKeyBindings = bindings + } + +riverLayouts = + renamed "4 Columns" (multiCol [1, 1, 1] 2 0.0 (-0.5)) + ||| renamed "3 Columns" (multiCol [1, 1] 2 0.01 (-0.5)) + ||| renamed "Grid" Grid + ||| renamed "Large Main" (Tall 1 (3 / 100) (3 / 4)) + ||| renamed "2 Columns" (Tall 1 (3 / 100) (1 / 2)) + ||| renamed "Mirror 2 Columns" (Mirror (Tall 1 (3 / 100) (1 / 2))) + ||| renamed "Accordion" Accordion + ||| renamed "Cross" simpleCross + ||| Full + where + renamed name = RN.renamed [RN.Replace name] + +keyBindings + :: (LayoutClass l Window, Read (l Window)) + => [RiverWMWaylandKeyBinding l] +keyBindings = + addHyperChordBindings hyper hyperChord $ + concat + [ directionalBindings super directionalFocus + , directionalBindings (super .|. shift) directionalSwap + , workspaceBindings + , layoutBindings + , spawnBindings + , mediaBindings + ] + +directionalBindings + :: RiverWMWaylandModifiers + -> (Direction -> RiverWMWaylandAction l) + -> [RiverWMWaylandKeyBinding l] +directionalBindings mods command = + [ key mods xK_w (command DirectionUp) + , key mods xK_s (command DirectionDown) + , key mods xK_a (command DirectionLeft) + , key mods xK_d (command DirectionRight) + ] + +workspaceBindings + :: [RiverWMWaylandKeyBinding l] +workspaceBindings = + [ key (mods .|. super) keysym (stackAction $ command workspace) + | (workspace, keysym) <- zip (map show [(1 :: Int) .. 9]) [xK_1 .. xK_9] + , (command, mods) <- + [ (W.greedyView, noMods) + , (W.shift, shift) + , (\workspaceId stackSet -> W.greedyView workspaceId (W.shift workspaceId stackSet), ctrl) + ] + ] + +layoutBindings + :: (LayoutClass l Window, Read (l Window)) + => [RiverWMWaylandKeyBinding l] +layoutBindings = + [ key super xK_space (layoutAction NextLayout) + , key super xK_bracketleft (layoutAction Shrink) + , key super xK_bracketright (layoutAction Expand) + , key super xK_comma (layoutAction (IncMasterN 1)) + , key super xK_period (layoutAction (IncMasterN (-1))) + ] + +spawnBindings + :: [RiverWMWaylandKeyBinding l] +spawnBindings = + [ key super xK_Return (spawnAction "ghostty --gtk-single-instance=false") + , key super xK_p (spawnAction "rofi -show drun -show-icons") + , key (super .|. shift) xK_p (spawnAction "rofi -show run") + , key (super .|. alt) xK_c (spawnAction "google-chrome-stable") + , key super xK_e (spawnAction "emacsclient --eval '(emacs-everywhere)'") + , key super xK_v (spawnAction "wl-paste | wtype -") + , key hyper xK_v (spawnAction "rofi -modi 'clipboard:greenclip print' -show clipboard") + , key hyper xK_p (spawnAction "rofi-pass") + , key hyper xK_h (spawnAction "rofi_shutter") + , key hyper xK_c (spawnAction "shell_command.sh") + , key hyper xK_x (spawnAction "rofi_command.sh") + , key (hyper .|. shift) xK_l (spawnAction "loginctl lock-session") + , key hyper xK_k (spawnAction "rofi_kill_process.sh") + , key (hyper .|. shift) xK_k (spawnAction "rofi_kill_all.sh") + , key hyper xK_r (spawnAction "rofi-systemd") + , key hyper xK_9 (spawnAction "start_synergy.sh") + , key hyper xK_backslash (spawnAction "$HOME/dotfiles/dotfiles/lib/functions/mpg341cx_input toggle") + , key hyper xK_i (spawnAction "rofi_select_input.hs") + , key hyper xK_o (spawnAction "rofi_paswitch") + , key hyper xK_w (spawnAction "rofi_wallpaper.sh") + , key hyper xK_y (spawnAction "rofi_agentic_skill") + ] + +mediaBindings + :: [RiverWMWaylandKeyBinding l] +mediaBindings = + [ key super xK_semicolon (spawnAction "playerctl play-pause") + , key noMods xF86XK_AudioPause (spawnAction "playerctl play-pause") + , key noMods xF86XK_AudioPlay (spawnAction "playerctl play-pause") + , key super xK_l (spawnAction "playerctl next") + , key noMods xF86XK_AudioNext (spawnAction "playerctl next") + , key super xK_j (spawnAction "playerctl previous") + , key noMods xF86XK_AudioPrev (spawnAction "playerctl previous") + , key noMods xF86XK_AudioRaiseVolume (spawnAction "set_volume --unmute --change-volume +5") + , key noMods xF86XK_AudioLowerVolume (spawnAction "set_volume --unmute --change-volume -5") + , key noMods xF86XK_AudioMute (spawnAction "set_volume --toggle-mute") + , key super xK_i (spawnAction "set_volume --unmute --change-volume +5") + , key super xK_k (spawnAction "set_volume --unmute --change-volume -5") + , key super xK_u (spawnAction "set_volume --toggle-mute") + , key (hyper .|. shift) xK_q (spawnAction "toggle_mute_current_window.sh") + , key (hyper .|. ctrl) xK_q (spawnAction "toggle_mute_current_window.sh only") + , key noMods xF86XK_MonBrightnessUp (spawnAction "brightness.sh up") + , key noMods xF86XK_MonBrightnessDown (spawnAction "brightness.sh down") + ] + +key + :: RiverWMWaylandModifiers + -> KeySym + -> RiverWMWaylandAction l + -> RiverWMWaylandKeyBinding l +key modifiers keysym action = + RiverWMWaylandKeyBinding + { riverWMWaylandKeyModifiers = modifiers + , riverWMWaylandKeyKeysym = fromIntegral keysym + , riverWMWaylandKeyAction = action + } + +spawnAction :: String -> RiverWMWaylandAction l +spawnAction command state = do + configLog $ "spawn start: " ++ command + process <- spawnCommand (riverSpawnPrelude ++ command) + _ <- forkIO $ do + exitCode <- waitForProcess process + configLog $ "spawn exit: " ++ command ++ " -> " ++ show exitCode + pure () + pure ([], state) + +riverSpawnPrelude :: String +riverSpawnPrelude = + "XDG_RUNTIME_DIR=\"${XDG_RUNTIME_DIR:-/run/user/$(id -u)}\"; " + ++ "export XDG_RUNTIME_DIR; " + ++ "if [ -z \"${WAYLAND_DISPLAY:-}\" ]; then " + ++ "for socket in \"$XDG_RUNTIME_DIR\"/wayland-*; do " + ++ "[ -S \"$socket\" ] || continue; " + ++ "WAYLAND_DISPLAY=\"$(basename \"$socket\")\"; " + ++ "break; " + ++ "done; " + ++ "fi; " + ++ "export WAYLAND_DISPLAY=\"${WAYLAND_DISPLAY:-wayland-1}\"; " + ++ "export XDG_CURRENT_DESKTOP=river; " + ++ "export XDG_SESSION_DESKTOP=river-xmonad; " + ++ "export XDG_SESSION_TYPE=wayland; " + ++ "export IMALISON_SESSION_TYPE=wayland; " + ++ "export IMALISON_WINDOW_MANAGER=river-xmonad; " + +configLog :: String -> IO () +configLog message = do + putStrLn $ "imalison-river-xmonad: " ++ message + hFlush stdout + +layoutAction + :: (LayoutClass l Window, Read (l Window), Message message) + => message + -> RiverWMWaylandAction l +layoutAction = handleRiverWMLayoutMessage + +stackAction + :: (W.StackSet WorkspaceId (l Window) Window RiverWMOutputId ScreenDetail + -> W.StackSet WorkspaceId (l Window) Window RiverWMOutputId ScreenDetail) + -> RiverWMWaylandAction l +stackAction f state = + pure $ modifyRiverWMStackSet f state + +directionalSwap :: Direction -> RiverWMWaylandAction l +directionalSwap direction = + stackAction $ + case direction of + DirectionUp -> W.swapUp + DirectionLeft -> W.swapUp + DirectionDown -> W.swapDown + DirectionRight -> W.swapDown + +directionalFocus :: Direction -> RiverWMWaylandAction l +directionalFocus direction state = + pure $ modifyRiverWMStackSet focusDirectionalWindow state + where + focusDirectionalWindow stackSet = + maybe stackSet (`W.focusWindow` stackSet) $ + directionalTarget direction state + +directionalTarget :: Direction -> RiverWMState l -> Maybe Window +directionalTarget direction RiverWMState{riverWMStackSet, riverWMWindows, riverWMWindowIds} = do + focused <- W.peek riverWMStackSet + focusedId <- M.lookup focused riverWMWindowIds + focusedRect <- riverWMWindowDesired =<< M.lookup focusedId riverWMWindows + let focusedCenter = rectCenter focusedRect + candidates = + [ (window, directionScore direction focusedCenter (rectCenter rect)) + | (windowId, RiverWMWindowState{riverWMWindowXWindow = window, riverWMWindowDesired = Just rect}) <- + M.toList riverWMWindows + , windowId /= focusedId + ] + viable = mapMaybe sequenceCandidate candidates + fst <$> minimumMaybeBy (compare `on` snd) viable + +sequenceCandidate :: (a, Maybe b) -> Maybe (a, b) +sequenceCandidate (value, Just score) = Just (value, score) +sequenceCandidate (_, Nothing) = Nothing + +rectCenter :: Rectangle -> (Double, Double) +rectCenter (Rectangle x y width height) = + ( fromIntegral x + fromIntegral width / 2 + , fromIntegral y + fromIntegral height / 2 + ) + +directionScore :: Direction -> (Double, Double) -> (Double, Double) -> Maybe (Double, Double) +directionScore direction (fx, fy) (cx, cy) = + case direction of + DirectionUp | cy < fy -> Just (fy - cy, abs (cx - fx)) + DirectionDown | cy > fy -> Just (cy - fy, abs (cx - fx)) + DirectionLeft | cx < fx -> Just (fx - cx, abs (cy - fy)) + DirectionRight | cx > fx -> Just (cx - fx, abs (cy - fy)) + _ -> Nothing + +minimumMaybeBy :: (a -> a -> Ordering) -> [a] -> Maybe a +minimumMaybeBy _ [] = Nothing +minimumMaybeBy compareFn xs = Just (minimumBy compareFn xs) + +addHyperChordBindings + :: RiverWMWaylandModifiers + -> RiverWMWaylandModifiers + -> [RiverWMWaylandKeyBinding l] + -> [RiverWMWaylandKeyBinding l] +addHyperChordBindings hyperMask chordMask bindings = + bindings ++ M.elems chosen + where + existingKeys = + M.fromList + [ ((riverWMWaylandKeyModifiers binding, riverWMWaylandKeyKeysym binding), ()) + | binding <- bindings + ] + + chordBinding binding@RiverWMWaylandKeyBinding{riverWMWaylandKeyModifiers} = + binding + { riverWMWaylandKeyModifiers = + (riverWMWaylandKeyModifiers .&. complement hyperMask) .|. chordMask + } + + candidates = + [ ( (riverWMWaylandKeyModifiers chorded, riverWMWaylandKeyKeysym chorded) + , (score (riverWMWaylandKeyModifiers binding), chorded) + ) + | binding <- bindings + , riverWMWaylandKeyModifiers binding .&. hyperMask /= 0 + , let chorded = chordBinding binding + , M.notMember (riverWMWaylandKeyModifiers chorded, riverWMWaylandKeyKeysym chorded) existingKeys + ] + + chosen = + fmap snd $ + foldl' keepBest M.empty candidates + + keepBest selected (bindingKey, candidate@(candidateScore, _binding)) = + case M.lookup bindingKey selected of + Nothing -> M.insert bindingKey candidate selected + Just (bestScore, _) -> + if candidateScore < bestScore + then M.insert bindingKey candidate selected + else selected + + score modifiers = + length $ + filter (/= 0) + [ modifiers .&. shift + , modifiers .&. ctrl + , modifiers .&. alt + , modifiers .&. hyper + , modifiers .&. super + , modifiers .&. riverWMWaylandModifierMod5 + ] + +noMods, shift, ctrl, alt, hyper, super, hyperChord :: RiverWMWaylandModifiers +noMods = riverWMWaylandModifierNone +shift = riverWMWaylandModifierShift +ctrl = riverWMWaylandModifierCtrl +alt = riverWMWaylandModifierAlt +hyper = riverWMWaylandModifierHyper +super = riverWMWaylandModifierSuper +hyperChord = ctrl .|. alt .|. super diff --git a/dotfiles/config/river-xmonad/imalison-river-xmonad.cabal b/dotfiles/config/river-xmonad/imalison-river-xmonad.cabal new file mode 100644 index 00000000..103e5a48 --- /dev/null +++ b/dotfiles/config/river-xmonad/imalison-river-xmonad.cabal @@ -0,0 +1,18 @@ +cabal-version: 2.4 +name: imalison-river-xmonad +version: 0.1.0.0 +license: BSD-3-Clause +author: Ivan Malison +maintainer: IvanMalison@gmail.com +build-type: Simple + +executable imalison-river-xmonad + main-is: Main.hs + build-depends: base >= 4.12 && < 5 + , containers + , process + , X11 + , xmonad + , xmonad-contrib + ghc-options: -Wall -Wno-unused-do-bind -Wno-deprecations -Wno-missing-signatures -Wno-name-shadowing + default-language: Haskell2010 diff --git a/dotfiles/config/river-xmonad/overlay.nix b/dotfiles/config/river-xmonad/overlay.nix new file mode 100644 index 00000000..20505a54 --- /dev/null +++ b/dotfiles/config/river-xmonad/overlay.nix @@ -0,0 +1,13 @@ +_: pkgs: { + haskellPackages = pkgs.haskellPackages.override (old: { + overrides = pkgs.lib.composeExtensions (old.overrides or (_: _: {})) (self: _super: { + imalison-river-xmonad = self.callCabal2nix "imalison-river-xmonad" ( + pkgs.lib.sourceByRegex ./. + [ + "Main.hs" + "imalison-river-xmonad.cabal" + ] + ) { }; + }); + }); +} diff --git a/nixos/configuration.nix b/nixos/configuration.nix index 36b92817..738a6e80 100644 --- a/nixos/configuration.nix +++ b/nixos/configuration.nix @@ -37,6 +37,7 @@ ./rabbitmq.nix ./quickshell.nix ./remote-hyprland.nix + ./river-xmonad.nix ./secrets.nix ./ssh.nix ./sni.nix diff --git a/nixos/flake.lock b/nixos/flake.lock index 4a70cd8c..51c8468f 100644 --- a/nixos/flake.lock +++ b/nixos/flake.lock @@ -1808,7 +1808,8 @@ "systems": "systems_4", "vscode-server": "vscode-server", "xmonad": "xmonad_2", - "xmonad-contrib": "xmonad-contrib_2" + "xmonad-contrib": "xmonad-contrib_2", + "xmonad-river": "xmonad-river" } }, "rust-analyzer-src": { @@ -2120,6 +2121,32 @@ "type": "github" } }, + "xmonad-river": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "git-ignore-nix": [ + "git-ignore-nix" + ], + "nixpkgs": [ + "nixpkgs" + ], + "unstable": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1777476488, + "narHash": "sha256-2Adfi0BDJEM/ITFbXJkS21wVQ96oNXLLUh4HsHonCl4=", + "path": "/home/imalison/Projects/xmonad-river", + "type": "path" + }, + "original": { + "path": "/home/imalison/Projects/xmonad-river", + "type": "path" + } + }, "xmonad_2": { "inputs": { "flake-utils": [ diff --git a/nixos/flake.nix b/nixos/flake.nix index 342eec48..f0d16944 100644 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -147,6 +147,16 @@ }; }; + xmonad-river = { + url = "path:/home/imalison/Projects/xmonad-river"; + inputs = { + nixpkgs.follows = "nixpkgs"; + unstable.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + git-ignore-nix.follows = "git-ignore-nix"; + }; + }; + xmonad-contrib = { url = "github:IvanMalison/xmonad-contrib/withMyChanges"; inputs = { diff --git a/nixos/machines/strixi-minaj.nix b/nixos/machines/strixi-minaj.nix index d20157e3..15e0bf78 100644 --- a/nixos/machines/strixi-minaj.nix +++ b/nixos/machines/strixi-minaj.nix @@ -9,6 +9,7 @@ myModules.base.enable = true; myModules.desktop.enable = true; myModules.xmonad.enable = true; + myModules.riverXmonad.enable = true; myModules.extra.enable = false; myModules.code.enable = true; myModules.games.enable = false; diff --git a/nixos/river-xmonad.nix b/nixos/river-xmonad.nix new file mode 100644 index 00000000..ca5cb783 --- /dev/null +++ b/nixos/river-xmonad.nix @@ -0,0 +1,169 @@ +{ + 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 "$@" + ''; + + 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 "[$(${pkgs.coreutils}/bin/date --iso-8601=seconds)] exec imalison-river-xmonad" + exec ${riverXmonadPackage}/bin/imalison-river-xmonad + ''; + + 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 + + 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 || 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 + ''; + + 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 + riverXmonadPackage + wl-clipboard + wtype + ]; +}