Introduction

From a long time I wanted to try lightweight WM (Window Manager) for Linux. I installed i3 once a few years ago, but I had no idea how to use it, so I just closed this thing and got back to Gnome. Recently, I watched several videos and tutorials about Linux and i3 on youtube, which helped me to learn this WM, how it works and how to use it. I also read some part of docs and user’s guide to customize my configuration. Official docs of i3 are really good.

Concept

The original concept of i3 and tiling windows managers in general is the fact, that windows are not layered one on top of another like in all typical GUIs for popular operating systems we know today. It makes sense, because when one window is behind another, you cannot see its content anyway. It’s possible to do that in i3 by detaching a windows and moving them around, but it’s not usual case and default behavior. Today we can see that operating systems like Windows or macOS are trying to mimic behavior of tiling window managers with window splitting, etc. This functionality is available in Unity for Ubuntu and can be achieved with additional plugins for Gnome environment. We also have applications like iTerm for macOS or Tilix for Linux, which mimics the same thing, but just for terminals. In i3, we have this functioality out of the box for all programs we use in the system. Not just terminals. We can also use the same key bindings and shortcuts across whole system, which is very convenient, because you don’t have to memorize shortcuts and keybindings for each program separately.

System configuration as a code

What is very convenient in i3 is the fact, that you can keep and maintain your configuration as a code in the single file located in ~/.config/i3/config. This file can contain your wallpaper definition, key bindings, configuration for moving and resizing windows, look and feel of the status bar (its colors, location, height, font, etc.) and windows (e.g. I removed title bars, because I don’t need them now)

Below, you can see my current i3 config. I keep it up to date in my dotfiles repo.

# This file has been auto-generated by i3-config-wizard(1).
# It will not be overwritten, so edit it as you like.
#
# Should you change your keyboard layout some time, delete
# this file and re-run i3-config-wizard(1).
#
# i3 config file (v4)
#
# Please see https://i3wm.org/docs/userguide.html for a complete reference!

set $mod Mod4

# Font for window titles. Will also be used by the bar unless a different font
# is used in the bar {} block below.
font pango:monospace 8

# setting bg color
exec --no-startup-id xsetroot -solid "#17212b"

# setting wallpaper
exec --no-startup-id feh --bg-scale ~/Pictures/config/wallpaper/wallpaper.jpg

# This font is widely installed, provides lots of unicode glyphs, right-to-left
# text rendering and scalability on retina/hidpi displays (thanks to pango).
#font pango:DejaVu Sans Mono 8

# The combination of xss-lock, nm-applet and pactl is a popular choice, so
# they are included here as an example. Modify as you see fit.

# xss-lock grabs a logind suspend inhibit lock and will use i3lock to lock the
# screen before suspend. Use loginctl lock-session to lock your screen.
exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork

# NetworkManager is the most popular way to manage wireless networks on Linux,
# and nm-applet is a desktop environment-independent system tray GUI for it.
exec --no-startup-id nm-applet

# Use pactl to adjust volume in PulseAudio.
set $refresh_i3status killall -SIGUSR1 i3status
bindsym XF86AudioRaiseVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ +10% && $refresh_i3status
bindsym XF86AudioLowerVolume exec --no-startup-id pactl set-sink-volume @DEFAULT_SINK@ -10% && $refresh_i3status
bindsym XF86AudioMute exec --no-startup-id pactl set-sink-mute @DEFAULT_SINK@ toggle && $refresh_i3status
bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOURCE@ toggle && $refresh_i3status

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

# start a terminal
bindsym $mod+Return exec gnome-terminal

# kill focused window
bindsym $mod+Shift+q kill

# start dmenu (a program launcher)
bindsym $mod+d exec dmenu_run
# There also is the (new) i3-dmenu-desktop which only displays applications
# shipping a .desktop file. It is a wrapper around dmenu, so you need that
# installed.
# bindsym $mod+d exec --no-startup-id i3-dmenu-desktop

# change focus
bindsym $mod+j focus left
bindsym $mod+k focus down
bindsym $mod+l focus up
bindsym $mod+semicolon focus right

# alternatively, you can use the cursor keys:
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right

# move focused window
bindsym $mod+Shift+j move left
bindsym $mod+Shift+k move down
bindsym $mod+Shift+l move up
bindsym $mod+Shift+semicolon move right

# alternatively, you can use the cursor keys:
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right

# split in horizontal orientation
bindsym $mod+h split h

# split in vertical orientation
bindsym $mod+v split v

# enter fullscreen mode for the focused container
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 container
bindsym $mod+a focus parent

# focus the child container
#bindsym $mod+d focus child

# 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 number $ws1
bindsym $mod+2 workspace number $ws2
bindsym $mod+3 workspace number $ws3
bindsym $mod+4 workspace number $ws4
bindsym $mod+5 workspace number $ws5
bindsym $mod+6 workspace number $ws6
bindsym $mod+7 workspace number $ws7
bindsym $mod+8 workspace number $ws8
bindsym $mod+9 workspace number $ws9
bindsym $mod+0 workspace number $ws10

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

# reload the configuration file
bindsym $mod+Shift+c reload
# restart i3 inplace (preserves your layout/session, can be used to upgrade i3)
bindsym $mod+Shift+r restart
# exit i3 (logs you out of your X session)
bindsym $mod+Shift+e exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -B 'Yes, exit i3' 'i3-msg exit'"

# set key binding for screen lock
bindsym $mod+b exec "i3lock -c 17212b"

# resize window (you can also use the mouse for that)
mode "resize" {
        # These bindings trigger as soon as you enter the resize mode

        # Pressing left will shrink the window’s width.
        # Pressing right will grow the window’s width.
        # Pressing up will shrink the window’s height.
        # Pressing down will grow the window’s height.
        bindsym j resize shrink width 10 px or 10 ppt
        bindsym k resize grow height 10 px or 10 ppt
        bindsym l resize shrink height 10 px or 10 ppt
        bindsym semicolon resize grow width 10 px or 10 ppt

        # same bindings, but for the arrow keys
        bindsym Left resize shrink width 10 px or 10 ppt
        bindsym Down resize grow height 10 px or 10 ppt
        bindsym Up resize shrink height 10 px or 10 ppt
        bindsym Right resize grow width 10 px or 10 ppt

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

bindsym $mod+r mode "resize"

# move window (you can also use mouse for that)
mode "moveit" {
    bindsym Up move up 20px
    bindsym Left move left 20px
    bindsym Down move down 20px
    bindsym Right move right 20px
    bindsym Mod4+m mode "default"
}

bindsym $mod+m focus floating; mode "moveit"

# brightness control (works for thinkpad, requires 'light' program)
bindsym XF86MonBrightnessUp exec "sudo light -A 20; notify-send 'Brightness (Up)' $(light)'%' && light > /var/log/scripts/screen_brightness.log && killall -SIGUSR1 i3status"
bindsym XF86MonBrightnessDown exec "sudo light -U 20; notify-send 'Brightness (Down)' $(light)'%' && light > /var/log/scripts/screen_brightness.log && killall -SIGUSR1 i3status"

# spotify play control (requires 'spotify-cli-linux' program and media keys on keyboard)
bindsym XF86AudioPlay exec "spotifycli --playpause"
bindsym XF86AudioNext exec "spotifycli --next"
bindsym XF86AudioPrev exec "spotifycli --prev"

# Start i3bar to display a workspace bar (plus the system information i3status
# finds out, if available)

bar {
workspace_buttons yes
    position top
    status_command i3status
font pango:Roboto 10
height 25
tray_output none

colors {
background #17212b
        statusline #ffffff
        separator  #666666
        active_workspace   #2c3e50 #2c3e50 #1abc9c
        focused_workspace  #2c3e50 #2c3e50 #1abc9c
        inactive_workspace #2c3e50 #2c3e50 #ecf0f1
        urgent_workspace   #e74c3c #e74c3c #ecf0f1
    }
}

# hide windows title bar
for_window [class="^.*"] border pixel 1
new_window 1pixel

I copied colors of the status bar from a place in the web (I don’t remember where) and I liked them, so I decided to keep them in my config.

You can see a lot of keywords $mod in this config. It’s just Win key on your keyboard. This config contains a few customizations I made. Probably, I’ll describe them in details in separate articles.

One of the useful customizations is key binding for locking the screen. I needed to install additional program called i3lock and add the following key binding for it:

bindsym $mod+b exec "i3lock -c 17212b"

Value in the end of this config is the background color of the locked screen.

Moving around the system

We don’t need to know a lot of keybinding to move aroud the system and use it.

Here are a few default shortcuts I use daily:

  • $mod+Enter - opens terminal window
  • $mod+d - opens dmenu with available programs (start typing your program name and Hit enter; hit Esc to close it)
  • $mod+arrow - switches between windows (arrows: left, right, top, bottom)
  • $mod+Shift+arrow - moves windows (arrows: left, right, top, bottom)
  • $mod+number - switches between workspaces (1, 2, 3, etc.)
  • $mod+Shift+number - moves window to another workspace (1, 2, 3, etc.)
  • $mod+Shit+space - detaches/attaches window (in case you want to have floating window or you don’t want to use it in full screen mode)
  • $mod+Shift+q - closes window
  • $mod+Shift+e - opens dialog for closing i3

That’s it.

I also use customized/non-default key bindings, which are not default, but they’re defined in my config:

  • $mod+b - locks screen
  • $mod+m - allows to move floating (detached) window (we need to use shortcut, move window around and then use shortcut again to finish the process)
  • $mod+r - allows to resize floating (detached) window in the same manner like moving it
  • media keys (mute/volume+/volume-/play/pause/next/prev) - they work in the same way like on any other system, but we need to configure this explicitly for i3
  • brightness keys (brightness+/-) - same story like media keys

It’s powerful thing and we can configure and customize our own key bindings for the stuff we need.

Status bar configuration as a code

Besides system configuration described above, we can keep status bar configuration as a code too! Configuration of the i3bar is well described in the official documentation. As you could see in my ~/.config/i3/config file above bar section looks as follows:

bar {
workspace_buttons yes
    position top
    status_command i3status
font pango:Roboto 10
height 25
tray_output none

colors {
background #17212b
        statusline #ffffff
        separator  #666666
        active_workspace   #2c3e50 #2c3e50 #1abc9c
        focused_workspace  #2c3e50 #2c3e50 #1abc9c
        inactive_workspace #2c3e50 #2c3e50 #ecf0f1
        urgent_workspace   #e74c3c #e74c3c #ecf0f1
    }
}

Command called status_command invokes i3status program, which is default setup, but there are at least a few programs, which can be used instead of this to customize and enhance status bar. For now, I decided to use default option because it’s good enough for me. You can also see that I customized colors, font, height, removed tray icons and moved status bar to the top (default position is bottom).

When we are using i3status, then detailed configuration of status bar is located in ~/.config/i3status/config file. You can check my current config below and in my dotfiles repository

# i3status configuration file.
# see "man i3status" for documentation.

# It is important that this file is edited as UTF-8.
# The following line should contain a sharp s:
# ß
# If the above line is not correctly displayed, fix your editor first!

general {
        colors = true
        interval= 10
    color_good      = '#88b090'
    color_degraded  = '#ccdc90'
    color_bad       = '#e89393'
}

order += "wireless _first_"
order += "ethernet _first_"
order += "battery 0"
order += "battery 1"
order += "disk /"
order += "disk /home"
order += "load"
order += "cpu_usage"
order += "memory"
order += "volume master"
order += "read_file spotify"
order += "read_file screen_brightness"
order += "read_file pacman_new_packages"
order += "read_file rss"
order += "read_file aqi"
order += "read_file weather"
order += "tztime local"

wireless _first_ {
        format_up = " %quality %essid %ip"
        format_down = ""
}

ethernet _first_ {
        format_up = "%ip (%speed)"
        format_down = ""
}

battery 0 {
    format = "%status %percentage %remaining → 0"
    format_down = ""
    last_full_capacity = true
    integer_battery_capacity = true
    low_threshold = 11
    threshold_type = percentage
    hide_seconds = true
    status_chr = " "
    status_bat = " "
    status_unk = " "
    status_full = " "
}

battery 1 {
    format = "%status %percentage %remaining → 1"
    format_down = ""
    last_full_capacity = true
    integer_battery_capacity = true
    low_threshold = 11
    threshold_type = percentage
    hide_seconds = true
    status_chr = " "
    status_bat = " "
    status_unk = " "
    status_full = " "
}

disk "/" {
format = " %avail"
}

disk "/home" {
        format = "⌂ %avail"
}

load {
        format = " %1min"
}

cpu_usage {
format = "💻 %usage"
}

memory {
        format = "⛁ %used"
        threshold_degraded = "1G"
        format_degraded = "MEMORY < %available"
}

volume master {
        format = "🔉 %volume"
        format_muted = "🔇 %volume"
        device = "default"
}

read_file spotify {
        format = "♪ %content"
        path = "/var/log/scripts/spotify.log"
}

read_file screen_brightness {
        format = "🔆 %content%"
        path = "/var/log/scripts/screen_brightness.log"
}

read_file pacman_new_packages {
format = "📦 %content"
path = "/var/log/scripts/pacman_new_packages.log"
}

read_file rss {
format = "📶 %content"
path = "/var/log/scripts/newsboat.log"
}

read_file aqi {
format = "%content"
path = "/var/log/scripts/aqi.log"
}

read_file weather {
format = "%content"
path = "/var/log/scripts/weather.log"
}

tztime local {
        format = " %a %Y-%m-%d ⌚ %H:%M %Z %z  "
}

We can setup refresh interval of our status bar, which is 10 s in my case, but we can increase or decrease it if we want.

As you can see, I configured multiple things. Going from the left:

  • network connectivity (WiFi + strength or wired connection + IP address)
  • usage of battery 0
  • usage of battery 1 (I have 2 batteries in my Thinkpad)
  • free space on the root directory
  • free space in the home directory
  • load (number of processes waiting to be executed)
  • usage of CPU
  • usage of RAM
  • sound
  • currently played Spotify song
  • screen brightness
  • number of new pacman packages to be updated
  • number of new RSS news from newsboat
  • air quality in my city monitored via Airly
  • weather in my city
  • date, time and time zone

To make it all work, I needed to write a few additional scripts in Python and Bash, create log files to be read in the status bar, configure dunst notifications to monitor changing Spotify songs, etc. Maybe I’ll put details of these configs in the separate articles.

Notifications

By default i3 doesn’t have any notifications. If we want to have them, we need to install notification daemon. We have a few choices. I decided to go with dunst, which is nice and minimalistic notification daemon. It’s also configurable and we can put its configuration into ~/.config/dunst/dunstrc file. You can check my configuration for this daemon in my dotfiles repo as well as previous configs. I’ve done a few adjustments in this file to make notifications be consistent with colors of the system and my personal preferences. I also moved notification window a little bit down because it was covering status bar. You can test notification with notify-send command. Remember to kill dunst after applying changes in the config to see them. Moreover, after installing dunst, we need to start it to invoke the daemon and then after future system start-ups it will start automatically.

Working with multiple screens

In my current setup, I have laptop screen and external monitor. By default, i3 utilizes all available screens and first workspace is located in my primary (laptop) monitor. When I would like to use this workspace on my external screen, when laptop lid is closed, then I need to disable laptop screen. It’s not neccessary, when you are using both laptop and external monitor. To do that, I’m using xrandr program. I created wrapper for this program called monitor.sh, which you can find in .scripts directory in my dotfiles repository.

Key functionalities of this script looks as follows:

function list {
  xrandr --listmonitors
}

function laptopoff {
  xrandr --output eDP-1 --off
}

function laptopmirror {
  xrandr --output eDP-1 --mode 1920x1080 --output HDMI-2 --mode 1920x1080 --same-as eDP-1
}

function reset {
  xrandr --auto
}

function wallreload {
  feh --bg-scale ~/Pictures/config/wallpaper/wallpaper.jpg
}

When I start the system, log in and close the laptop lid, I need to execute monitor.sh laptopoff to disable laptop screen, and then monitor.sh wallreload to reload wallpaper and adjust it into the higher resolution for the workspace 1. eDP-1 is name of my laptop monitor and it may be different in your case. In order, to reset settings, I can use reset command. It’s more convenient than remembering all xrandr commands.

WiFi and Bluetooth

WiFi

i3 doesn’t have any control panel window known from desktop environments like Gnome. If we want to use WiFi and Bluetooth, we need command line tools (or GUI if we prefer) installed additionally in the system. For WiFi, I’m using nmcli. To list WiFi networks, we can type:

nmcli dev wifi

To connect to the given network, we can type:

nmcli --ask device wifi connect hot_spot_name

I also created wrapper for this, which you can find in network.sh file inside .scripts directory in my dotfiles repository.

Bluetooth

We have the same story with Bluetooth. In order to connect to bluetooth devices, I’m using bluetoothctl program.

To display devices, we can type:

bluetoothctl devices

In order to connect to the device, we can type:

bluetoothctl connect mac_address

I also created wrapper for this stuff, which allows me to connect to different devices, enable/disable bluetooth, start scanning, show paired devices, etc. You can find it in bluetooth.sh in my dotfiles repository in the same place where previous scripts are located.

Final thoughts

i3 WM is definitely great Window Manager. Probably the best I have been using so far. Initial configuration and setup can be overwhelming, but once we get into it, it will become clear and simple. Whole system is very fast, highly configurable, we can store complete configuration in the files and easily reproduce it on other machines or in our own machine in case when something will go wrong or we just want to reinstall the system. Using shortcuts and tiling windows all the time may be odd in the beginning, but once we get used to it, usage of this system is very efficient and conveninent. I have been using MS Windows, macOS, Unity desktop environment and Gnome in the past. i3wm is my favorite environment so far. Once we perform all the configuration in the beginning, we don’t have to touch it later until we want to change or add something. I wouldn’t recommend this WM to someone who doesn’t like computers, configurations, solving problems, etc. If you want something working out of the box, which you can trade for speed, high customization, control over your system, etc., then you should choose macOS, Windows or maybe Gnome if you prefer Linux.

References