{ lib, pkgs, config, ...}: let inherit (lib) mkIf mkEnableOption mkOption types optionals optionalAttrs; cfg = config.chuj.stuff.i3; user = config.chuj.system.user; in { options.chuj.stuff.i3 = { enable = mkEnableOption "i3"; workspaceOutput = mkOption { type = types.attrsOf types.str; default = {}; }; communicators = mkOption { type = types.submodule { options = { autostart = mkOption { type = types.bool; default = false; }; workspace = mkOption { type = types.str; default = ""; }; }; }; default = {}; }; battery = mkEnableOption "battery"; mediaKeys = mkOption { type = types.submodule { options = let mkKeyOpt = default: mkOption { inherit default; type = types.str; }; in { musicMenu = mkKeyOpt "F5"; musicPlay = mkKeyOpt "F7"; musicPrev = mkKeyOpt "F6"; musicNext = mkKeyOpt "F8"; audioMute = mkKeyOpt "F9"; audioDown = mkKeyOpt "F10"; audioUp = mkKeyOpt "F11"; }; }; default = {}; }; }; config = let hmCfg = config.home-manager.users.${user}; exec = what: "exec --no-startup-id ${what}"; i3-msg = lib.getExe' hmCfg.xsession.windowManager.i3.package "i3-msg"; terminal = if (config.chuj.stuff.alacritty.enable) then (lib.getExe hmCfg.programs.alacritty.package) else (lib.getExe' hmCfg.xsession.windowManager.i3.package "i3-sensible-terminal"); firefox = lib.getExe hmCfg.programs.firefox.package; schildichat = lib.getExe pkgs.schildichat-desktop; flameshot = lib.getExe hmCfg.services.flameshot.package; dunst = lib.getExe' hmCfg.services.dunst.package "dunst"; dunstify = lib.getExe' hmCfg.services.dunst.package "dunstify"; dmenu_run = lib.getExe' pkgs.dmenu "dmenu_run"; dmenu = lib.getExe pkgs.dmenu; yad = lib.getExe pkgs.yad; mpc = lib.getExe pkgs.mpc-cli; mpdstatus = lib.getExe config.chuj.stuff.mpd.mpdstatusPackage; pamixer = lib.getExe pkgs.pamixer; xdotool = lib.getExe pkgs.xdotool; xkill = lib.getExe pkgs.xorg.xkill; killall = lib.getExe pkgs.killall; sync = lib.getExe' pkgs.coreutils "sync"; tail = lib.getExe' pkgs.coreutils "tail"; find = lib.getExe' pkgs.findutils "find"; sed = lib.getExe pkgs.gnused; i3custom-window-actions = lib.getExe (pkgs.writeShellScriptBin "i3custom-window-actions" '' ${yad} --splash --mouse \ --title="i3 window actions" \ --gtkrc="${../../files/i3gtk.css}" \ --text="i3 window actions" \ --button float:0 --button kill:1 case $? in 0) ${i3-msg} "floating toggle";; 1) ${i3-msg} "kill";; -1) ;; esac ''); musicsel = lib.getExe (pkgs.writeShellScriptBin "musicsel" '' cd ~/Music path=$(${find} . -maxdepth 1 -type d | ${tail} -n +2 | ${sed} 's|^./||g' | ${dmenu}) ${dunstify} "playing from \"$path\"" ${mpc} update ${mpc} rescan ${mpc} add "$path" ${mpc} random on ${mpc} repeat on ${mpc} play ''); i3custom-center-mouse-on-focus = lib.getExe (pkgs.writeShellScriptBin "i3custom-center-mouse-on-focus" '' eval $(${xdotool} getwindowfocus getwindowgeometry --shell) MX=$(($X + $WIDTH / 2)) MY=$(($Y + $HEIGHT / 2)) ${xdotool} mousemove $MX $MY ''); in mkIf cfg.enable { services.xserver.windowManager.i3.enable = true; home-manager.users.${user} = { xsession.windowManager.i3 = { enable = true; config = rec { assigns = mkIf (cfg.communicators.workspace != "") { ${cfg.communicators.workspace} = [ { class = "discord"; } { class = "SchildiChat"; } { class = "element"; } ]; }; window = { border = 1; titlebar = true; }; floating = { border = 1; titlebar = true; criteria = [ { class = "zenity"; } { class = "fceux"; } { title = "Friends List"; } # steam ]; }; gaps = { inner = 0; outer = 0; }; fonts = { names = [ "monospace" ]; size = 10.0; }; defaultWorkspace = "workspace number 1"; # transforms # { "" = ""; } # into # [{ workspace = ""; output = ""; }] workspaceOutputAssign = lib.mapAttrsToList (workspace: output: { inherit workspace output; }) cfg.workspaceOutput; focus = { followMouse = true; mouseWarping = true; newWindow = "urgent"; }; startup = [] ++ optionals config.chuj.stuff.dunst.enable [{ command = "${dunst} &"; notification = false; }] ++ optionals config.chuj.stuff.flameshot.enable [{ command = "${flameshot} &"; notification = false; }] ++ optionals cfg.communicators.autostart [{ command = "${schildichat} &"; notification = false; }] ++ optionals config.chuj.stuff.mpd.enable [{ command = "${mpdstatus} &"; notification = false; }]; modes = { resize = { "Left" = "resize shrink width 10 px or 10 ppt; ${exec i3custom-center-mouse-on-focus}"; "Right" = "resize grow width 10 px or 10 ppt; ${exec i3custom-center-mouse-on-focus}"; "Up" = "resize shrink height 10 px or 10 ppt; ${exec i3custom-center-mouse-on-focus}"; "Down" = "resize grow height 10 px or 10 ppt; ${exec i3custom-center-mouse-on-focus}"; "Escape" = "mode default"; }; }; modifier = "Mod4"; floating.modifier = "Mod4"; keybindings = with config.chuj.stuff.i3.mediaKeys; let mod = modifier; update_mpdstatus = "${killall} -USR1 mpdstatus && ${sync} ${config.chuj.stuff.mpd.statusFile}"; update_i3status = "${killall} -USR1 i3status"; center_mouse = exec i3custom-center-mouse-on-focus; in { # Program keybinds "${mod}+Return" = exec terminal; "${mod}+d" = exec dmenu_run; "${mod}+Shift+s" = mkIf config.chuj.stuff.flameshot.enable (exec "${flameshot} gui"); "${mod}+b" = mkIf config.chuj.stuff.firefox.enable (exec firefox); "--release ${mod}+k" = exec xkill; # Volume control "Ctrl+${audioMute}" = exec "${pamixer} --toggle-mute && ${update_i3status}"; "Ctrl+${audioDown}" = exec "${pamixer} --decrease 5 && ${update_i3status}"; "Ctrl+${audioUp}" = exec "${pamixer} --increase 5 && ${update_i3status}"; # General i3 keybindings "${mod}+Shift+q" = "kill; ${center_mouse}"; "${mod}+f" = "fullscreen toggle; ${center_mouse}"; "${mod}+r" = "mode resize"; "${mod}+Shift+r" = "restart"; "--border button3" = exec i3custom-window-actions; "${mod}+Left" = "focus left; ${center_mouse}"; "${mod}+Right" = "focus right; ${center_mouse}"; "${mod}+Up" = "focus up; ${center_mouse}"; "${mod}+Down" = "focus down; ${center_mouse}"; "${mod}+Shift+Left" = "move left; ${center_mouse}"; "${mod}+Shift+Right" = "move right; ${center_mouse}"; "${mod}+Shift+Up" = "move up; ${center_mouse}"; "${mod}+Shift+Down" = "move down; ${center_mouse}"; "${mod}+q" = "layout stacking; ${center_mouse}"; "${mod}+w" = "layout tabbed; ${center_mouse}"; "${mod}+e" = "layout toggle split; ${center_mouse}"; "${mod}+Shift+space" = "floating toggle; ${center_mouse}"; "${mod}+space" = "focus mode_toggle; ${center_mouse}"; "${mod}+1" = "workspace number 1; ${center_mouse}"; "${mod}+2" = "workspace number 2; ${center_mouse}"; "${mod}+3" = "workspace number 3; ${center_mouse}"; "${mod}+4" = "workspace number 4; ${center_mouse}"; "${mod}+5" = "workspace number 5; ${center_mouse}"; "${mod}+6" = "workspace number 6; ${center_mouse}"; "${mod}+7" = "workspace number 7; ${center_mouse}"; "${mod}+8" = "workspace number 8; ${center_mouse}"; "${mod}+9" = "workspace number 9; ${center_mouse}"; "${mod}+0" = "workspace number 10; ${center_mouse}"; "${mod}+Shift+1" = "move container to workspace number 1; ${center_mouse}"; "${mod}+Shift+2" = "move container to workspace number 2; ${center_mouse}"; "${mod}+Shift+3" = "move container to workspace number 3; ${center_mouse}"; "${mod}+Shift+4" = "move container to workspace number 4; ${center_mouse}"; "${mod}+Shift+5" = "move container to workspace number 5; ${center_mouse}"; "${mod}+Shift+6" = "move container to workspace number 6; ${center_mouse}"; "${mod}+Shift+7" = "move container to workspace number 7; ${center_mouse}"; "${mod}+Shift+8" = "move container to workspace number 8; ${center_mouse}"; "${mod}+Shift+9" = "move container to workspace number 9; ${center_mouse}"; "${mod}+Shift+0" = "move container to workspace number 10; ${center_mouse}"; } // (optionalAttrs config.chuj.stuff.mpd.enable { # MPD controls "Ctrl+${musicMenu}" = exec "${musicsel} && ${update_mpdstatus} && ${update_i3status}"; "Ctrl+${musicPrev}" = exec "${mpc} prev && ${update_mpdstatus} && ${update_i3status}"; "Ctrl+${musicNext}" = exec "${mpc} next && ${update_mpdstatus} && ${update_i3status}"; "Ctrl+${musicPlay}" = exec "${mpc} toggle && ${update_mpdstatus} && ${update_i3status}"; "Ctrl+Shift+${musicPlay}" = exec "${mpc} clear && ${update_mpdstatus} && ${update_i3status}"; "Ctrl+Shift+${musicPrev}" = exec "${mpc} seek -3"; "Ctrl+Shift+${musicNext}" = exec "${mpc} seek +3"; "Ctrl+Shift+${audioDown}" = exec ''${mpc} volume -5 && ${dunstify} "mpc $(${mpc} volume)" -r 420''; "Ctrl+Shift+${audioUp}" = exec ''${mpc} volume +5 && ${dunstify} "mpc $(${mpc} volume)" -r 420''; }); bars = [{ inherit fonts; trayOutput = "primary"; statusCommand = "${lib.getExe hmCfg.programs.i3status.package}"; extraConfig = '' height 24 ''; }]; }; }; programs.i3status = { enable = true; enableDefault = false; general = { interval = 1; colors = true; color_good = "#91d956"; color_bad = "#f43841"; color_degraded = "#ffdd33"; output_format = "i3bar"; }; modules = { "cpu_usage" = { position = 0; settings = { format = "cpu: %usage"; }; }; "memory" = { position = 1; settings = { format = "mem: %used"; threshold_degraded = "1G"; format_degraded = "MEMORY < %available"; }; }; "volume master" = { position = 2; settings = { color_degraded = "#333333"; format = "vol: %volume"; format_muted = "vol: ---"; }; }; "tztime local" = { position = 4; settings = { format = "%Y-%m-%d %H:%M:%S (%A)"; }; }; } // (optionalAttrs cfg.battery { "battery 0" = { position = 3; settings = { format = "bat%status: %percentage"; status_chr = "(+)"; status_bat = ""; status_unk = "(?)"; status_full = "(+)"; status_idle = ""; low_threshold = 30; }; }; }) // (optionalAttrs config.chuj.stuff.mpd.enable { "read_file mpd" = { position = -1; settings = { format = "%content"; format_bad = "mpdstatus file not found"; path = config.chuj.stuff.mpd.statusFile; color_good = "#ffffff"; }; }; }); }; }; }; }