Sway config

rtags: linux, config, wayland

Migrating to sway/wayland from i3wm/X. This bellow is my sway config. It can be tangled with Org Mode to appropriate locations. See the source of this site.

See https://wiki.archlinux.org/title/Sway

Put a warning at the beginning of the tangled file just to be sure not to forget.

#Warning: this config is maintained through Org Mode file. Do not edit directly!


sudo pacman -S wayland xorg-xwayland sway swaylock swaybg

See other sections for the installation of other tools used in this config.

The standard location of sway config is ~/.config/sway/config.

Some variables used in the config

set {

Mod4 is the Win key on most keyboards. Since it is unused for other purposes it is ideal to be a sway modifier key.

    $mod Mod4

Sway config root.

    $sway ~/.config/sway

A script to handle lock/suspend.

    $exit $sway/exit.sh

A script for managing multiple displays. See bellow.

    $display_profile $sway/display-profile.sh

Socket used for wob, a tool for creating overlay bar during setting of volume, screen brightness etc. Create it with mkfifo. See the README of the project. See bellow for the usage.

    $SWAYSOCK.wob ~/.local/share/sway-wob.sock

Windows setup

Use minimal borders.

default_border pixel 2

Disable border on screen edges and hide title for a single child tabbed/stacked containers.

hide_edge_borders --i3 smart

Use Mouse+$mod to drag floating windows to their wanted position.

floating_modifier $mod

Background color.

output "*" bg "#002244" solid_color

Font for window titles and swaybar.

font pango:Hack 10

Hide mouse cursor after a period of inactivity.

seat * hide_cursor 3000

Basic keybindings

Start alacritty terminal:

bindsym $mod+Return exec alacritty

Emacs everywhere. Launch emacs to edit content in other applications. Not working at the moment for wayland.

#bindsym $mod+m exec emacsclient --eval "(emacs-everywhere)"

Screenshot can be taken with grim which uses slurp to select a region and then open the captured image in gthumb:

bindsym Print exec grim -g "$(slurp)" ~/screenshot.png && gthumb ~/screenshot.png
bindsym Shift+Print exec grim -g "$(slurp)" - | wl-copy

Kill focused window:

bindsym $mod+Shift+q kill

Switch back and forth between workspaces:

workspace back_and_forth yes
bindsym $mod+tab workspace back_and_forth

Change focus (use vim bindings).

bindsym $mod+h focus left
bindsym $mod+j focus down
bindsym $mod+k focus up
bindsym $mod+l focus right

Move focused window (vim bindings):

bindsym $mod+Shift+h move left
bindsym $mod+Shift+j move down
bindsym $mod+Shift+k move up
bindsym $mod+Shift+l move right

Split in horizontal and vertical orientations. When the shortcut is pressed a hint is displayed at the side of the window where the next application will be positioned.

bindsym $mod+b split h
bindsym $mod+v split v

Toggle fullscreen mode for focused window:

bindsym $mod+f fullscreen toggle

Change container layout (stacked, tabbed, toggle split):

bindsym $mod+s layout stacking
bindsym $mod+w layout tabbed
bindsym $mod+e layout toggle split

Toggle tiling/floating:

bindsym $mod+Shift+space floating toggle

Change focus between tiling/floating windows

bindsym $mod+space focus mode_toggle

Focus the parent/child container:

bindsym $mod+a focus parent
bindsym $mod+Shift+a focus child

Reload configuration file:

bindsym $mod+Shift+r reload

Exit sway. Log out of wayland session:

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'"

Workspaces and window associations

Define names for default workspaces for which we configure key bindings later on. We use variables to avoid repeating the names in multiple places.

set $ws1 1
set $ws2 2
set $ws3 3
set $ws4 4
Set $ws5 5
set $ws6 6
set $ws7 7
set $ws8 8
set $ws9 9
set $ws10 10

Switch to workspace:

bindsym $mod+1 workspace $ws1
bindsym $mod+2 workspace $ws2
bindsym $mod+3 workspace $ws3
bindsym $mod+4 workspace $ws4
bindsym $mod+5 workspace $ws5
bindsym $mod+6 workspace $ws6
bindsym $mod+7 workspace $ws7
bindsym $mod+8 workspace $ws8
bindsym $mod+9 workspace $ws9
bindsym $mod+0 workspace $ws10

Move focused container to workspace:

bindsym $mod+Shift+1 move container to workspace $ws1
bindsym $mod+Shift+2 move container to workspace $ws2
bindsym $mod+Shift+3 move container to workspace $ws3
bindsym $mod+Shift+4 move container to workspace $ws4
bindsym $mod+Shift+5 move container to workspace $ws5
bindsym $mod+Shift+6 move container to workspace $ws6
bindsym $mod+Shift+7 move container to workspace $ws7
bindsym $mod+Shift+8 move container to workspace $ws8
bindsym $mod+Shift+9 move container to workspace $ws9
bindsym $mod+Shift+0 move container to workspace $ws10

Move workspace to left/right output:

bindsym $mod+Shift+Alt+h move workspace to output left
bindsym $mod+Shift+Alt+l move workspace to output right

You can map workspace to output using this. Can be handy in multi-display setups.

workspace 1 output DP1 eDP1
workspace 2 output DP1 eDP1

Assign application window classes to workspaces:

assign [class="Viber"] 3
assign [class="Skype"] 3
assign [class="Audacious"] 10

TODO: Write how to get the name of the application class.

Send current workspace to other output:

bindsym $mod+o exec swaymsg -t get_outputs | \
        jq '.[] | select(.focused!=true) | .name' | head -n1 | xargs swaymsg move workspace to


Media buttons with bar overlay

Install wob:

sudo pacman -S wob

Setup named pipe/socket (do this once):

mkfifo ~/.local/share/sway-wob.sock

Connect socket to wob in the config:

exec tail -f $SWAYSOCK.wob | wob

Screen brightness

Install light:

sudo pacman -S light

Call light on brightness keys and pipe to wob:

bindsym XF86MonBrightnessUp exec light -A 10 && light -G | cut -d'.' -f1 > $SWAYSOCK.wob
bindsym XF86MonBrightnessDown exec light -U 10 && light -G | cut -d'.' -f1 > $SWAYSOCK.wob

Media player controls

Install playerctl:

sudo pacman -S playerctl

Call playerctl on media keys:

bindsym XF86AudioPlay exec playerctl play-pause
bindsym XF86AudioStop exec playerctl stop
bindsym XF86AudioNext exec playerctl next
bindsym XF86AudioPrev exec playerctl previous

Sound volume

Install pamixer:

sudo pacman -S pamixer

Call pamixer on volume keys and pipe to wob:

bindsym XF86AudioRaiseVolume exec pamixer -ui 5 && pamixer --get-volume > $SWAYSOCK.wob
bindsym XF86AudioLowerVolume exec pamixer -ud 5 && pamixer --get-volume > $SWAYSOCK.wob
bindsym XF86AudioMute exec pamixer --toggle-mute

Window resizing

# resize window (you can also use the mouse for that)
mode "resize" {
        # These bindings trigger as soon as you enter the resize mode
        bindsym {
          h resize shrink width 10 px or 10 ppt
          j resize grow height 10 px or 10 ppt
          k resize shrink height 10 px or 10 ppt
          l resize grow width 10 px or 10 ppt

          # back to normal: Enter or Escape or $mod+r
          Return mode "default"
          Escape mode "default"
          $mod+r mode "default"
bindsym $mod+r mode "resize"

System mode

set $mode_system System (l) lock, (e) logout, (s) suspend, (r) reboot, (Shift+s) shutdown
mode "$mode_system" {
    bindsym l exec $exit lock; mode "default"
    bindsym e exec $exit logout
    bindsym s exec $exit suspend & systemctl suspend; mode "default"
    bindsym r exec systemctl reboot
    bindsym Shift+s exec systemctl shutdown

    # back to normal: Enter or Escape
    bindsym Return mode "default"
    bindsym Escape mode "default"
bindsym $mod+End mode "$mode_system"

exit.sh script called on lock/suspend/logout

Exit script which locks and unmounts encFS mounts on suspend.

export XDG_RUNTIME_DIR=/run/user/$(id -u)
export WAYLAND_DISPLAY=wayland-1
export SWAYSOCK=$XDG_RUNTIME_DIR/sway-ipc.$(id -u).$(pgrep -x sway).sock

lock() {
    swaylock -c 003300 &

case "$1" in
        swaymsg 'exit'
        encfs -u ~/Consulting
        encfs -u ~/.ssh
        ssh-add -D
        echo "Usage: $0 {lock|logout|suspend}"
        exit 2

exit 0

Source for exports setup. Needed cause this is called from system-level systemd service.

Call exit.sh script on lid close

System should lock and unmount all encrypted shares on lid close. See here for power management hooks.

File </etc/systemd/logind.conf> has handling of lid switch

$ loginctl show-session

Create systemd service file in </etc/systemd/system/suspend@.service>

Description=Lock screen on suspend

ExecStart=/home/igor/.config/sway/exit.sh suspend


This must be a system-level service as user services can’t have sleep dependencies.

Enable with sudo systemctl enable suspend@igor

Display mode

Screen modes by location.

Note: mirroring is not yet properly supported by sway but there is wl-mirror as a workaround. See also this.

set $mode_display LOCATION: (l)aptop (h)ome (m)eeting (d)ual dual-(r)ight (p)res-low
mode "$mode_display" {
     bindsym {
             l exec $display_profile laptop, mode "default"
             h exec $display_profile home, mode "default"
             d exec $display_profile dual, mode "default"
             r exec $display_profile dual-right, mode "default"
             m exec $display_profile meeting, mode "default"
             p exec $display_profile pres-lowres, mode "default"

             # back to normal: Enter or Escape
             Return mode "default"
             Escape mode "default"
bindsym $mod+x mode "$mode_display"

Mirroring of output can be done with:

wl-mirror eDP-1

To mirror only a part of the output combine with slurp:

wl-mirror -r "$(slurp)"

display-profile script

# Set display profiles for various locations.
# WARNING! This script is maintained through Org Mode file. Do not edit directly.

LAPTOP_MODE=`swaymsg -t get_outputs | jq ".[] | select(.name==\"$LAPTOP_OUTPUT\") | .modes[-1]"`
LAPTOP_RES=`echo $LAPTOP_MODE | jq ".width"`x`echo $LAPTOP_MODE | jq ".height"`
LAPTOP_WIDTH=`echo $LAPTOP_MODE | jq ".width"`

# Get auxiliary output
AUX_OUTPUT_JSON=`swaymsg -t get_outputs | jq ".[] | select(.name!=\"$LAPTOP_OUTPUT\")"`
AUX_MODE=`echo $AUX_OUTPUT_JSON | jq ".modes[0]"`
AUX_OUTPUT=`echo $AUX_OUTPUT_JSON | jq ".name"`
AUX_RES=`echo $AUX_MODE | jq ".width"`x`echo $AUX_MODE | jq ".height"`
AUX_WIDTH=`echo $AUX_MODE | jq ".width"`

echo $AUX_RES
echo $AUX_RES

    for i in {1..10}
        swaymsg "[workspace=$i]" move workspace to output $OUTPUT

case "$LOCATION" in
        swaymsg output $LAPTOP_OUTPUT enable pos 0 0 res $LAPTOP_RES
        swaymsg output $AUX_OUTPUT disable

        swaymsg output $AUX_OUTPUT enable pos 0 0 res $AUX_RES, \
                output $LAPTOP_OUTPUT disable

        swaymsg output $AUX_OUTPUT enable pos 0 0 res $AUX_RES scale 1, \
                output $LAPTOP_OUTPUT enable pos $AUX_WIDTH 0
        move_workspaces_to_output $LAPTOP_OUTPUT

        swaymsg output $AUX_OUTPUT enable pos $AUX_WIDTH 0 res $AUX_RES scale 1, \
                output $LAPTOP_OUTPUT enable pos 0 0
        move_workspaces_to_output $LAPTOP_OUTPUT


        swaymsg output $AUX_OUTPUT enable pos $AUX_WIDTH 0 res $AUX_RES scale 1.5, \
                output $LAPTOP_OUTPUT enable pos 0 0
        move_workspaces_to_output $LAPTOP_OUTPUT

        swaymsg output $AUX_OUTPUT enable pos $LAPTOP_WIDTH 0 res 1280x1024, \
                output $LAPTOP_OUTPUT enable
        move_workspaces_to_output $LAPTOP_OUTPUT



Config sway to use waybar.

bar {
       swaybar_command waybar

Install font Hack Nerd used for swaync notification icons.

sudo pacman -S ttf-hack-nerd

Waybar configuration:

    "layer": "top",
    "position": "bottom",

    "modules-left": [
    "modules-right": [
//      "idle_inhibitor",

    "custom/left-arrow-dark": {
        "format": "",
        "tooltip": false
    "custom/left-arrow-light": {
        "format": "",
        "tooltip": false
    "custom/right-arrow-dark": {
        "format": "",
        "tooltip": false
    "custom/right-arrow-light": {
        "format": "",
        "tooltip": false

    "sway/workspaces": {
        "disable-scroll": true,
        "format": "{name}:{icon} ",
        "format-icons": {
            "1": "",
            "2": "",
            "3": "",
            "10": ""

    "clock#1": {
        "format": "{:%a}",
        "tooltip": false
    "clock#2": {
        "format": "{:%H:%M}",
        "tooltip": false
    "clock#3": {
        "format": "{:%d-%m}",
        "tooltip": false
    "custom/weather": {
        "exec": "curl 'https://wttr.in/Novi_Sad?format=1' -s | cut -c 1-15",
        "interval": 3600
    "custom/pomodoro": {
         "format": "{}",
         "exec": "emacsclient -e '(ird/org-pomodoro-time)' | sed -e 's/^\"//'  -e 's/\"$//'",
         "interval": 30,
         "signal": 12
    "sway/language": {
        "format": "{}",
        "max-length": 5,
        "min-length": 5,
    "pulseaudio": {
        "format": "{icon} {volume:2}%",
        "format-bluetooth": "{icon}  {volume}%",
        "format-muted": "MUTE",
        "format-icons": {
            "headphones": "",
            "default": [
        "scroll-step": 5,
        "on-click": "pamixer -t",
        "on-click-right": "pavucontrol"
    "memory": {
        "interval": 5,
        "format": "Mem {}%"
    "cpu": {
        "interval": 5,
        "format": "CPU {usage:2}%"
    "battery": {
        "states": {
            "good": 95,
            "warning": 30,
            "critical": 15
        "format": "{icon} {capacity}%",
        "format-icons": [
  "network": {
      // "interface": "wlp2*", // (Optional) To force the use of this interface
      "interval": 3,
      "format-wifi": "{essid} ({signalStrength}%, {bandwidthDownBits}|{bandwidthUpBits}) ",
      "format-ethernet": "{ifname}: {ipaddr}/{cidr} ({bandwidthDownBits}|{bandwidthUpBits}) ",
      "format-linked": "{ifname} (No IP) ",
      "format-disconnected": "Disconnected ⚠",
      "format-alt": "{ifname}: {ipaddr}/{cidr}"
  "idle_inhibitor": {
          "format": "{icon}",
          "format-icons": {
              "activated": "",
              "deactivated": ""
  "custom/notification": {
          "tooltip": false,
          "format": "{icon}",
          "format-icons": {
                  "notification": "<span foreground='red'><sup></sup></span>",
                  "none": "",
                  "dnd-notification": "<span foreground='red'><sup></sup></span>",
                  "dnd-none": ""
          "return-type": "json",
          "exec-if": "which swaync-client",
          "exec": "swaync-client -swb",
          "on-click": "swaync-client -t -sw",
          "on-click-right": "swaync-client -d -sw",
          "escape": true

   "tray": {
        "icon-size": 20


** {
    font-size: 15px;
    font-family: monospace;

window#waybar {
    background: #292b2e;
    color: #fdf6e3;

#custom-left-arrow-dark {
    color: #1a1a1a;
#custom-left-arrow-light {
    color: #292b2e;
    background: #1a1a1a;

#tray {
    background: #1a1a1a;

#workspaces button {
    padding: 0 2px;
    color: #fdf6e3;
#workspaces button.focused {
    color: #268bd2;
#workspaces button.urgent {
      background: #aa0000;
#workspaces button:hover {
    box-shadow: inherit;
    text-shadow: inherit;
#workspaces button:hover {
    background: #1a1a1a;
    border: #1a1a1a;
    padding: 0 3px;

#mode {
      background: #aa0000;

#pulseaudio {
    color: #268bd2;
#backlight {
    color: #b58900;
#memory {
    color: #2aa198;
#temperature {
    color: #b58900;
#cpu {
    color: #6c71c4;
#battery {
    color: #859900;
#battery.discharging {
      color: #dd9900;
#network {
  color: #8599aa;
#custom-notification {
  font-family: "NotoSansMono Nerd Font";

#battery {
    padding: 0 10px;

Application launcher, window switcher

Rofi is a popular application launcher and window switcher. See also ArchLinux wiki page.

Currently original rofi has no support for wayland. There is a fork with wayland support. Install with:

yay -S --mflags "--nocheck" rofi-lbonn-wayland

--nocheck is needed at the moment as there is a problem with libnkutils. If you are reading this in a fairly distant future probably you won’t need this.

Keyboard shortcut.

bindsym $mod+d exec rofi -modes combi -show combi -combi-modes run,drun

Rofi configuration

@theme "/usr/share/rofi/themes/Arc-Dark.rasi"
configuration {
    modi: "window,run,ssh";
    timeout {
        action: "kb-cancel";
        delay:  0;
    filebrowser {
        directories-first: true;
        sorting-method:    "name";


wl-clipboard is a command line tool for copy/pasting text.

sudo pacman -S wl-clipboard

Now, text can be copied from CLI with <some command> | wl-copy or pasted from clipboard with wl-paste | <some command>.

A clipboard is emptied when window is closed by default. To persist clipboard after window closing use clipboard manager like clipman.

sudo pacman -S clipman

Start with sway:

exec wl-paste -t text --watch clipman store --no-persist

Touch pad

Enable tap and natural scroll.

input type:touchpad {
    tap enabled
    natural_scroll enabled

Keyboards layouts

input * {
  xkb_layout "us,rs,rs"
  xkb_variant ",latin,"
  xkb_options "grp:shifts_toggle"

Red shift

Install gammastep

sudo pacman -S gammastep

Execute it from sway config with your long/lat:

exec_always gammastep -l 45.26:19.83

There is also a tray indicator gammastep-indicator. I’m not using it.


Run applet with --indictator switch:

exec_always nm-applet --indicator

TODO Bluetooth

Not working with sway. Will investigate.

exec blueman-applet

Wacom tablet

I use Wacom CTL-471 for screen annotation during lectures. OpenTabletDriver is a user-mode cross-platform tablet driver with settings GUI.

Blacklist wacom kernel module:

sudo sh -c "echo 'blacklist wacom' > /etc/modprobe.d/nowacom.conf"
yay -S opentabletdriver

Start OTD from sway:

exec otd

Run otd-gui for configuration.

Using gromit-mpx for screen annotation.

There is a problem with capturing hotkeys. A workaround is to handle hotkeys through sway.

mode "gromit-mpx" {
    # toggle painting
    bindsym f9 exec gromit-mpx --toggle
    # clear
    bindsym Shift+f9 exec gromit-mpx --clear
    # toggle visibility
    bindsym Ctrl+f9 exec gromit-mpx --visibility
    # quit
    bindsym Alt+f9 exec gromit-mpx --quit
    # undo
    bindsym f8 exec gromit-mpx --undo
    # redo
    bindsym Shift+f8 exec gromit-mpx --redo

    # Return to default mode
    bindsym $mod+g exec gromit-mpx --quit; mode "default"
bindsym $mod+g exec gromit-mpx --active; mode "gromit-mpx"

Gromit-MPX use Xwayland. So to find the name of the device use:

xinput --list

Gromit-MPX config with red and green pen and eraser:

"red Pen" = PEN (size=7 color="red");
"green Pen" = "red Pen" (color="green");

"Eraser" = ERASER (size = 75);

"Virtual core pointer" = "red Pen";
"Virtual core pointer"[Button2] = "green Pen";
"Virtual core pointer"[Button3] = "Eraser";


Notification daemon

Using swaync.

sudo yay -S swaync

Start with sway.

exec_always swaync

Show pannel.

bindsym $mod+grave exec swaync-client -t

Toggle Do-Not-Disturb mode.

bindsym $mod+p exec swaync-client -d

Close all notifications:

bindsym $mod+c exec swaync-client -C

See also the waybar configuration above.

Make some notification transient (I don’t want them to persist in the notification list).

 "notification-visibility": {
    "emacs-pomodoro": {
      "_comment": "Make all emacs org-pomodoro notitification transient",
      "state": "transient",
      "category": "org-pomodoro"
    "connection-info": {
      "_comment": "Make all connection info notitification transient",
      "state": "transient",
      "app_name": "NetworkManager Applet",
      "summary": "Connection Established"


I’m using Syncthing for syncing various stuff. Mostly Org files and podcasts with my phone and reading stuff with my Boox Air Note 2.

exec syncthing-gtk

Warning on battery critical level

Check battery, notify when low and suspend when critical. Taken and adapted from ArchLinux wiki.

acpi -b | awk -F'[,:%]' '{print $2, $3}' | {
    read -r status capacity

    if [ "$status" = Discharging ]; then
           if [ "$capacity" -lt 5 ]; then
               systemctl suspend
           elif [ "$capacity" -lt 15 ]; then
               notify-send -u critical "Battery low" "Current capacity is (${capacity}%)."

Create crontab entry with command crontab -e which call check script at 5 minute interval.

*/5  * * * *  XDG_RUNTIME_DIR=/run/user/$(id -u) $HOME/.config/sway/check_battery.sh

Screen sharing

See this and this.

sudo pacman -S xdg-desktop-portal xdg-desktop-portal-wlr

Configuration for output selection and disabling of notification during screen-cast.

exec_before=swaync-client -dn
exec_after=swaync-client -df
chooser_cmd=slurp -f %o -or

Added this to ~/.profile (add manually)

export XDG_SESSION_TYPE=wayland

And this to sway config to launch xdg-desktop-portal when sway starts.

exec_always /usr/lib/xdg-desktop-portal -r & /usr/lib/xdg-desktop-portal-wlr -r

Firefox works out-of-the-box. For chromium enable experiment setting chrome://flags/#enable-webrtc-pipewire-capturer.

Test with Mozilla’s getUserMedia / getDisplayMedia Test Page

Autostart apps

exec {
  swaymsg 'workspace 1; exec emacsclient-one-frame.sh'
  swaymsg 'workspace 2; exec firefox'