swaywm

Sway↗ is a tiling window manager and Wayland compositor, inspired by i3, and written in C.

Motivation

After using dwm for sometime, installed sway in Linux Mint for the first time to try Wayland. I moved to Arch Linux mainly to try the updated sway version. Since end'24, using swayWM in Alpine Linux .

Tools

This page includes literate configuration to almost all the components used in my sway setup. The configuration file for sway tangles from Sway configuration page. Sway compositor is launched using a startup script .

All configuration files in the setup are version tracked using git .

SwayWM has been configured using the following tools:

  • Terminal - foot
  • Notification daemon - mako
  • Locking tool - swaylockd
  • Feed generator for swaybar - i3blocks with custom scripts
  • For displaying time in swaybar - slclock, a vibe coded C utility
  • Launcher menu - tofi
  • Logout menu - tofi-exit - tofi based custom script
  • power management tool - powerctl↗

To achieve consistent nord theme:

Launcher script

The Sway launcher script below tangles to ~/.local/bin/SwayWM↗ . This is symlinked to /usr/local/bin/SwayWM.

Environment setup

Wayland compositors require XDG_RUNTIME_DIR to be set before they can be launched. Since i use seatd as seat manager, this needs to be set first. Earlier i was using pam-rundir package.

export XDG_RUNTIME_DIR=$(mkrundir)

The following wayland specific variables were taken from the seatd author’s site↗ .

export XDG_SESSION_TYPE=wayland
export XDG_SESSION_DESKTOP=sway
export XDG_CURRENT_DESKTOP=sway

Application specific variables

The following setting needs to be enabled to run DRM content as documented in Alpine Linux wiki.

The below settings were earlier copied during initial configuration. Disabling them did not cause any errors.

The following settings were added to handle various errors on enabling debug. The setting related to Adwaita is required for Nordic theme to be applied cleanly.

export WLR_DRM_NO_ATOMIC=1
export WLR_NO_HARDWARE_CURSORS=1
export ADW_DISABLE_PORTAL=1

Graphics configuration

I use Intel integrated graphics. So including the necessary driver information.

export LIBVA_DRIVER_NAME=iHD
export MESA_LOADER_DRIVER_OVERRIDE=iris
export __GLX_VENDOR_LIBRARY_NAME=mesa

Window manager execution

Enable debug for sway, if needed. Add “-d” flag to the sway command along with a file to capture the debug log.

exec dbus-run-session sway "$@"

sway-notify

I have a one-line wrapper script for notify-send that logs when notification fails. If notification fails for any reason, logging happens without affecting the script flow.

#!/usr/bin/env sh
notify-send "${@}" || logger -t sway-notify -p user.err "Notification failed to send."

sway-volume-notify

To control multi-media volume i use the following script which tangles to ~/.config/sway/scripts/sway-volume-notify. The script is based on RichieHH’s dotfiles↗ and official swaywm guide for volume controls↗ I changed symbols from 🔊 to others like 🔇, 🔈 and 🔊 where U+1F507, U+1F508 and U+1F50A respectively.

#!/usr/bin/env sh

NOTIFY_CMD="sway-notify"
get_volume(){
    volume=$(wpctl get-volume @DEFAULT_AUDIO_SINK@)
        local volume_decimal
        volume_decimal=$(echo "$volume" | awk '/Volume/ {print $2}')
        volumep=$(echo "$volume_decimal * 100" | bc | awk '{printf "%d", $1}')
        if echo "$volume" | grep -q '[Mm]UTED'; then
        volume_status="MUTED"
    else
        volume_status="UNMUTED"
    fi
}
# Initialise variables and clear notifications first
get_volume && makoctl dismiss 2>/dev/null

case "$1" in
    "0")
        # Toggle mute status
        wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle && pkill -RTMIN+1 i3blocks
        ;;
    "+")
        # Unmute if needed and Increase volume by 5% (0.05)
                wpctl set-mute @DEFAULT_AUDIO_SINK@ 0
                wpctl set-volume @DEFAULT_AUDIO_SINK@ "0.05+" -l 1.0 && pkill -RTMIN+1 i3blocks
        get_volume && "$NOTIFY_CMD" -h "int:value:${volumep}" "🔊 ${volumep}%"
        ;;
    "-")
        # Decrease volume by 5% (0.05)
        wpctl set-volume @DEFAULT_AUDIO_SINK@ "0.05-" -l 1.0 && pkill -RTMIN+1 i3blocks
                get_volume && "$NOTIFY_CMD" -h "int:value:${volumep}" "🔈 ${volumep}%"
        ;;
    *)
        # Default action is to show volume
        get_volume && "$NOTIFY_CMD" -h "int:value:${volumep}" "${volumep}%"
        ;;
esac

swaylockd

swaylockd↗ is a dumb launcher to spawn swaylock and ensure it’s running no matter what. Otherwise, this uses swaylock configuration.

This section tangles to the swaylock configuration file ~/.config/swaylock/config↗ . The colour scheme nord is based on this issue↗ .

color=2e3440
image=/usr/share/backgrounds/nordic/nord_lake.png
# Sets the color of backspace highlight segments
bs-hl-color=b48eadff
# Sets the color of backspace highlight segments when Caps Lock is active
caps-lock-bs-hl-color=d08770ff
# Sets the color of the key press highlight segments when Caps Lock is active
caps-lock-key-hl-color=ebcb8bff
# Sets the indicator radius (default: 50)
indicator-radius=100
# Sets the indicator thickness (default: 10)
indicator-thickness=10
# Sets the color of the inside of the indicator
inside-color=2e3440ff
# Sets the color of the inside of the indicator when cleared
inside-clear-color=81a1c1ff
# Sets the color of the inside of the indicator when verifying
inside-ver-color=5e81acff
# Sets the color of the inside of the indicator when invalid
inside-wrong-color=bf616aff
# Sets the color of key press highlight segments
key-hl-color=a3be8cff
# Sets the background color of the box containing the layout text
layout-bg-color=2e3440ff
# Use the ring color for the line between the inside and ring
line-uses-ring
# Sets the color of the outside of the indicator when typing or idle
ring-color=3b4252ff
# Sets the color of the outside of the indicator when cleared
ring-clear-color=88c0d0ff
# Sets the color of the outside of the indicator when verifying
ring-ver-color=81a1c1ff
# Sets the color of the outside of the indicator when invalid
ring-wrong-color=d08770ff
# Sets the color of the lines that separate highlight segments
separator-color=3b4252ff
# Sets the color of the text
text-color=eceff4ff
# Sets the color of the text when cleared
text-clear-color=3b4252ff
# Sets the color of the text when verifying
text-ver-color=3b4252ff
# Sets the color of the text when invalid
text-wrong-color=3b4252ff

tofi

I went with tofi mainly due to it’s lightweight nature. Eventhough there has not been much of a release since 2+ years as of mid'26, the software is feature complete. Most changes were based on official website↗ . Initial customisation based on a reddit post↗ . Later modified the colors to reflect Nord color palette based on Nordic rofi theme↗ . Further references were made to official Nord Color palette↗ .

### Fonts
font = "/usr/share/fonts/hack/Hack-Bold.ttf"
font-size = 15
font-features = ""
font-variations = ""
hint-font = false

### Text theming
text-color = #81a1c1
background-color = #2E3440

# Prompt text theme
prompt-background = #3b4252
prompt-background-padding = 8, 12
prompt-background-corner-radius = 20

# Placeholder text theme
placeholder-color = #FFFFFFA8
placeholder-background-corner-radius = 0

# Input text theme
input-color = #b48ead
# Default result text theme
default-result-color = #5e81ac

# Selection text
selection-color = #8fbcbb
selection-match-color = #b48ead

### Text layout
prompt-text = >_
prompt-padding = 22
placeholder-text = " "
num-results = 5
result-spacing = 30
horizontal = true
min-input-width = 300

### Window theming
width = 66%
height = 70
outline-width = 0
border-width = 4
border-color = #5e81ac
corner-radius = 30
padding-bottom = 8
padding-right = 17
padding-top = 13
padding-left = 17
clip-to-padding = false
scale = true

### Window positioning
output = ""
anchor = center
exclusive-zone = -1
margin-top = 0
margin-bottom = 0
margin-left = 0
margin-right = 0

### Behaviour
history = true
fuzzy-match = false
auto-accept-single = false
hide-input = false
late-keyboard-init = false
multi-instance = false
ascii-input = false
terminal = foot

tofi-exit

This tofi-exit script is based on bdwalton@’s config↗ .

prabu@homepc2 ~> cat

#!/bin/sh
set -e
set -u

selection=$(
    printf "Suspend🌙\nLock🔒\nExit \nReboot\nPowerOff\n " |
    # printf "Suspend\nLock\nExit\nReboot\nPowerOff\n " |
        tofi --prompt-text="" \
             --hide-input=true \
             --hide-cursor=true \
             --font "Symbols Nerd Font:size=14"\
             --require-match=true\
             --auto-accept-single=true
         )

case "${selection}" in
        *"Lock"*)
                swaylockd
                ;;
        *"Exit"*)
                swaymsg exit
                ;;
        *"Suspend"*)
                powerctl mem
                swaylockd
                ;;
        *"Reboot"*)
                doas /sbin/reboot
                ;;
        *"PowerOff"*)
                doas /sbin/poweroff
                ;;
esac

I retained foot↗ as terminal due to its lightweight nature.

foot configuration

For the toggle_theme script to work we use include section and foot theme files . To use signals to effect theme changes, foot requires [colors-dark] and [colors-light] sections.

# -*- conf -*-
term=xterm-256color
font=Hack:size=14

[main]
include=~/.config/foot/current_theme.ini

[colors-dark]
# nord theme
alpha=0.9
cursor = 2e3440 d8dee9
foreground = d8dee9
background = 2e3440
selection-foreground = d8dee9
selection-background = 4c566a
regular0 = 3b4252
regular1 = bf616a
regular2 = a3be8c
regular3 = ebcb8b
regular4 = 81a1c1
regular5 = b48ead
regular6 = 88c0d0
regular7 = e5e9f0

bright0 = 4c566a
bright1 = bf616a
bright2 = a3be8c
bright3 = ebcb8b
bright4 = 81a1c1
bright5 = b48ead
bright6 = 8fbcbb
bright7 = eceff4

dim0 = 373e4d
dim1 = 94545d
dim2 = 809575
dim3 = b29e75
dim4 = 68809a
dim5 = 8c738c
dim6 = 6d96a5
dim7 = aeb3bb

[colors-light]
# Nord Light / Snow Storm Palette
background=eceff4     # nord6 (Ice white background)
foreground=2e3440     # nord0 (Deep night text for great contrast)

selection-background=d8dee9
selection-foreground=2e3440

regular0=3b4252       # nord1 (Dark grey)
regular1=bf616a       # nord11 (Aurora Red)
regular2=a3be8c       # nord14 (Aurora Green)
regular3=ebcb8b       # nord13 (Aurora Yellow)
regular4=5e81ac       # nord10 (Deep Frost Blue)
regular5=b48ead       # nord15 (Aurora Purple)
regular6=88c0d0       # nord8 (Bright Frost Cyan)
regular7=e5e9f0       # nord5 (Soft Snow White)

bright0=4c566a        # nord3 (Medium grey)
bright1=bf616a        # nord11 (Aurora Red)
bright2=a3be8c       # nord14 (Aurora Green)
bright3=ebcb8b       # nord13 (Aurora Yellow)
bright4=81a1c1       # nord9 (Medium Frost Blue)
bright5=b48ead       # nord15 (Aurora Purple)
bright6=8fbcbb       # nord7 (Calm Frost Cyan)
bright7=d8dee9       # nord4 (Muted Grey-White)

## Miscelaneous UI overrides
urls=81a1c1

[csd]
border-width=1

foot theme files

The toggle script resets the symbolic file link for current_theme.ini to overwrite the existing link to the appropriate theme.

prabu@homepc2 ~> cat .config/foot/themes/light.ini

# ~/.config/foot/themes/light.ini
[main]
initial-color-theme=light

prabu@homepc2 ~> cat .config/foot/themes/dark.ini

# ~/.config/foot/themes/dark.ini
[main]
initial-color-theme=dark

mako

I use mako↗ as notifications daemon for sway. This section tangles to the configuration file ~/.config/mako/config↗ .

For Nordic theme, i used this hex46’s dotfiles↗ . The RSI options are for wayeyes . I didn’t modify the theme for mako, as these are notifications lasts few seconds by design. Instead of the default notify-send command, sway-notify , a wrapper is used in all sway scripts.

sort=-time
default-timeout=3000
layer=overlay
icon-path=/usr/share/icons/Mint-Y:/usr/share/icons/Nordzy:/usr/share/icons/Nordzy-dark
max-icon-size=64
height=300
background-color=#4c566a
progress-color=over #5e81ac
padding=0,5,20
border-color=#88c0d0
border-radius=20
format=<span font="Symbols Nerd Font 13" rise="5pt" color="#b48ead">%s</span>\n%b
text-alignment=center

[grouped]
format= <span font="Symbols Nerd Font 13" rise="5pt" color="#b48ead" font-weight="700">%s</span>\n%b

[app-name=RSI]
default-timeout=20000
background-color=#000000
text-color=#000000
border-color=#000000
border-radius=0
width=1532
height=820
format=<span font="Symbols Nerd Font 12" line_height="42" rise="1pt" color="#b48ead">%s</span>\n%b
anchor=center

[urgency=critical]
border-color=#bf616a
default-timeout=0

i3blocks

I use i3blocks↗ as a feed generator to generate status line content for my swaybar.

The i3blocks configuration file is mostly based on the sample file with tweaks based on trill’s dotfiles↗ . The $SCRIPT_DIR variable used below is set in status_command in sway configuration file .

# Global properties
align=center
separator=false
align=center
separator_block_width=15
markup=pango
command=$SCRIPT_DIR/$BLOCK_NAME

# [active]
# command=swaymsg -t subscribe -m '["window"]' | jq -r '.container.name'
# interval=once

[calendar]
command=slclock "%H:%M" "" --action
interval=persist
# DATEFMT=+%H:%M
LABEL=
CLICK_SCRIPT=/home/prabu/.config/sway/scripts/i3blocks/calendar
signal=4

# The min_width has to adjusted to ensure that time appears in the center of the bar
[spaceblock]
full_text=" "
min_width=640
# min_width=500
align=center

[backup]
interval=once
signal=5

[mediaplayer]
interval=60
signal=2
min_width=20

[wayeyes]
interval=60
signal=3
min_width=20

[volume-pipewire]
interval=once
signal=1
min_width=20

All the scripts used by the i3blocks are maintained in $HOME/.config/sway/scripts/i3blocks/ folder.

backup indicator

btrbk utility is used to snapshot and backup BTRFS subvolumes in Alpine Linux . An indicator is displayed on the swaybar, when backup runs, so that one doesn’t accidentally shutdown/suspend the computer.

#!/bin/sh

INDICATOR_FILE="/tmp/btrbk_running"


# Function to print the icon and status
print_block() {
        if [ -f "$INDICATOR_FILE" ]; then
           echo ""
           echo "btrbk"
           echo "#FF0000"
        else
            echo ""
            echo ""
        fi
}

# Print the icon and status to i3blocks
print_block

mediaplayer

I mainly use cmus to play mp3 files and mpv as media player. The below script uses playerctl to control and display media information.

#!/usr/bin/env sh

now_playing() {
    local current_song
        current_song=$(playerctl metadata --format '{{ title }} from {{ album }} by {{artist }} is playing in {{ playerName }}')
    makoctl dismiss && sway-notify "$current_song"
}
print_block() {
    # Check player status
    local player_status
    player_status=$(playerctl status 2>/dev/null)

    case "$player_status" in
        "Paused")
            echo ""
            echo ""
            echo "#FF0000"
            ;;
        "Playing")
            echo ""
            ;;
        "Stopped")
            echo ""
            ;;
        # Handle the case when playerctl returns nothing (no players or error)
        "")
            echo " "
            ;;
        # Fallback for other states like 'Idling'
        *)
            echo " "
            ;;
    esac
}

# 1 Left click: display current song info
# 2 Middle click: Toggle play-pause
# 3 Right click: show scratchpad (effective only if cmus is running there)
# 4 Wheel up: Next song
# 5 Wheel down: Prev song

handle_button() {
        # Exit early if no button was pressed, which is the default behavior
        [ -z "$BLOCK_BUTTON" ] && return 0
    case "$BLOCK_BUTTON" in
        1) now_playing && pkill -RTMIN+2 i3blocks ;;
        2) playerctl play-pause && pkill -RTMIN+2 i3blocks ;;
        3) swaymsg "[app_id=\"play_cmus_in_scratch\"] scratchpad show" ;;
                4) playerctl next && now_playing ;;
                5) playerctl previous && now_playing  ;;
    esac
}
# Handle the mouse button click, and all its output is discarded
handle_button > /dev/null 2>&1
# Print the block to status bar
print_block

calendar

The time displayed on the status_command is generated by a small utility slclock.c↗ written in C language.

#!/usr/bin/env sh

# Action Handler for wayclock, runs in the background and doesn't print anything
if [ "$1" = "--action" ]; then
    sway-notify "    $(date +'%A %F')"
    date +'%A %F %H:%M:%S' | wl-copy
    exit 0
fi

# Function to print the icon and status and copy the content to buffer
current_date() {
    sway-notify "    $(date +'%A %F')"
    date +'%A %F %H:%M:%S' | wl-copy
}
print_block() {
    # This uses the $LABEL and $DATEFMT variables provided by i3blocks
    echo "$LABEL$(date "$DATEFMT")"
}
# 1 Left click: Display Date and Time
# 2 Middle click: Display calendar
# 3 Right click: NA
# 4 Wheel up: NA
# 5 Wheel down: NA
handle_button() {
        # Exit early if no button was pressed, which is the default behavior
        [ -z "$BLOCK_BUTTON" ] && return 0
    case "$BLOCK_BUTTON" in
        1) current_date ;;
    esac
}
# Handle the mouse button click, and all its output is discarded
handle_button > /dev/null 2>&1
# Print the block to status bar
print_block

wayeyes

Wayeyes↗ is a lightweight tool to reduce eye strain for Wayland compositors using wlopm↗ utility. The main files are wayeyes script and the toggle_wayeyes script.

The i3blocks script is given below and the openrc script is here↗ .

#!/usr/bin/env sh
# Icons are FontAwesome eye-open & eye-slash
WAYEYES_SCRIPT="$HOME/.local/bin/wayeyes"
TOGGLE_SCRIPT="$HOME/.local/bin/toggle_wayeyes"
PRE_ALERT_FILE="${XDG_RUNTIME_DIR:-/tmp}/wayeyes.pre_alert"
I3BLOCKS_SIGNAL=3

wayeyes_status=$("$WAYEYES_SCRIPT" status 2>/dev/null)

print_block() {
    case "$wayeyes_status" in
        "Next break at"*)
            if [ -f "$PRE_ALERT_FILE" ]; then
                ICON=""
                COLOR="#FFA500"
            else
                ICON=""
                COLOR=""
            fi
            ;;
        *)
            ICON=""
            COLOR="#FF0000"
            ;;
    esac
    echo "$ICON"
    echo ""
    echo "$COLOR"
}

case "$BLOCK_BUTTON" in
    1) notify-send "$wayeyes_status" >/dev/null 2>&1 ;;
    2) "$TOGGLE_SCRIPT" >/dev/null 2>&1 ;;
esac

print_block

Volume

Pipewire is used for controlling the audio stack, though alsa is used for 5.1 audio as explained in HTPC page.

#!/usr/bin/env sh

print_block() {
        local volume
        volume=$(wpctl get-volume @DEFAULT_AUDIO_SINK@)
        case "$volume" in
                *MUTED*)
                        echo ""
                        echo ""
                        echo "#FF0000"
                        ;;
                *)
                        echo ""
                        ;;
        esac
}

# Mouse click
# 1 Left click: display current volume
# 2 Middle click: Toggle mute
# 3 Right click: Select sink - Not implemented yet
# 4 Wheel up: increase volume
# 5 Wheel down: decrease volume

handle_button() {
        # Exit early if no button was pressed, which is the default behavior
    [ -z "$BLOCK_BUTTON" ] && return 0
        local notify_cmd
        notify_cmd="$HOME/.config/sway/scripts/sway-volume-notify"
    case "$BLOCK_BUTTON" in
        1) "$notify_cmd" ;;
        2) "$notify_cmd" "0" ;;
        4) "$notify_cmd" "+" ;;
        5) "$notify_cmd" "-" ;;
    esac
}
# Handle the mouse button click, and all its output is discarded
handle_button > /dev/null 2>&1
# Print the block to status bar
print_block

Usage notes

Some of these were used in the past.

Bluetooth applet

To run the bluetooth applets:

exec blueman-applet exec nm-applet --indicator

© Prabu Anand K 2020-2026