{ config, lib, pkgs, ... }: with lib; { options.lwad.applications.i3 = { enable = mkEnableOption "i3wm"; modifiers = { primary = mkOption { default = "Mod4"; description = "Primary modifier to use."; type = types.str; }; alternative = mkOption { default = "Mod1"; description = "Alternative modifier to use."; type = types.str; }; }; }; imports = [ ./audioControls.nix ./autoRun.nix ./backlight.nix ./displayProperties.nix ./moveDisplayUp.nix ./outputWorkspaceMappings.nix ]; config = let cfg = config.lwad.applications.i3; in mkIf cfg.enable { home-manager.users.${config.lwad.definitions.username} = let colors = config.lwad.definitions.theme.colors; in { programs.rofi = { enable = true; font = "FiraCode Nerd Font 11"; location = "top"; package = pkgs.rofi; terminal = config.lwad.applications.terminalEmulator.default; theme = builtins.toFile "rofi-theme" '' * { /* Colors. */ red: rgba ( 190, 42, 73, 100 % ); blue: rgba ( 167, 214, 247, 100 % ); /* Selected entry (normal). */ selected-normal-foreground: ${colors.text}; selected-normal-background: ${colors.menu_background}; /* Selected entry (active). */ selected-active-foreground: @selected-normal-foreground; selected-active-background: @selected-normal-background; /* Selected entry (urgent). */ selected-urgent-foreground: @selected-normal-foreground; selected-urgent-background: @selected-normal-background; /* Unselected entry (normal). */ foreground: ${colors.text}; background: ${colors.background}; normal-foreground: @foreground; normal-background: @background; /* Unselected entry (active). */ active-foreground: @normal-foreground; active-background: @normal-background; /* Unselected entry (urgent). */ urgent-foreground: @normal-foreground; urgent-background: @normal-background; /* Unselected entry (alternate). */ alternate-normal-foreground: @normal-foreground; alternate-normal-background: @normal-background; /* Unselected entry (alternate active). */ alternate-active-foreground: @normal-foreground; alternate-active-background: @normal-background; /* Unselected entry (alternate urgent). */ alternate-urgent-foreground: @normal-foreground; alternate-urgent-background: @normal-background; /* Search box. */ background-color: @normal-background; /* Other. */ spacing: 0; border-color: ${colors.highlight}; separatorcolor: @normal-background; /* Unknown. */ lightfg: rgba ( 0, 0, 0, 0 % ); lightbg: rgba ( 0, 0, 0, 0 % ); } window { background-color: @background; border: 3; padding: 3; } mainbox { border: 0; padding: 0; } message { border: 2px 0px 0px ; border-color: @separatorcolor; padding: 1px ; } textbox { text-color: @foreground; } listview { fixed-height: 0; border: 2px 0px 0px ; border-color: @separatorcolor; scrollbar: true; padding: 2px 0px 0px ; } element { border: 0; padding: @spacing; } element-text { background-color: inherit; text-color: inherit; } element.normal.normal { background-color: @normal-background; text-color: @normal-foreground; } element.normal.urgent { background-color: @urgent-background; text-color: @urgent-foreground; } element.normal.active { background-color: @active-background; text-color: @active-foreground; } element.selected.normal { background-color: @selected-normal-background; text-color: @selected-normal-foreground; } element.selected.urgent { background-color: @selected-urgent-background; text-color: @selected-urgent-foreground; } element.selected.active { background-color: @selected-active-background; text-color: @selected-active-foreground; } element.alternate.normal { background-color: @alternate-normal-background; text-color: @alternate-normal-foreground; } element.alternate.urgent { background-color: @alternate-urgent-background; text-color: @alternate-urgent-foreground; } element.alternate.active { background-color: @alternate-active-background; text-color: @alternate-active-foreground; } scrollbar { width: 0; border: 0; handle-color: @normal-foreground; handle-width: 0; padding: 0; } mode-switcher { border: 2px 0px 0px ; border-color: @separatorcolor; } button { spacing: 0; text-color: @normal-foreground; } button.selected { background-color: @selected-normal-background; text-color: @selected-normal-foreground; } inputbar { spacing: 0; text-color: @normal-foreground; padding: 1px ; } case-indicator { spacing: 0; text-color: @normal-foreground; } entry { spacing: 0; text-color: @normal-foreground; } prompt { spacing: 0; text-color: @normal-foreground; } inputbar { children: [ prompt,textbox-prompt-colon,entry,case-indicator ]; } textbox-prompt-colon { expand: false; str: ":"; margin: 0px 0.3em 0em 0em ; text-color: @normal-foreground; } ''; }; xsession.windowManager.i3 = { enable = true; config = let mod = cfg.modifiers.primary; alt = cfg.modifiers.alternative; in { bars = mkForce [ ]; colors = { background = colors.near_black; focused = { background = colors.near_black; border = colors.highlight; childBorder = colors.highlight; indicator = colors.highlight_darker; text = colors.text; }; focusedInactive = { background = colors.near_black; border = colors.near_black; childBorder = colors.near_black; indicator = colors.near_black; text = colors.text; }; placeholder = { background = colors.near_black; border = colors.near_black; childBorder = colors.near_black; indicator = colors.near_black; text = colors.text; }; unfocused = { background = colors.near_black; border = colors.near_black; childBorder = colors.near_black; indicator = colors.near_black; text = colors.text; }; urgent = { background = colors.near_black; border = "#F8E85F"; childBorder = "#F8E85F"; indicator = "#F4631A"; text = colors.text; }; }; floating = { modifier = mod; titlebar = false; }; keybindings = mkMerge [ # Meta. { "${mod}+Shift+c" = "reload"; "${mod}+Shift+r" = "restart"; } # Focus movement. { "${mod}+1" = "workspace 1"; "${mod}+2" = "workspace 2"; "${mod}+3" = "workspace 3"; "${mod}+4" = "workspace 4"; "${mod}+5" = "workspace 5"; "${alt}+1" = "workspace 6"; "${alt}+2" = "workspace 7"; "${alt}+3" = "workspace 8"; "${alt}+4" = "workspace 9"; "${alt}+5" = "workspace 10"; "${mod}+h" = "focus left"; "${mod}+j" = "focus down"; "${mod}+k" = "focus up"; "${mod}+l" = "focus right"; "${mod}+a" = "focus parent"; "${mod}+x" = "focus child"; "${mod}+space" = "focus mode_toggle"; } # Window movement. { "${mod}+Shift+1" = "move container to workspace 1"; "${mod}+Shift+2" = "move container to workspace 2"; "${mod}+Shift+3" = "move container to workspace 3"; "${mod}+Shift+4" = "move container to workspace 4"; "${mod}+Shift+5" = "move container to workspace 5"; "${alt}+Shift+1" = "move container to workspace 6"; "${alt}+Shift+2" = "move container to workspace 7"; "${alt}+Shift+3" = "move container to workspace 8"; "${alt}+Shift+4" = "move container to workspace 9"; "${alt}+Shift+5" = "move container to workspace 10"; "${mod}+Shift+h" = "move left"; "${mod}+Shift+j" = "move down"; "${mod}+Shift+k" = "move up"; "${mod}+Shift+l" = "move right"; "${mod}+Shift+space" = "floating toggle"; } # Window other. { "${mod}+Shift+q" = "kill"; "${mod}+f" = "fullscreen toggle"; "${mod}+e" = "layout toggle split"; "${mod}+v" = "split v"; } # Scratchpad management. { "${mod}+w" = "exec --no-startup-id ${ let i3-msg = "${pkgs.i3}/bin/i3-msg"; jq = "${pkgs.jq}/bin/jq"; in pkgs.writeScript "show_from_scratchpad.sh" '' class="$(${i3-msg} -t get_tree | ${jq} '.nodes | map(select(.name == "__i3")) | .[].nodes[].nodes[].floating_nodes[].nodes[].window_properties.class' --raw-output | ${pkgs.coreutils}/bin/sort | ${pkgs.coreutils}/bin/uniq | ${pkgs.rofi}/bin/rofi -dmenu -p "Window Class" -i)" [[ "$class" != "" ]] && ${i3-msg} [ class="$class" ] scratchpad show '' }"; "${mod}+Shift+w" = "exec --no-startup-id ${ let i3-msg = "${pkgs.i3}/bin/i3-msg"; jq = "${pkgs.jq}/bin/jq"; in pkgs.writeScript "move_to_scratchpad.sh" '' ${i3-msg} "move scratchpad" > /dev/null last_window_id=$(${i3-msg} -t get_tree \ | ${jq} 'recurse(.nodes[]) | select(.name == "__i3_scratch").floating_nodes[-1].nodes[0].id') if [ "$last_window_id" != "null" ]; then ${i3-msg} "[con_id=\"$last_window_id\"] scratchpad show, resize set 1280 px 75 ppt, move position center, move scratchpad" > /dev/null fi '' }"; "${mod}+Tab" = ''[class="(Alacritty|kitty)"] scratchpad show''; } # Draw to float. { "${mod}+q" = "exec --no-startup-id ${ let xdotool = "${pkgs.xdotool}/bin/xdotool"; in pkgs.writeScript "float_on_selection.sh" '' color="${colors.highlight}" color="''${color:1}" eval "$(${pkgs.perl}/bin/perl -e "printf('R=%.3f G=%.3f B=%.3f ',0x''${color:0:2}/255,0x''${color:2:2}/255,0x''${color:4:2}/255)")" slopped="$(${pkgs.slop}/bin/slop -t 0 -c ''${R},''${G},''${B} -b 2 -f "X=%x Y=%y W=%w H=%h")" [[ "$slopped" == "" ]] && exit 1 eval "$slopped" ${pkgs.i3}/bin/i3-msg floating enable \ && ${xdotool} getactivewindow windowmove $X $Y \ && ${xdotool} getactivewindow windowsize $W $H '' }"; } # Launch. { "${mod}+Return" = mkIf (config.lwad.applications.terminalEmulator.default != null) "exec --no-startup-id ${config.lwad.applications.terminalEmulator.default}"; "${mod}+Shift+Return" = "exec --no-startup-id ${pkgs.pcmanfm}/bin/pcmanfm"; "${mod}+d" = "exec --no-startup-id ${pkgs.rofi}/bin/rofi -show drun"; "${alt}+Shift+s" = mkIf config.lwad.applications.terminalEmulator.alacritty.enable "exec --no-startup-id alacritty -e sh -c '(read target && ssh \"$target\")'"; "${mod}+s" = "exec --no-startup-id ${ pkgs.writeScript "web_search.sh" '' searchString=$(${pkgs.rofi}/bin/rofi -dmenu -p "Search" -i -theme-str "listview {lines: 0;}" | ${pkgs.jq}/bin/jq -Rsj @uri | ${pkgs.gnused}/bin/sed -e "s/%0A\$//") [[ "$searchString" == "" ]] || ${pkgs.librewolf}/bin/librewolf --new-tab "https://duckduckgo.com/?q=''${searchString}" '' }"; } # Screenshots. { "${mod}+shift+s" = " exec --no-startup-id ${ let scrot = "${pkgs.scrot}/bin/scrot"; in pkgs.writeScript "copy_screenshot.sh" '' SCREENSHOT_FILE="/tmp/$(date +"%y%m%d%H%M%S").png" case "$(echo -e "desktop\nwindow\nselection" | ${pkgs.rofi}/bin/rofi -dmenu -p "Screenshot Type" -i)" in desktop) ${scrot} -F "$SCREENSHOT_FILE" || exit 1 ;; window) ${scrot} -u -b -F "$SCREENSHOT_FILE" || exit 1 ;; selection) color="${colors.highlight}" color="''${color:1}" eval "$(${pkgs.perl}/bin/perl -e "printf('R=%.3f G=%.3f B=%.3f ',0x''${color:0:2}/255,0x''${color:2:2}/255,0x''${color:4:2}/255)")" slopped="$(${pkgs.slop}/bin/slop -t 0 -c ''${R},''${G},''${B} -b 2 -f "X=%x Y=%y W=%w H=%h")" [[ "$slopped" == "" ]] && exit 1 eval "$slopped" ${scrot} -F "$SCREENSHOT_FILE" -a $X,$Y,$W,$H ;; *) exit 1 ;; esac ${pkgs.xclip}/bin/xclip -selection clipboard -i "$SCREENSHOT_FILE" -t image/png '' }"; } # Media. (let change_volume = command: step: boost: "exec --no-startup-id ${pamixer} ${ if boost then "--allow-boost" else "" } -${command} ${ if (command == "t") then "" else toString step } && ${show_volume_notification} ${command}"; pamixer = "${pkgs.pamixer}/bin/pamixer"; playerctl = "${pkgs.playerctl}/bin/playerctl"; show_volume_notification = pkgs.writeScript "show_volume_notification.sh" '' volume="$(${pamixer} --get-volume)" if [[ $(${pamixer} --get-mute) == "true" ]]; then icon="audio-volume-muted" elif [[ "$1" == "down" ]]; then icon="audio-volume-medium" else icon="audio-volume-high" fi ${pkgs.dunst}/bin/dunstify -a "changeVolume" -u low -i "$icon" -h string:x-dunst-stack-tag:changeVolume -h int:value:"$volume" "$volume%" -t 750 ''; in { "${alt}+k" = "${change_volume "i" 2 false}"; "${alt}+j" = "${change_volume "d" 2 false}"; "${alt}+shift+k" = "${change_volume "i" 10 false}"; "${alt}+shift+j" = "${change_volume "d" 10 false}"; "${alt}+control+k" = "${change_volume "i" 2 true}"; "${alt}+control+j" = "${change_volume "d" 2 true}"; "${alt}+control+shift+k" = "${change_volume "i" 10 true}"; "${alt}+control+shift+j" = "${change_volume "d" 10 true}"; "XF86AudioRaiseVolume" = "${change_volume "i" 2 false}"; "XF86AudioLowerVolume" = "${change_volume "d" 2 false}"; "XF86AudioMute" = "${change_volume "t" 0 false}"; "XF86AudioPlay" = "exec --no-startup-id ${playerctl} play-pause"; "XF86AudioPause" = "exec --no-startup-id ${playerctl} play-pause"; "XF86AudioPrev" = "exec --no-startup-id ${playerctl} previous"; "XF86AudioNext" = "exec --no-startup-id ${playerctl} next"; }) # Modes. { "${mod}+p" = "mode power"; "${mod}+r" = "mode resize"; } ]; modes = { resize = { "h" = "resize shrink width 20 px"; "j" = "resize grow height 20 px"; "k" = "resize shrink height 20 px"; "l" = "resize grow width 20 px"; "Escape" = ''mode "default"''; }; power = let systemctl = "${pkgs.systemd}/bin/systemctl"; lockCommand = "pkill xcompmgr; XSECURELOCK_PASSWORD_PROMPT=time_hex ${pkgs.xsecurelock}/bin/xsecurelock && (${pkgs.xcompmgr}/bin/xcompmgr & sleep 1 && ${pkgs.i3}/bin/i3-msg restart)"; in { "l" = ''exec --no-startup-id "${lockCommand}", mode "default"''; "e" = '' exec --no-startup-id ${pkgs.i3}/bin/i3-msg exit, mode "default"''; "s" = '' exec --no-startup-id "${lockCommand}", exec --no-startup-id ${systemctl} suspend, mode "default"''; "r" = ''exec --no-startup-id ${systemctl} reboot, mode "default"''; "Shift+s" = '' exec --no-startup-id ${systemctl} poweroff, mode "default"''; "Escape" = ''mode "default"''; }; }; startup = [ { command = "${pkgs.caffeine-ng}/bin/caffeine start --activate"; notification = false; } { command = "${pkgs.xcompmgr}/bin/xcompmgr"; notification = false; } ]; window = { commands = [{ command = "floating disable"; criteria = { class = "librewolf"; floating = true; title = "^Extension:.*Bitwarden.*"; }; }]; hideEdgeBorders = "smart"; titlebar = false; }; }; }; }; services.xserver = { displayManager.defaultSession = "none+i3"; windowManager.i3 = { enable = true; package = pkgs.i3; }; }; }; }