{ config, lib, pkgs, ... }: with lib; let cfg = config.home.roles.i3_sway; shared_config = recursiveUpdate rec { modifier = "Mod4"; terminal = "ghostty"; defaultWorkspace = "workspace number 1"; keybindings = { "${shared_config.modifier}+Return" = "exec ${terminal}"; "${shared_config.modifier}+Shift+q" = "kill"; "${shared_config.modifier}+a" = "focus parent"; "${shared_config.modifier}+Shift+a" = "focus child"; "${shared_config.modifier}+h" = "focus left"; "${shared_config.modifier}+j" = "focus down"; "${shared_config.modifier}+k" = "focus up"; "${shared_config.modifier}+l" = "focus right"; "${shared_config.modifier}+Shift+h" = "move left"; "${shared_config.modifier}+Shift+j" = "move down"; "${shared_config.modifier}+Shift+k" = "move up"; "${shared_config.modifier}+Shift+l" = "move right"; "${shared_config.modifier}+Left" = "focus left"; "${shared_config.modifier}+Down" = "focus down"; "${shared_config.modifier}+Up" = "focus up"; "${shared_config.modifier}+Right" = "focus right"; "${shared_config.modifier}+Shift+Left" = "move left"; "${shared_config.modifier}+Shift+Down" = "move down"; "${shared_config.modifier}+Shift+Up" = "move up"; "${shared_config.modifier}+Shift+Right" = "move right"; #"${shared_config.modifier}+h" = "split h"; "${shared_config.modifier}+v" = "split v"; "${shared_config.modifier}+Shift+f" = "fullscreen toggle"; "${shared_config.modifier}+s" = "layout stacking"; "${shared_config.modifier}+w" = "layout tabbed"; "${shared_config.modifier}+e" = "layout toggle split"; "${shared_config.modifier}+Shift+space" = "floating toggle"; "${shared_config.modifier}+space" = "focus mode_toggle"; "${shared_config.modifier}+Shift+minus" = "move scratchpad"; "${shared_config.modifier}+minus" = "scratchpad show"; "${shared_config.modifier}+1" = "workspace number 1"; "${shared_config.modifier}+2" = "workspace number 2"; "${shared_config.modifier}+3" = "workspace number 3"; "${shared_config.modifier}+4" = "workspace number 4"; "${shared_config.modifier}+5" = "workspace number 5"; "${shared_config.modifier}+6" = "workspace number 6"; "${shared_config.modifier}+7" = "workspace number 7"; "${shared_config.modifier}+8" = "workspace number 8"; "${shared_config.modifier}+9" = "workspace number 9"; "${shared_config.modifier}+0" = "workspace number 10"; "${shared_config.modifier}+Shift+1" = "move container to workspace number 1"; "${shared_config.modifier}+Shift+2" = "move container to workspace number 2"; "${shared_config.modifier}+Shift+3" = "move container to workspace number 3"; "${shared_config.modifier}+Shift+4" = "move container to workspace number 4"; "${shared_config.modifier}+Shift+5" = "move container to workspace number 5"; "${shared_config.modifier}+Shift+6" = "move container to workspace number 6"; "${shared_config.modifier}+Shift+7" = "move container to workspace number 7"; "${shared_config.modifier}+Shift+8" = "move container to workspace number 8"; "${shared_config.modifier}+Shift+9" = "move container to workspace number 9"; "${shared_config.modifier}+Shift+0" = "move container to workspace number 10"; "${shared_config.modifier}+Shift+c" = "reload"; "${shared_config.modifier}+Shift+r" = "restart"; "${shared_config.modifier}+r" = "mode resize"; "XF86MonBrightnessUp" = "exec ddcutil setvcp 10 + 5"; "XF86MonBrightnessDown" = "exec ddcutil setvcp 10 - 5"; }; } cfg.extraSharedConfig; in { options.home.roles.i3_sway = { enable = mkEnableOption "i3 and Sway tiling window managers with waybar and rofi"; extraSharedConfig = mkOption { type = types.attrs; default = {}; description = "Extra configuration shared between i3 and sway"; }; extraI3Config = mkOption { type = types.attrs; default = {}; description = "Extra i3-specific configuration"; }; extraSwayConfig = mkOption { type = types.attrs; default = {}; description = "Extra sway-specific configuration"; }; }; config = mkIf cfg.enable { # i3blocks configuration file home.file.".config/i3blocks/config".text = '' # i3blocks config - replicating waybar setup separator_block_width=15 markup=pango [disk] command=df -h / | awk 'NR==2 {print "💾 " $5}' interval=30 separator=true [cpu] command=top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print "🧠 " int(100 - $1) "%"}' interval=2 separator=true [memory] command=free | awk 'NR==2 {printf "🐏 %.0f%%\n", $3*100/$2}' interval=5 separator=true [pulseaudio] command=${pkgs.writeShellScript "i3blocks-pulseaudio" '' volume=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -Po '\d+%' | head -1) muted=$(pactl get-sink-mute @DEFAULT_SINK@ | grep -o 'yes') if [ "$muted" = "yes" ]; then echo "🔇" else vol_num=''${volume%\%} if [ $vol_num -le 33 ]; then echo "🔈 $volume" elif [ $vol_num -le 66 ]; then echo "🔉 $volume" else echo "🔊 $volume" fi fi ''} interval=1 signal=10 separator=true [backlight] command=${pkgs.writeShellScript "i3blocks-backlight" '' if command -v ddcutil &>/dev/null; then # Handle mouse scroll events case $BLOCK_BUTTON in 4) ddcutil setvcp 10 + 5 ;; # Scroll up - increase brightness 5) ddcutil setvcp 10 - 5 ;; # Scroll down - decrease brightness esac # Display current brightness brightness=$(ddcutil getvcp 10 2>/dev/null | grep -oP 'current value =\s*\K\d+') if [ -n "$brightness" ]; then echo "â˜€ī¸ $brightness%" fi fi ''} interval=5 separator=true [network] command=${pkgs.writeShellScript "i3blocks-network" '' if iwgetid -r &>/dev/null; then ssid=$(iwgetid -r) signal=$(grep "^\s*w" /proc/net/wireless | awk '{print int($3 * 100 / 70)}') echo "đŸ“ļ $ssid ($signal%)" else ip=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v '127.0.0.1' | head -1) if [ -n "$ip" ]; then echo "🔌 $ip" else echo "❌" fi fi ''} interval=5 separator=true [battery] command=${pkgs.writeShellScript "i3blocks-battery" '' if [ -d /sys/class/power_supply/BAT0 ]; then capacity=$(cat /sys/class/power_supply/BAT0/capacity) status=$(cat /sys/class/power_supply/BAT0/status) if [ "$status" = "Charging" ]; then echo "⚡ $capacity%" else echo "🔋 $capacity%" fi fi ''} interval=10 separator=true [time] command=date '+%Y-%m-%d %H:%M' interval=1 separator=false ''; xsession.windowManager.i3 = let base_i3_config = recursiveUpdate shared_config { bars = [{ position = "bottom"; statusCommand = "${pkgs.i3blocks}/bin/i3blocks"; trayOutput = "primary"; # Enable system tray on primary output fonts = { names = [ "Fira Code" "monospace" ]; size = 11.0; }; colors = { background = "#000000"; statusline = "#ffffff"; separator = "#666666"; # Workspace button colors (matching waybar) focusedWorkspace = { border = "#285577"; background = "#285577"; text = "#ffffff"; }; activeWorkspace = { border = "#5f676a"; background = "#5f676a"; text = "#ffffff"; }; inactiveWorkspace = { border = "#222222"; background = "#222222"; text = "#888888"; }; urgentWorkspace = { border = "#900000"; background = "#900000"; text = "#ffffff"; }; }; }]; keybindings = shared_config.keybindings // { "${shared_config.modifier}+d" = "exec rofi -show drun"; "${shared_config.modifier}+Shift+e" = "exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'"; }; startup = [ # GNOME polkit authentication agent { command = "/run/current-system/sw/libexec/polkit-gnome-authentication-agent-1"; always = false; notification = false; } # Picom compositor for smooth rendering and no tearing (important for Nvidia) { command = "picom --backend glx -b"; always = false; notification = false; } # NetworkManager system tray applet { command = "nm-applet"; always = false; notification = false; } # Set wallpaper with feh { command = "feh --bg-scale ${../../wallpapers/metroid-samus-returns-kz-3440x1440.jpg}"; always = false; notification = false; } ]; }; in { enable = true; config = recursiveUpdate base_i3_config cfg.extraI3Config; }; wayland.windowManager.sway = let base_sway_config = recursiveUpdate shared_config { bars = []; # Disable default bar, use waybar instead keybindings = shared_config.keybindings // { "${shared_config.modifier}+d" = "exec wofi --show drun"; "${shared_config.modifier}+Shift+e" = "exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'"; }; input = { "type:keyboard" = { xkb_options = "caps:escape"; }; "type:touchpad" = { tap = "enabled"; tap_button_map = "lrm"; drag = "enabled"; natural_scroll = "disabled"; dwt = "enabled"; }; }; output = { "*" = { bg = "${../../wallpapers/metroid-samus-returns-kz-3440x1440.jpg} fill"; }; }; startup = [ # Launch waybar status bar { command = "waybar"; always = false; } ]; }; in { enable = true; extraOptions = [ "--unsupported-gpu" ]; config = recursiveUpdate base_sway_config cfg.extraSwayConfig; }; programs.waybar = { enable = true; systemd.enable = false; # Don't auto-start via systemd - only launch in sway settings = { mainBar = { layer = "top"; position = "bottom"; height = 30; spacing = 4; modules-left = [ "sway/workspaces" "sway/mode" ]; modules-center = [ ]; modules-right = [ "disk" "cpu" "memory" "pulseaudio" "custom/backlight-ddc" "backlight" "network" "battery" "tray" "clock" ]; "sway/workspaces" = { disable-scroll = true; all-outputs = true; }; "clock" = { format = "{:%Y-%m-%d %H:%M}"; tooltip-format = "{calendar}"; calendar = { mode = "year"; mode-mon-col = 3; weeks-pos = "right"; on-scroll = 1; format = { months = "{}"; days = "{}"; weeks = "W{}"; weekdays = "{}"; today = "{}"; }; }; }; "disk" = { interval = 30; format = "💾 {percentage_used}%"; path = "/"; tooltip-format = "Used: {used} / {total} ({percentage_used}%)\nFree: {free} ({percentage_free}%)"; }; "cpu" = { format = "🧠 {usage}%"; tooltip = false; }; "memory" = { format = "🐏 {percentage}%"; tooltip-format = "RAM: {used:0.1f}G / {total:0.1f}G"; }; "pulseaudio" = { format = "{icon} {volume}%"; format-muted = "🔇"; format-icons = { headphone = "🎧"; default = [ "🔈" "🔉" "🔊" ]; }; on-click = "pavucontrol"; }; "backlight" = { format = "â˜€ī¸ {percent}%"; tooltip = false; }; "custom/backlight-ddc" = { exec = pkgs.writeShellScript "waybar-backlight-ddc" '' if command -v ddcutil &>/dev/null; then # Display current brightness brightness=$(ddcutil getvcp 10 --brief 2>/dev/null | awk '{print $4}') if [ -n "$brightness" ]; then echo "â˜€ī¸ $brightness%" fi fi ''; interval = 5; format = "{}"; on-scroll-up = "ddcutil setvcp 10 + 5 2>/dev/null &"; on-scroll-down = "ddcutil setvcp 10 - 5 2>/dev/null &"; tooltip = false; }; "network" = { format-wifi = "đŸ“ļ {essid} ({signalStrength}%)"; format-ethernet = "🔌 {ipaddr}"; format-disconnected = "❌"; tooltip-format = "{ifname}: {ipaddr}/{cidr}"; }; "battery" = { states = { warning = 30; critical = 15; }; format = "{icon} {capacity}%"; format-charging = "⚡ {capacity}%"; format-icons = [ "đŸĒĢ" "🔋" "🔋" "🔋" "🔋" ]; }; "tray" = { spacing = 10; }; }; }; style = '' * { padding: 0 4px; font-family: "Fira Code", monospace; font-size: 13px; } #workspaces button { padding: 0 8px; background-color: #333333; color: #ffffff; border: none; } #workspaces button.focused { background-color: #285577; font-weight: bold; } #workspaces button.visible { background-color: #5f676a; } #workspaces button.urgent { background-color: #900000; } ''; }; programs.rofi = { enable = true; theme = "solarized"; extraConfig = { modi = "drun,run,window"; show-icons = true; drun-display-format = "{name}"; disable-history = false; hide-scrollbar = true; display-drun = " Apps"; display-run = " Run"; display-window = " Windows"; sidebar-mode = true; }; }; }; }