UP | HOME
Kristian Alexander P

Kristian Alexander

Free Software Enthusiast | GNU Emacs User

Qtile Configuration

Published on Mar 14, 2022 by Kristian Alexander P.

About

screenshot_2022_03_19-00_38-38.png
Figure 1: the mandatory screenshot

This file will be the base of my configuration. With org-mode I can tangle every source code snippet in this file and put them in one or more files.

These are the keybindings:

  • Navigation

    Table 1: Navigation Key
    Modifier Key Key Function
    Mod4 (Super Key) h Switch to left window
    Mod4 (Super Key) j Switch to lower window
    Mod4 (Super Key) k Switch to upper window
    Mod4 (Super Key) l Switch to right window
  • Window Movement

    Table 2: Window Movement
    Modifier Key Key Function
    Mod4 (Super Key) + Shift h Move window to the left
    Mod4 (Super Key) + Shift j Move window to bottom
    Mod4 (Super Key) + Shift k Move window to top
    Mod4 (Super Key) + Shift l Move window to the right
  • Resizing Only works in floating mode.

    Table 3: Window Resizing
    Modifier Key Key Function
    Mod4 (Super Key) + Control h grow window to the left
    Mod4 (Super Key) + Control j grow window to bottom
    Mod4 (Super Key) + Control k grow window to top
    Mod4 (Super Key) + Control l grow window to the right
  • Basic Commands

    Table 4: Basic Command
    Modifier Key Key Function
    Mod4 (Super Key) TAB Toggle between previous group
    Mod4 (Super Key) + Mod1 (ALT) TAB Toggle between layouts
    Mod4 (Super Key) F4 Kill focused window
    Mod4 (Super Key) + Control r Reload Qtile configuration file(s)
    Mod4 (Super Key) + Control q Logout
    Mod4 (Super Key) d Spawn Command
    Mod4 (Super Key) Space Switch to next window
  • Other Commands
    • picom These commands need picom available and running.

      Table 5: Other Command
      Modifier Key Key Function  
      Mod4 (Super Key) F2 Decrease transparency value for current window picom
      Mod4 (Super Key) F3 Increase transparency value for current window picom
    • Hardware keys

      Table 6: Hardware Keys
      Modifier Key Key Function  
      - XF86MonBrightnessUp Increase brightness brightnessctl
      - XF86MonBrightnessDown Decrease brightness brightnessctl
      - XF86AudioLowerVolume Decrease volume pactl
      - XF86AudioRaiseVolume Increase volume pactl
      - XF86AudioMute Toggle volume muting pactl
      - XF86AudioPlay Toggle media play/pause playerctl
      - XF86AudioNext Toggle media play next playerctl
      - XF86AudioPrev Toggle media play previous playerctl
      - XF86Mail Opens Notmuch in Emacs emacs
      - XF86MyComupter Open Thunar thunar
      - Print Fullscreen screenshot maim
      Mod4 (Super Key) Print Selection screenshot maim
    • Various apps

      Table 7: Various Apps
      Modifier Key Key Function  
      Mod4 (Super Key) Return (Enter Key) Opens Terminal Configurable via Variable
      Mod4 (Super Key) + Shift Return (Enter Key) Open Emacs vterm Emacs
      Mod4 (Super Key) b Opens Firefox firefox
      Mod4 (Super Key) m Opens Notmuch in Emacs emacs
      Mod4 (Super Key) n Opens emacsclient window emacs
      Mod4 (Super Key) + Shift m Opens Emms in Emacs emacs
      Mod4 (Super Key) f Opens Elfeed in Emacs emacs
      Mod4 (Super Key) t Opens Telega in Emacs emacs

Main configuration

header-args: :tangle ~/.config/qtile/config.py :mkdirp t

If you open this file in Emacs, this section has a property drawer that tangles every source code blocks in this section to config.py.

Header

Qtile will look for user configuration at ~/.config/qtile/config.py, so we’ll start there.

"""Qtile configuration file.

Copyright (c) 2010 Aldo Cortesi
Copyright (c) 2010, 2014 dequis
Copyright (c) 2012 Randall Ma
Copyright (c) 2012-2014 Tycho Andersen
Copyright (c) 2012 Craig Barnes
Copyright (c) 2013 horsik
Copyright (c) 2013 Tao Sauvage
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""

Since I’m basing from the default config, use their license header for now, also note in org-mode, if a source code block is using :shebang arguments, it’ll set the file as executable, it’s not required for qtile actually, but a good habit nonetheless.

Imports

This is what the default configuration listed:

from typing import List  # noqa: F401

from libqtile import bar, layout, widget, hook
from libqtile.config import Click, Drag, Group, Key, Match, Screen
from libqtile.lazy import lazy

  • Imports for custom function.

    import psutil
    import subprocess
    import os
    import json
    from datetime import datetime
    

Variables

Subtitutes the actual keys.

mod = "mod4"
alt = "mod1"
altgr = "mod5"
shift = "shift"
control = "control"

left = "h"
right = "l"
down = "j"
up = "k"

Other variables

if os.getenv("TERMINAL"):
    terminal = os.getenv("TERMINAL")
else:
    terminal = "xterm"
groups = [Group(i) for i in "123456789"]
dgroups_key_binder = None
dgroups_app_rules = []  # type: List
follow_mouse_focus = False
bring_front_click = True
cursor_warp = True
auto_fullscreen = True
focus_on_window_activation = "smart"
reconfigure_screens = True
auto_minimize = True
wmname = "LG3D"
home = os.getenv("HOME")

wlan_interface = "wlo1"

if os.getenv("XDG_CONFIG_HOME"):
    xdg_config_home = os.getenv("XDG_CONFIG_HOME")
else:
    xdg_config_home = os.path.join(home, ".config")

if os.getenv("XDG_CACHE_HOME"):
    xdg_cache_home = os.getenv("XDG_CACHE_HOME")
else:
    xdg_cache_home = os.path.join(home, ".cache")

if os.getenv("XDG_DATA_HOME"):
    xdg_data_home = os.getenv("XDG_DATA_HOME")
else:
    xdg_data_home = os.path.join(home, ".local/share")

if os.getenv("XDG_PICTURES_DIR"):
    xdg_pictures_dir = os.getenv("XDG_PICTURES_DIR")
else:
    xdg_pictures_dir = os.path.join(home, "Pictures")

if os.path.exists("/usr/share/backgrounds/archlinux"):
    wallpaper_dir = "/usr/share/backgrounds/archlinux"
elif os.path.exists(os.path.join(home, xdg_pictures_dir + "Wallpapers")):
    wallpaper_dir = os.path.join(home, xdg_pictures_dir + "Wallpapers")
elif os.path.exists(os.path.join(xdg_data_home, "backgrounds")):
    wallpaper_dir = os.path.join(home, xdg_data_home + "backgrounds")

color_file = "wal/colors.json"
colors_absolute = os.path.join(xdg_cache_home, color_file)

Variables and import list will obviously changed depending on the settings I use.

Functions

  • Check running process.

    def checkIfProcessRunning(processName):
          """Check if process running.
    
          Check if there is any running process that contains
          the given name processName.
          """
          # Iterate over the all the running process
          for proc in psutil.process_iter():
              try:
                    # Check if process name contains the given name string.
                  if processName.lower() in proc.name().lower():
                      return True
              except (psutil.NoSuchProcess,
                      psutil.AccessDenied,
                      psutil.ZombieProcess):
                  pass
          return False
    
    
  • maim
    • fullscreen

      def maimFullScreen():
          now = datetime.now()
          screenshotfile = os.path.join(screenshots_dir,
                                        now.strftime("%Y_%m_%d-%H_%M_%S")
                                        + ".png")
          return subprocess.Popen(["maim", screenshotfile])
      
      
  • Parse Xresources

    if os.path.isfile(colors_absolute):
        with open(colors_absolute, encoding="utf-8") as colorfile:
            color_list = json.load(colorfile)
            xcursor = color_list['special']['cursor']
            xbackground = color_list['special']['background']
            xforeground = color_list['special']['foreground']
            xcolor0 = color_list['colors']['color0']
            xcolor1 = color_list['colors']['color1']
            xcolor2 = color_list['colors']['color2']
            xcolor3 = color_list['colors']['color3']
            xcolor4 = color_list['colors']['color4']
            xcolor5 = color_list['colors']['color5']
            xcolor6 = color_list['colors']['color6']
            xcolor7 = color_list['colors']['color7']
            xcolor8 = color_list['colors']['color8']
            xcolor9 = color_list['colors']['color9']
            xcolor10 = color_list['colors']['color10']
            xcolor11 = color_list['colors']['color11']
            xcolor12 = color_list['colors']['color12']
            xcolor13 = color_list['colors']['color13']
            xcolor14 = color_list['colors']['color14']
            xcolor15 = color_list['colors']['color15']
    
    

TODO Keys

  • State “TODO” from [2022-03-15 Tue 04:54]

Definitely will be tweaking these. I also break the keybindings into several sections.

  • navigations

    keys = []
    # Navigation
    
    keys.extend([
        # Window Navigation
        Key([mod], left,
            lazy.layout.left()),
        Key([mod], down,
            lazy.layout.down()),
        Key([mod], up,
            lazy.layout.up()),
        Key([mod], right,
            lazy.layout.right()),
        ]
    )
    
  • Window movement

    keys.extend([
        # Window movement
        Key([mod, shift], left,
            lazy.layout.shuffle_left()),
        Key([mod, shift], down,
            lazy.layout.shuffle_down()),
        Key([mod, shift], up,
            lazy.layout.shuffle_up()),
        Key([mod, shift], right,
            lazy.layout.shuffle_right()),
        ]
    )
    
  • resize

    keys.extend([
        # window resize
        Key([mod, control], left,
            lazy.layout.grow_left()),
        Key([mod, control], down,
            lazy.layout.grow_down()),
        Key([mod, control], up,
            lazy.layout.grow_up()),
        Key([mod, control], right,
            lazy.layout.grow_right()),
        ]
    )
    
  • commands

    keys.extend([
        Key([mod], "Tab",
            lazy.screen.toggle_group()),
        Key([mod, alt], "Tab",
            lazy.next_layout()),
        Key([mod], "F4",
            lazy.window.kill()),
        Key([mod, "control"], "r",
            lazy.reload_config()),
        Key([mod, "control"], "q",
            lazy.shutdown()),
        Key([mod], "d",
            lazy.spawncmd()),
        Key([mod], "space",
            lazy.layout.next()),
        ]
    )
    
  • apps

    keys.extend([
        Key([mod], "F2",
            lazy.spawn("picom-trans -c -5"),
            desc="Decrease transparency by 5%"),
        Key([mod], "F3",
            lazy.spawn("picom-trans -c +5"),
            desc="Increase transparency by 5%"),
        # Hardware keys
        Key([], "XF86MonBrightnessUp",
            lazy.spawn("brightnessctl set +1%"),
            desc="Increase brightness by 1%"),
        Key([], "XF86MonBrightnessDown",
            lazy.spawn("brightnessctl set 1%-"),
            desc="Decrease brightness by 1%"),
        Key([], "XF86AudioLowerVolume",
            lazy.spawn("pactl set-sink-volume @DEFAULT_SINK@ -1%"),
            desc="Decrease volume by 1%"),
        Key([], "XF86AudioRaiseVolume",
            lazy.spawn("pactl set-sink-volume @DEFAULT_SINK@ +1%"),
            desc="Increase volume by 1%"),
        Key([], "XF86AudioMute",
            lazy.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle"),
            desc="Toggle volume on/off"),
        Key([], "XF86AudioPlay",
            lazy.spawn("playerctl play-pause"),
            desc="Toggle play/pause"),
        Key([], "XF86AudioNext",
            lazy.spawn("playerctl next"),
            desc="Play next"),
       Key([], "XF86AudioPrev",
            lazy.spawn("playerctl previous"),
            desc="Play previous"),
        Key([mod], "Return",
            lazy.spawn(terminal)),
        Key([mod, shift], "Return",
            lazy.spawn("emacsclient -ce '(vterm)'")),
        Key([mod], "e",
            lazy.spawn("thunar")),
        Key([], "XF86MyComputer",
            lazy.spawn("thunar")),
        Key([mod], "b",
            lazy.spawn("firefox")),
        Key([mod], "n",
            lazy.spawn("emacsclient -c -a \"\"")),
        Key([mod], "m",
            lazy.spawn("emacsclient -c -e '(notmuch)'")),
        Key([], "XF86Mail",
            lazy.spawn("emacsclient -c -e '(notmuch)'")),
        Key([mod, shift], "m",
            lazy.spawn("emacsclient -c -e '(emms-browse-by-artist)'")),
        Key([mod], "f",
            lazy.spawn("emacsclient -c -e '(elfeed)'")),
        Key([mod], "t",
            lazy.spawn("emacsclient -c -e '(telega)'")),
    
        # Print
        Key([], "Print",
            lazy.spawn("sh -c \"maim \
            ~/Pictures/Screenshots/screenshot_$(date +%Y_%m_%d-%H_%M-%S).png\"")),
        Key([mod], "Print",
            lazy.spawn("sh -c \"maim -o -s -u \
            ~/Pictures/Screenshots/screenshot_selection_$(date +%Y_%m_%d-%H_%M-%S).png\"")),
        ]
    )
    

Groups

The default already set a for loop for group keybindings, it’s also what I set in other window manager so I’m using it.

for i in groups:
    keys.extend(
        [
            # mod1 + letter of group = switch to group
            Key(
                [mod],
                i.name,
                lazy.group[i.name].toscreen(),
                desc="Switch to group {}".format(i.name),
            ),
            # mod1 + shift + letter of group = switch to &
            # move focused window to group
            Key(
                [mod, "shift"],
                i.name,
                lazy.window.togroup(i.name, switch_group=True),
                desc="Switch and move window > group {}".format(i.name),
            ),
            # Or, use below if you prefer not to switch to that group.
            # # mod1 + shift + letter of group = move focused window to group
            # Key([mod, "shift"], i.name, lazy.window.togroup(i.name),
            #     desc="move focused window to group {}".format(i.name)),
        ]
    )

TODO Layout

  • State “TODO” from [2022-03-15 Tue 04:59]

I’ll try all of them one by one when I have the time, but the Max and Column will be the ones I’ll use often.

layout_defaults = {
    "border_width": 3,
    "margin": 9,
    "border_focus": xcolor14,
    "border_normal": xcolor15,
}

layouts = [
    layout.Columns(
        border_focus_stack=[xcolor3, xcolor8],
        grow_amount=5,
        **layout_defaults
    ),
    layout.Max(**layout_defaults),
    layout.MonadTall(
        **layout_defaults
    ),
]

TODO Widgets, extensions and screens

  • State “TODO” from [2022-03-15 Tue 05:00]
widget_defaults = dict(
    font="Source Code Pro",
    background=xbackground,
    fontsize=10,
    padding=4,
)

extension_defaults = widget_defaults.copy()

screens = [
    Screen(
        bottom=bar.Bar(
            [
                widget.CurrentLayout(
                    background=xbackground,
                    foreground=xcolor3),
                widget.GroupBox(
                    active=xforeground,
                    foreground=xcolor2,
                    highlight_color=[xbackground, xforeground],
                    inactive=xcolor1,
                    other_current_screen_border=xcolor14,
                    other_screen_border=xcolor14,
                    this_current_screen_border=xcolor4,
                    this_screen_border=xcolor4,
                    urgent_border=xcolor9,
                    urgent_text=xforeground),
                widget.Prompt(),
                widget.WindowName(
                    format="{state}{name}",
                    foreground=xcolor4),
                widget.Backlight(
                    foreground=xcolor10,
                    backlight_name="intel_backlight",
                    change_command="xbacklight -set {0}",
                    format=" {percent:2.0%}",
                    step=5
                ),
                widget.Wlan(
                    foreground=xcolor12,
                    interface=wlan_interface,
                    format=" {essid} {percent:2.0%}"),
                widget.Battery(
                    foreground=xcolor11,
                    charge_char="",
                    discharge_char="",
                    empty_char="",
                    format="{char} {percent:2.0%} {hour:d}:{min:02d}"
                ),
                widget.PulseVolume(
                    foreground=xcolor14,
                    fmt=" {}"),
                widget.Maildir(
                    foreground=xcolor6,
                    maildir_path="~/.mail",
                    sub_folders=[
                        {'label': '', 'path': 'gmail/Inbox'},
                        {'label': '', 'path': 'hotmail/Inbox'},
                        {'label': '', 'path': 'yahoo/Inbox'},
                        {'label': '', 'path': 'ymail/Inbox'},
                    ],
                    subfolder_fmt="{label} {value}",
                    update_interval=300
                ),
                widget.Clock(
                    foreground=xcolor11,
                    format="%Y-%m-%d %a %I:%M %p"),
                widget.Systray(),
            ],
            20,
            opacity=1.0,
            background=xbackground
        ),
    ),
]

Mouse

I don’t think I’ll be modifying these.

# Drag floating layouts.
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()),
]

TODO Floating layout rules

  • State “TODO” from [2022-03-15 Tue 05:03]

Eventually it’ll reflects various apps I use.

floating_layout = layout.Floating(
    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(title="branchdialog"),  # gitk
        Match(title="pinentry"),  # GPG key password entry
    ]
)

Hooks

@hook.subscribe.startup_once
def startup():
    """Run after qtile initialization."""
    processes = [
        ["xrdb", "-merge", "~/.Xresources"],
        ["picom"],
        ["unclutter"],
        ["xsettingsd"],
        ["udiskie", "-t"],
        ["nm-applet"],
        ["blueman-applet"],
        ["wal", "-i", wallpaper_dir],
        ["emacs", "--debug-init", "--daemon"],
        ["dunst"],
        ["/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"]
    ]
    for p in processes:
        if not checkIfProcessRunning(p[0]):
            subprocess.Popen(p)