There are so many tiling window manager out there, I have tried some of them and from my experience, Qtile is best fit for me and my workflow.
Little introduction about Qtile
Qtile is written and configured fully in python
so as a python developer, I feel like home when using Qtile. There is no need to learn other language like lua
to config awesome, haskell
for xmonad, i3 config syntax
for i3wm, xml
for openbox, C
for edit dwm source code, ...
Quick compare Qtile and the other, I must state some point that:
- Qtile functionality is similar to xmonad, but I find it is easy to learn
python
thanhaskell
- Both Qtile and i3wm are well documented but i3wm lack of some useful tiling layout
- Qtile and openbox are two different concept of window manager, one is tiling, one is floating
- Qtile and dwm have a huge difference in the way of config. Qtile configuring is straight forward meanwhile dwm require user skill to edit and patch source code, manual compile and install from that source
Install and setup
To install Qtile, type one of these command to your terminal (chose the right for your distro)
Ubuntu and Debian base
As official document of Qtile, they say that Ubuntu has drop Qtile package from main repo so we need to install qtile use pip
pip install xcffib
pip install qtile
Fedora
Since stable version of Qtile is not in fedora repo so from the document, they say that user should install from source instruction
Arch and Arch base
sudo pacman -S qtile
Void linux
sudo xbps-install qtile
Next go to default config and copy all the default configuration to $HOME/.config/qtile/config.py
Starting Qtile
There are two common ways
- From any default login manager like
gdm
,lightdm
,sddm
, ... all have a button for you to choose the session you want to use - Use
startx
command from linux tty. For this, copy/etc/X11/xinit/xinitrc
to$HOME/.xinitrc
, remove these line if it has
Then addtwm & xclock -geometry 50x50-1+1 & xterm -geometry 80x50+494+51 & xterm -geometry 80x20+494-0 & exec xterm -geometry 80x66+0+0 -name login
to the end ofqtile start
.xinitrc
Configurations
By default, Qtile has all config in a single config.py
. I recommend to split it into three file
bar_type.py
store all your design of the status barcolors.py
store the definition of your color schemeconfig.py
mainly define your shortcut
Note that these file must be in ~/.config/qtile/
Now I will share the content of my config file
config
from libqtile import bar, layout
from libqtile.config import Click, Drag, Group, Key, Match, Screen
from libqtile.lazy import lazy
from colors import color
import bar_type
mod = "mod4"
keys = [
Key([mod], "h", lazy.layout.left(), desc="Move focus to left"),
Key([mod], "l", lazy.layout.right(), desc="Move focus to right"),
Key([mod], "j", lazy.layout.down(), desc="Move focus down"),
Key([mod], "k", lazy.layout.up(), desc="Move focus up"),
Key([mod, "shift"], "h", lazy.layout.swap_left(), desc="Move window to the left"),
Key([mod, "shift"], "l", lazy.layout.swap_right(), desc="Move window to the right"),
Key([mod, "shift"], "j", lazy.layout.shuffle_down(), desc="Move window down"),
Key([mod, "shift"], "k", lazy.layout.shuffle_up(), desc="Move window up"),
Key([mod], "b", lazy.layout.grow(), desc="Increase size of window"), Key([mod], "s", lazy.layout.shrink(),
desc="Decrease size of window"),
Key([mod], "f", lazy.window.toggle_floating(), desc="Toggle floating",),
Key([mod], "m", lazy.window.toggle_fullscreen(), desc="Toggle fullscreen"),
Key([mod], "Tab", lazy.next_layout(), desc="Toggle between layouts"),
Key([mod], "q", lazy.window.kill(), desc="Kill focused window"),
Key([mod], "Escape", lazy.spawn("xsecurelock"), desc='Lock screen'),
Key([mod], "Return", lazy.spawn("alacritty"), desc="Launch terminal"),
Key(["control"], "space", lazy.spawn("rofi -show run"), desc="Spawn a command"),
Key([mod], "a", lazy.spawn("xfce4-appfinder"), desc="Find applications"),
Key([], "XF86AudioRaiseVolume", lazy.spawn("amixer set Master 5%+"), desc='Volume up'),
Key([], "XF86AudioLowerVolume", lazy.spawn("amixer set Master 5%-"), desc='Volume down'),
Key([], "XF86AudioMute", lazy.spawn("amixer set Master toggle"), desc='Toggle volume'),
Key([], "XF86AudioMicMute", lazy.spawn("amixer sset Capture toggle"), desc='Toggle volume'),
Key([], "Print", lazy.spawn("xfce4-screenshooter"), desc='Screenshot'),
Key([mod, "control"], "r", lazy.reload_config(), desc="Reload the config"),
Key([mod, "control"], "q", lazy.shutdown(), desc="Shutdown Qtile"),
]
groups = [Group(i) for i in "12345678"]
for i in groups:
keys.extend(
[
Key(
[mod],
i.name,
lazy.group[i.name].toscreen(),
desc="Switch to group {}".format(i.name),
),
Key(
[mod, "shift"],
i.name,
lazy.window.togroup(i.name, switch_group=True),
desc="Switch to & move focused window to group {}".format(i.name),
),
]
)
mouse = [
Drag([mod], "Button1", lazy.window.set_position_floating(), start=lazy.window.get_position()),
Drag([mod], "Button3", lazy.window.set_size_floating(), start=lazy.window.get_size()),
Click([mod], "Button2", lazy.window.bring_to_front()),
]
layouts = [
layout.MonadTall(
border_focus=color['ac'],
border_normal=color['dark_ac'],
border_width=1,
margin=12,
),
layout.MonadWide(
border_focus=color['ac'],
border_normal=color['dark_ac'],
border_width=1,
margin=12,
),
]
widget_defaults = dict(
font="Fira Code",
fontsize=22,
padding=10,
)
extension_defaults = widget_defaults.copy()
screens = [
Screen(
top=bar.Bar(bar_type.bar_theme, 44, background=color['black']),
),
]
dgroups_key_binder = None
dgroups_app_rules = []
follow_mouse_focus = True
bring_front_click = False
cursor_warp = False
floating_layout = layout.Floating(
border_focus=color['ac'],
float_rules=[
# Run the utility of "xprop" to see the wm class and name of an X client.
*layout.Floating.default_float_rules,
Match(wm_class="confirmreset"), # gitk
Match(wm_class="makebranch"), # gitk
Match(wm_class="maketag"), # gitk
Match(wm_class="ssh-askpass"), # ssh-askpass
Match(wm_class="tk"), #application in python tk
Match(wm_class="python3.10"), #application in python
Match(title="Application Finder"), # app finder
Match(title="branchdialog"), # gitk
Match(title="pinentry"), # GPG key password entry
Match(title="steam"), # steam
]
)
auto_fullscreen = True
focus_on_window_activation = "smart"
reconfigure_screens = True
auto_minimize = False
wl_input_rules = None
wmname = "Qtile"
bar_type
from libqtile import widget
from colors import color
bar_theme = [
widget.TextBox(
text=' ',
padding=0.1,
background=color['black'],
),
widget.GroupBox(
background=color['black'],
disable_drag=True,
highlight_method='line',
urgent_alert_method='text',
highlight_color=color['black'],
this_current_screen_border=color['fg'],
this_screen_border=color['fg'],
other_current_screen_border=color['fg'],
other_screen_border=color['fg'],
active=color['fg'],
inactive=color['dark_fg'],
urgent_text=color['fg'],
use_mouse_wheel=False,
),
widget.Spacer(),
widget.Battery(
format='Power {percent:2.0%}',
foreground=color['fg'],
background=color['black'],
show_short_text=False,
update_interval=60,
),
widget.TextBox(
text=' ',
background=color['black'],
),
widget.Clock(
format="%a %I:%M %P",
background=color['black'],
foreground=color['fg'],
update_interval=60,
),
widget.TextBox(
text=' ',
padding=0.1,
background=color['black'],
),
]
colors
color = {
'ac': '#689d6a',
'dark_ac': '#282828',
'fg': '#ffffff',
'dark_fg': '#808080',
'black': '#000000CC',
}
Let me explain details about these files
Config file
First section
from libqtile import bar, layout
from libqtile.config import Click, Drag, Group, Key, Match, Screen
from libqtile.lazy import lazy
from colors import color
import bar_type
mod = "mod4"
Just import module need for qtile configuration. Notice that I also include the bar and the color from bar_type module and colors module
Next, I set mod variable to mod4 which indicate the super key (usually the key with window logo in laptop). this variable is then use for key binding setting.
If you prefer Alt, set mod = "mod1"
Second section
keys = [
Key([mod], "h", lazy.layout.left(), desc="Move focus to left"),
Key([mod], "l", lazy.layout.right(), desc="Move focus to right"),
Key([mod], "j", lazy.layout.down(), desc="Move focus down"),
Key([mod], "k", lazy.layout.up(), desc="Move focus up"),
Key([mod, "shift"], "h", lazy.layout.swap_left(), desc="Move window to the left"),
Key([mod, "shift"], "l", lazy.layout.swap_right(), desc="Move window to the right"),
Key([mod, "shift"], "j", lazy.layout.shuffle_down(), desc="Move window down"),
Key([mod, "shift"], "k", lazy.layout.shuffle_up(), desc="Move window up"),
Key([mod], "b", lazy.layout.grow(), desc="Increase size of window"), Key([mod], "s", lazy.layout.shrink(), desc="Decrease size of window"),
Key([mod], "f", lazy.window.toggle_floating(), desc="Toggle floating",),
Key([mod], "m", lazy.window.toggle_fullscreen(), desc="Toggle fullscreen"),
Key([mod], "Tab", lazy.next_layout(), desc="Toggle between layouts"),
Key([mod], "q", lazy.window.kill(), desc="Kill focused window"),
Key([mod], "Escape", lazy.spawn("xsecurelock"), desc='Lock screen'),
Key([mod], "Return", lazy.spawn("alacritty"), desc="Launch terminal"),
Key(["control"], "space", lazy.spawn("rofi -show run"), desc="Spawn a command"),
Key([mod], "a", lazy.spawn("xfce4-appfinder"), desc="Find applications"),
Key([], "XF86AudioRaiseVolume", lazy.spawn("amixer set Master 5%+"), desc='Volume up'),
Key([], "XF86AudioLowerVolume", lazy.spawn("amixer set Master 5%-"), desc='Volume down'),
Key([], "XF86AudioMute", lazy.spawn("amixer set Master toggle"), desc='Toggle volume'),
Key([], "XF86AudioMicMute", lazy.spawn("amixer sset Capture toggle"), desc='Toggle volume'),
Key([], "Print", lazy.spawn("xfce4-screenshooter"), desc='Screenshot'),
Key([mod, "control"], "r", lazy.reload_config(), desc="Reload the config"),
Key([mod, "control"], "q", lazy.shutdown(), desc="Shutdown Qtile"),
]
The keys array contains my key binding in the form
Key(prefix, key on keyboard, function to execute, description)
There are some Qtile builtin function like lazy.layout.left()
to move focus to the left window, lazy.shutdown()
to exit Qtile.
If you need execute bash command, Qtile also offer lazy.spawn("your bash command")
to fit your need
Third section
groups = [Group(i) for i in "12345678"]
for i in groups:
keys.extend(
[
Key(
[mod],
i.name,
lazy.group[i.name].toscreen(),
desc="Switch to group {}".format(i.name),
),
Key(
[mod, "shift"],
i.name,
lazy.window.togroup(i.name, switch_group=True),
desc="Switch to & move focused window to group {}".format(i.name),
),
]
)
mouse = [
Drag([mod], "Button1", lazy.window.set_position_floating(), start=lazy.window.get_position()),
Drag([mod], "Button3", lazy.window.set_size_floating(), start=lazy.window.get_size()),
Click([mod], "Button2", lazy.window.bring_to_front()),
]
The key and mouse binding are just the default, I only modify groups array, which will set how many workspace I want to use. You can see the workspace indicator in the left side of the bar.
Fourth section
layouts = [
layout.MonadTall(
border_focus=color['ac'],
border_normal=color['dark_ac'],
border_width=1,
margin=12,
),
layout.MonadWide(
border_focus=color['ac'],
border_normal=color['dark_ac'],
border_width=1,
margin=12,
),
]
This part define which layout I want to use, see built-in layout.
Layout is basically an algorithm to arrange windows on screen, I have try all the default but to me, only MonadTall
and MonadWide
is usefull for daily use.
Fifth section
widget_defaults = dict(
font="Fira Code",
fontsize=22,
padding=10,
)
extension_defaults = widget_defaults.copy()
screens = [
Screen(
top=bar.Bar(bar_type.bar_theme, 44, background=color['black']),
),
]
The code told everything, this section define how the bar look.
The bar contains widget such as clock, battery info,... I will explain more in next few sections.
Last section of config file
dgroups_key_binder = None
dgroups_app_rules = []
follow_mouse_focus = True
bring_front_click = False
cursor_warp = False
floating_layout = layout.Floating(
border_focus=color['ac'],
float_rules=[
# Run the utility of "xprop" to see the wm class and name of an X client.
*layout.Floating.default_float_rules,
Match(wm_class="confirmreset"), # gitk
Match(wm_class="makebranch"), # gitk
Match(wm_class="maketag"), # gitk
Match(wm_class="ssh-askpass"), # ssh-askpass
Match(wm_class="tk"), #application in python tk
Match(wm_class="python3.10"), #application in python
Match(title="Application Finder"), # app finder
Match(title="branchdialog"), # gitk
Match(title="pinentry"), # GPG key password entry
Match(title="steam"), # steam
]
)
auto_fullscreen = True
focus_on_window_activation = "smart"
reconfigure_screens = True
auto_minimize = False
wl_input_rules = None
wmname = "Qtile"
In this part, only focus on float_rules
array. It manage which window will be float.
To indicate which type of window will be floated, install xorg-xprop
, then type xprop
after open the deserve window, the mouse will turn to +
symol. Click on that window and all information include WM_CLASS(STRING) = "......."
appear in terminal that run xprop
.
Copy the string and add too Match(wm_class="class tring here")
Colors define file
This is a dictionary that store the color I want to use in Qtile
Bar_type file
The configuration of bar is an array that contain widgets, the index of widgets is counted from 0 which corresponding to the left most widget on the bar.
In my case, the left most is TextBox
because I want some padding to left. The next one is GroupBox
which show information about the workspace I'm using.
The widget is in the form widget.NameOfWidget( configuration for widget )
, for example:
widget.Battery(
format='Power {percent:2.0%}',
foreground=color['fg'],
background=color['black'],
show_short_text=False,
update_interval=60,
),
The battery widget is used, the format to show on bar is Power "current percentage"%
, update_interval
is the amount of time count in second between two different updates.
You can find and add more widget here widget