# Advanced Sway Setup — Full Custom Environment (LLM Agent Prompt) You are setting up a complete, fully custom Sway tiling window manager environment on a fresh Ubuntu installation. This is an advanced configuration with custom scripts, a Dracula-themed waybar, clipboard manager, night light, OSD feedback bar, and all the tools needed for a productive daily-driver laptop workflow. The user is experienced with Sway. Do not ask clarifying questions — just execute everything in order. --- ## Phase 1: Install All Required Packages ```bash sudo apt update && sudo apt install -y \ sway \ waybar \ wofi \ swaylock \ swayidle \ grim \ slurp \ wl-clipboard \ wtype \ cliphist \ brightnessctl \ pamixer \ pavucontrol \ playerctl \ mako-notifier \ libnotify-bin \ network-manager-gnome \ xdg-desktop-portal-wlr \ gammastep \ wob \ wdisplays \ dex \ fonts-font-awesome \ power-profiles-daemon \ flatpak ``` Then install Ghostty terminal (not in apt repos — install from official source): ```bash # Ghostty - check https://ghostty.org/download for latest install method # As of 2025+, install via the official .deb or build from source: curl -fsSL https://ghostty.org/install.sh | bash # If that doesn't work, fall back to the snap/flatpak or manual .deb: # sudo snap install ghostty --classic ``` Install NoiseTorch (microphone noise suppression): ```bash # Download latest release from https://github.com/noisetorch/NoiseTorch/releases # Install to ~/.local/bin/ mkdir -p ~/.local/bin curl -L -o /tmp/noisetorch.tgz "https://github.com/noisetorch/NoiseTorch/releases/latest/download/NoiseTorch_x64_v0.12.2.tgz" tar -xzf /tmp/noisetorch.tgz -C ~/.local/bin/ # Grant capabilities sudo setcap 'cap_sys_resource=+ep' ~/.local/bin/noisetorch ``` Install Meslo Nerd Font (used by terminal and waybar): ```bash mkdir -p ~/.local/share/fonts cd /tmp curl -fLo "MesloLGMNerdFont-Regular.ttf" \ "https://github.com/ryanoasis/nerd-fonts/releases/latest/download/Meslo.zip" unzip -o Meslo.zip -d ~/.local/share/fonts/ fc-cache -fv ``` Install Cloudflare WARP (VPN): ```bash curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | sudo gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflare-client.list sudo apt update && sudo apt install -y cloudflare-warp warp-cli registration new warp-cli connect ``` --- ## Phase 2: Create Directory Structure ```bash mkdir -p ~/.config/sway mkdir -p ~/.config/waybar mkdir -p ~/.config/ghostty mkdir -p ~/.config/mako mkdir -p ~/.config/gammastep mkdir -p ~/.config/wob ``` --- ## Phase 3: Sway Main Config Create `~/.config/sway/config`: ```ini # Sway Configuration — Advanced Custom Setup # Mod4 = Super key, Ghostty terminal, Wofi launcher ### Variables set $mod Mod4 set $left h set $down j set $up k set $right l set $term ghostty set $menu dmenu_path | wmenu | xargs swaymsg exec -- include /etc/sway/config-vars.d/* ### Output configuration output * bg /usr/share/backgrounds/sway/sway.jpg fill ### Input configuration # Keyboard — Caps Lock swapped with Ctrl, GB layout input type:keyboard { xkb_options ctrl:swapcaps xkb_layout gb } # Touchpad — tap, natural scroll, acceleration input type:touchpad { dwt enabled tap enabled natural_scroll enabled pointer_accel 0.4 scroll_factor 0.5 } # Monitor setup (adjust for your hardware) # Laptop internal display scaled for HiDPI output eDP-1 scale 1.9 pos 1920 0 # External monitor at native res to the left output HDMI-A-1 scale 1 pos 0 0 # Cursor theme seat seat0 xcursor_theme Adwaita 24 ### Key bindings — Basics bindsym $mod+Return exec $term bindsym $mod+Shift+q kill bindsym $mod+d exec $menu floating_modifier $mod normal bindsym $mod+Shift+c reload bindsym $mod+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' ### Navigation bindsym $mod+$left focus left bindsym $mod+$down focus down bindsym $mod+$up focus up bindsym $mod+$right focus right bindsym $mod+Left focus left bindsym $mod+Down focus down bindsym $mod+Up focus up bindsym $mod+Right focus right bindsym $mod+Shift+$left move left bindsym $mod+Shift+$down move down bindsym $mod+Shift+$up move up bindsym $mod+Shift+$right move right bindsym $mod+Shift+Left move left bindsym $mod+Shift+Down move down bindsym $mod+Shift+Up move up bindsym $mod+Shift+Right move right ### Workspaces bindsym $mod+1 workspace number 1 bindsym $mod+2 workspace number 2 bindsym $mod+3 workspace number 3 bindsym $mod+4 workspace number 4 bindsym $mod+5 workspace number 5 bindsym $mod+6 workspace number 6 bindsym $mod+7 workspace number 7 bindsym $mod+8 workspace number 8 bindsym $mod+9 workspace number 9 bindsym $mod+0 workspace number 10 bindsym $mod+Shift+1 move container to workspace number 1, workspace number 1 bindsym $mod+Shift+2 move container to workspace number 2, workspace number 2 bindsym $mod+Shift+3 move container to workspace number 3, workspace number 3 bindsym $mod+Shift+4 move container to workspace number 4, workspace number 4 bindsym $mod+Shift+5 move container to workspace number 5, workspace number 5 bindsym $mod+Shift+6 move container to workspace number 6, workspace number 6 bindsym $mod+Shift+7 move container to workspace number 7, workspace number 7 bindsym $mod+Shift+8 move container to workspace number 8, workspace number 8 bindsym $mod+Shift+9 move container to workspace number 9, workspace number 9 bindsym $mod+Shift+0 move container to workspace number 10, workspace number 10 # Dedicated backtick workspace bindsym $mod+grave workspace terminal bindsym $mod+Shift+grave move container to workspace terminal # Quick switch to last workspace bindsym $mod+Tab workspace back_and_forth ### Layout bindsym $mod+b splith bindsym $mod+v splitv bindsym $mod+w layout tabbed bindsym $mod+e layout toggle split bindsym $mod+f fullscreen bindsym $mod+Shift+space floating toggle bindsym $mod+space focus mode_toggle bindsym $mod+a focus parent ### Scratchpad bindsym $mod+Shift+minus move scratchpad bindsym $mod+minus scratchpad show ### Resize mode mode "resize" { bindsym $left resize shrink width 10px bindsym $down resize grow height 10px bindsym $up resize shrink height 10px bindsym $right resize grow width 10px bindsym Left resize shrink width 10px bindsym Down resize grow height 10px bindsym Up resize shrink height 10px bindsym Right resize grow width 10px bindsym Return mode "default" bindsym Escape mode "default" } bindsym $mod+r mode "resize" ### Picture-in-Picture for_window [title="^Picture-in-Picture$"] floating enable, sticky enable, border pixel 2 for_window [title="^Picture-in-Picture$"] resize set 480 270, move position 1400 20 ### Lock Screen & Idle # Lock screen manually bindsym $mod+Escape exec swaylock -f -i /usr/share/backgrounds/sway/sway.jpg -s fill # Lock + suspend bindsym $mod+Shift+s exec swaylock -f -i /usr/share/backgrounds/sway/sway.jpg -s fill && systemctl suspend # Idle daemon — lock after 15min, screen off after 15.5min exec swayidle -w \ timeout 900 'swaylock -f -i /usr/share/backgrounds/sway/sway.jpg -s fill' \ timeout 930 'swaymsg "output * dpms off"' \ resume 'swaymsg "output * dpms on"' \ before-sleep 'swaylock -f -i /usr/share/backgrounds/sway/sway.jpg -s fill' ### Screenshot (region to clipboard) bindsym $mod+s exec grim -g "$(slurp)" - | wl-copy ### App Launcher (Wofi — toggles on/off) bindsym Control+space exec pkill wofi || wofi --show drun ### GUI Monitor Configuration bindsym $mod+m exec wdisplays ### Clipboard History Picker bindsym $mod+c exec ~/.config/sway/clipboard-picker.sh ### Media / Volume / Brightness (with OSD via wob) bindsym XF86AudioRaiseVolume exec sh -c 'pamixer --increase 5; pamixer --get-volume > /tmp/wobpipe' bindsym XF86AudioLowerVolume exec sh -c 'pamixer --decrease 5; pamixer --get-volume > /tmp/wobpipe' bindsym XF86AudioMute exec sh -c 'pamixer --toggle-mute; if [ "$(pamixer --get-mute)" = "true" ]; then echo 0 > /tmp/wobpipe; else pamixer --get-volume > /tmp/wobpipe; fi' bindsym XF86AudioPlay exec playerctl play-pause bindsym XF86AudioNext exec playerctl next bindsym XF86AudioPrev exec playerctl previous bindsym XF86MonBrightnessUp exec sh -c 'brightnessctl set +5%; brightnessctl -m | cut -d, -f4 | tr -d "%" > /tmp/wobpipe' bindsym XF86MonBrightnessDown exec sh -c 'brightnessctl set 5%-; brightnessctl -m | cut -d, -f4 | tr -d "%" > /tmp/wobpipe' ### Autostart exec dex --autostart --environment sway exec nm-applet exec warp-cli connect exec gammastep exec sh -c 'sleep 4 && noisetorch -i' exec ~/.config/sway/battery-monitor.sh # Kill dunst so mako takes over notifications exec pkill -x dunst # Clipboard history daemon exec wl-paste --watch cliphist store # OSD overlay bar (wob) exec mkfifo /tmp/wobpipe && tail -f /tmp/wobpipe | wob ### Status Bar # Kill default swaybar from distro includes exec_always killall swaybar # Launch waybar (kill old on reload) exec_always pkill waybar; waybar ### Appearance default_border none default_floating_border none include /etc/sway/config.d/* ``` --- ## Phase 4: Custom Scripts ### `~/.config/sway/battery-monitor.sh` ```bash #!/bin/bash LAST_NOTIFIED=-1 while true; do BATTERY_PATH="/sys/class/power_supply/BAT0" if [ -d "$BATTERY_PATH" ]; then CAPACITY=$(cat "$BATTERY_PATH/capacity") STATUS=$(cat "$BATTERY_PATH/status") if [ "$STATUS" = "Discharging" ]; then if [ "$CAPACITY" -le 5 ] && [ "$LAST_NOTIFIED" -ne "$CAPACITY" ]; then notify-send -u critical -t 0 "🔋 CRITICAL BATTERY!" "Battery at ${CAPACITY}% - PLUG IN NOW!" LAST_NOTIFIED=$CAPACITY elif [ "$CAPACITY" -le 10 ] && [ "$LAST_NOTIFIED" -ne "$CAPACITY" ]; then notify-send -u critical -t 10000 "⚠️ Low Battery Warning" "Battery at ${CAPACITY}% - Please charge soon" LAST_NOTIFIED=$CAPACITY fi else LAST_NOTIFIED=-1 fi fi sleep 60 done ``` ### `~/.config/sway/clipboard-picker.sh` ```bash #!/bin/bash # Clipboard history picker using cliphist + wofi selection=$(cliphist list | wofi --show dmenu --prompt="Clipboard" --width=600 --height=400 --cache-file=/dev/null) if [ -n "$selection" ]; then echo "$selection" | cliphist decode | wl-copy sleep 0.1 wtype -M ctrl v -m ctrl fi ``` ### `~/.config/sway/gammastep-toggle.sh` ```bash #!/bin/bash # Toggle gammastep on/off and emit waybar signal if pgrep -x gammastep > /dev/null; then pkill -x gammastep else gammastep & disown fi pkill -RTMIN+10 waybar ``` ### `~/.config/sway/performance-toggle.sh` ```bash #!/bin/bash # Toggle power profile: power-saver -> balanced -> performance -> power-saver current=$(powerprofilesctl get) case "$current" in power-saver) powerprofilesctl set balanced ;; balanced) powerprofilesctl set performance ;; performance) powerprofilesctl set power-saver ;; esac pkill -RTMIN+11 waybar ``` Make all scripts executable: ```bash chmod +x ~/.config/sway/battery-monitor.sh chmod +x ~/.config/sway/clipboard-picker.sh chmod +x ~/.config/sway/gammastep-toggle.sh chmod +x ~/.config/sway/performance-toggle.sh ``` --- ## Phase 5: Waybar Config (Dracula Theme, Bottom Bar) ### `~/.config/waybar/config` ```json { "layer": "top", "position": "bottom", "height": 24, "spacing": 4, "modules-left": ["sway/workspaces", "sway/mode", "idle_inhibitor", "custom/nightlight", "custom/powerprofile"], "modules-center": ["mpris"], "modules-right": ["custom/vpn", "network", "pulseaudio", "battery", "clock", "tray"], "sway/workspaces": { "disable-scroll": true, "all-outputs": true }, "idle_inhibitor": { "format": "{icon}", "format-icons": { "activated": "☕", "deactivated": "💤" }, "tooltip-format-activated": "Sleep prevention: ON", "tooltip-format-deactivated": "Sleep prevention: OFF" }, "custom/nightlight": { "exec": "if pgrep -x gammastep > /dev/null; then echo '{\"text\": \"🌙\", \"class\": \"on\", \"tooltip\": \"Night Light: ON\"}'; else echo '{\"text\": \"☀️\", \"class\": \"off\", \"tooltip\": \"Night Light: OFF\"}'; fi", "return-type": "json", "interval": "once", "signal": 10, "on-click": "~/.config/sway/gammastep-toggle.sh" }, "custom/powerprofile": { "exec": "profile=$(powerprofilesctl get); case $profile in performance) echo '{\"text\": \"🚀\", \"class\": \"performance\", \"tooltip\": \"Power: Performance\"}';; balanced) echo '{\"text\": \"⚡\", \"class\": \"balanced\", \"tooltip\": \"Power: Balanced\"}';; power-saver) echo '{\"text\": \"🔋\", \"class\": \"power-saver\", \"tooltip\": \"Power: Power Saver\"}';; esac", "return-type": "json", "interval": "once", "signal": 11, "on-click": "~/.config/sway/performance-toggle.sh" }, "mpris": { "format": "{player_icon} {artist} - {title}", "format-paused": "{status_icon} {artist} - {title}", "player-icons": { "default": "▶", "spotify": "" }, "status-icons": { "paused": "⏸" }, "max-length": 50, "on-click": "playerctl play-pause", "on-click-right": "playerctl next" }, "custom/vpn": { "exec": "if warp-cli status 2>/dev/null | grep -q 'Connected'; then echo '󰖂 Warp'; elif nmcli con show --active | grep -q 'vpn\\|tun\\|wireguard'; then echo '󰖂 VPN'; else echo '󰖂 None'; fi", "interval": 5, "format": "{}", "tooltip": false }, "tray": { "spacing": 10 }, "clock": { "interval": 1, "tooltip-format": "{:%Y %B}\n{calendar}", "format": "{:%Y-%m-%d %H:%M:%S}", "format-alt": "{:%A, %B %d, %Y %H:%M:%S}" }, "battery": { "states": { "warning": 30, "critical": 15 }, "format": "{capacity}% {icon}", "format-charging": "{capacity}% ⚡", "format-plugged": "{capacity}% ", "format-alt": "{time} {icon}", "format-icons": ["", "", "", "", ""] }, "network": { "format-wifi": "{essid} ({signalStrength}%) ", "format-ethernet": "{ipaddr}/{cidr} ", "tooltip-format": "{ifname} via {gwaddr} ", "format-linked": "{ifname} (No IP) ", "format-disconnected": "Disconnected ⚠", "format-alt": "{ifname}: {ipaddr}/{cidr}" }, "pulseaudio": { "format": "{volume}% {icon}", "format-bluetooth": "{volume}% {icon}", "format-muted": " Muted", "format-icons": { "headphone": "", "default": ["", ""] }, "on-click": "pavucontrol" } } ``` ### `~/.config/waybar/style.css` ```css * { border: none; border-radius: 0; font-family: "Meslo LGM Nerd Font", "DejaVu Sans Mono", monospace; font-size: 13px; min-height: 0; } window#waybar { background-color: #000000; color: #f8f8f2; } #workspaces button { padding: 0 5px; background-color: transparent; color: #888888; } #workspaces button:hover { background: rgba(255, 255, 255, 0.1); color: #ffffff; } #workspaces button.focused { background-color: #111111; box-shadow: inset 0 -2px #ffffff; color: #ffffff; } #clock { padding: 0 10px; background-color: #282a36; color: #f1fa8c; } #battery { padding: 0 10px; background-color: #44475a; color: #8be9fd; } #network { padding: 0 10px; background-color: #282a36; color: #50fa7b; } #pulseaudio { padding: 0 10px; background-color: #44475a; color: #ff79c6; } #tray { padding: 0 10px; background-color: #282a36; } #mode { padding: 0 10px; background-color: #ff5555; color: #f8f8f2; font-weight: bold; } #idle_inhibitor { padding: 0 10px; background-color: #282a36; color: #bd93f9; } #idle_inhibitor.activated { background-color: #50fa7b; color: #000000; } #custom-nightlight { padding: 0 10px; } #custom-nightlight.on { background-color: #282a36; color: #ffb86c; } #custom-nightlight.off { background-color: #282a36; color: #6272a4; } #mpris { padding: 0 10px; background-color: #44475a; color: #ffb86c; } #custom-vpn { padding: 0 10px; background-color: #282a36; color: #8be9fd; } #battery.charging { color: #50fa7b; } #battery.warning:not(.charging) { color: #ffb86c; } #battery.critical:not(.charging) { background-color: #ff5555; color: #f8f8f2; animation-name: blink; animation-duration: 0.5s; animation-timing-function: linear; animation-iteration-count: infinite; animation-direction: alternate; } @keyframes blink { to { background-color: #f8f8f2; color: #ff5555; } } #custom-powerprofile { padding: 0 10px; background-color: #282a36; } #custom-powerprofile.performance { color: #ff5555; } #custom-powerprofile.balanced { color: #f1fa8c; } #custom-powerprofile.power-saver { color: #50fa7b; } ``` --- ## Phase 6: Ghostty Terminal Config Create `~/.config/ghostty/config`: ```ini # Fonts font-family = "Meslo LGM Nerd Font" font-size = 13 # Fix line-wrapping/cursor position issues grapheme-width-method = unicode # OLED Jet Black Theme background = 000000 foreground = f8f8f2 # Window Appearance window-decoration = false window-padding-x = 8 window-padding-y = 8 # Native Tabs Configuration window-theme = dark gtk-tabs-location = bottom # Trackpad and mouse scroll tuning mouse-scroll-multiplier = precision:0.5,discrete:1.0 # Disable shell integration (conflicts with powerlevel10k prompt) shell-integration = none # Use xterm-256color for SSH compatibility term = xterm-256color # Ctrl+Backspace deletes previous word keybind = ctrl+backspace=text:\x17 ``` --- ## Phase 7: Mako Notification Config (Dracula Theme) Create `~/.config/mako/config`: ```ini # Mako notification daemon — Dracula theme font=Meslo LGM Nerd Font 11 background-color=#282a36 text-color=#f8f8f2 border-color=#6272a4 border-size=2 border-radius=8 padding=12 margin=8 width=380 height=150 max-visible=4 default-timeout=8000 layer=overlay anchor=top-right on-notify=none on-button-left=invoke-default-action on-button-right=dismiss [urgency=low] border-color=#44475a default-timeout=5000 [urgency=normal] border-color=#6272a4 [urgency=critical] border-color=#ff5555 background-color=#44475a default-timeout=0 ``` --- ## Phase 8: Gammastep (Night Light) Config Create `~/.config/gammastep/config.ini`: ```ini [general] location-provider=manual ; Warm baseline during day, deeper red at night temp-day=5500 temp-night=3400 ; Smooth transitions matching sunrise/sunset dawn-time=06:00-07:00 dusk-time=19:00-20:00 ; Slight brightness reduction at night brightness-day=1.0 brightness-night=0.9 gamma=1.0 fade=1 adjustment-method=wayland [manual] lat=51.5 lon=-0.1 [wayland] ``` --- ## Phase 9: Wob (OSD Overlay Bar) Config Create `~/.config/wob/wob.ini`: ```ini timeout = 1000 max = 100 width = 400 height = 30 border_offset = 0 border_size = 2 bar_padding = 2 anchor = bottom margin = 50 bar_color = bd93f9FF background_color = 282a36FF border_color = 6272a4FF overflow_bar_color = ff5555FF ``` --- ## Phase 10: Final Steps ### Set correct permissions ```bash chmod +x ~/.config/sway/*.sh ``` ### Verify sway config syntax ```bash sway --validate 2>&1 || echo "Config has errors — review above" ``` ### Enable required systemd services ```bash # Power profiles daemon sudo systemctl enable --now power-profiles-daemon # NetworkManager (should already be running from GNOME) sudo systemctl enable --now NetworkManager ``` ### Notes for first login 1. Log out, select "Sway" from the GDM gear menu, log in. 2. Key bindings quick reference: - `Ctrl+Space` — App launcher (Wofi) - `Mod+Return` — Terminal (Ghostty) - `Mod+S` — Screenshot region to clipboard - `Mod+C` — Clipboard history - `Mod+Escape` — Lock screen - `Mod+Shift+S` — Lock + suspend - `Mod+M` — Monitor config GUI - `Mod+Tab` — Switch to last workspace - `Mod+Shift+C` — Reload config - `Mod+Shift+E` — Exit sway 3. Waybar clickable modules: - Click 🌙/☀️ to toggle night light - Click 🚀/⚡/🔋 to cycle power profiles - Click ▶ in center to play/pause media - Click volume to open pavucontrol 4. **Adjust `output` lines in sway config** for your specific monitor setup. Use `swaymsg -t get_outputs` to identify connected displays, then edit accordingly. 5. **Adjust gammastep lat/lon** in `~/.config/gammastep/config.ini` for your location. --- ## Rules for the Agent - Create backups of any existing config files before overwriting: `cp file file.backup.$(date +%Y%m%d-%H%M%S)` - Write all files exactly as specified — do not simplify, summarize, or omit sections - Make all `.sh` files executable - Run `sway --validate` at the end to confirm the config is valid - If any package fails to install, note it and continue with the rest - Do NOT install or configure a display manager — assume GDM is already present from GNOME - Do NOT remove GNOME — it stays as a fallback