home | section main page


My Qtile Config (Mocha)

Table of Contents

1. Goals

1.1. We want the configuration to have efficient and ergonomic keybindings.

Commonly used keybindings should be two keys, less common ones should use more.

1.2. We want it to work with other programs that it references.

For example, the qutebrowser and qtile configurations should not cause conflict and they should have mutual consistency.

1.2.1. This will therefore make the configuration self sufficient.

1.3. General enough that it will work for most purposes.

Specialized programs or tools should be in a separate section which will make it easy to parse and remove.

2. Configuration

2.1. Imports and Definitions

All of this is also in the default configuration.

from libqtile import bar, layout, widget
from libqtile.config import Click, Drag, Group, Key, Match, Screen
from libqtile.lazy import lazy
from libqtile.utils import guess_terminal
from libqtile import hook
from libqtile.backend.wayland import InputConfig

import os
import subprocess


mod = "mod4"
terminal = "kitty"
wl_import_rules = None
auto_minimize = True
wmname = "LG3D"

2.2. Hex Colors

We then load the catppuccin colors for the bar and window borders.

def get_colors(theme):
    if theme == "city-lights":
        return [
        # Normal colors
        '#45475a',
        '#f38ba8',
        '#a6e3a1',
        '#f9e2af',
        '#89b4fa',
        '#f5c2e7',
        '#94e2d5',
        '#bac2de',

        # Bright colors
        '#585b70',
        '#f38ba8',
        '#a6e3a1',
        '#f9e2af',
        '#89b4fa',
        '#f5c2e7',
        '#94e2d5',
        '#a6adc8',

        # background
        '#1e1e2e',
        # foreground
        '#cdd6f4',
    ]
    elif theme == "gruvbox":
        return [
        # normal colors
        '#282828',
        '#cc241d',
        '#98971a',
        '#d79921',
        '#458588',
        '#b16286',
        '#689d6a',
        '#a89984',
        #bright colors
        '#928374',
        '#fb4934',
        '#b8bb26',
        '#fabd2f',
        '#83a598',
        '#d3869b',
        '#8ec07c',
        '#ebdbb2',
        # background
        '#282828',
        # foreground
        '#ebdbb2',
    ]

colors = get_colors("gruvbox")

2.3. Keybindings

The keys variable is going to be our final list of keybindings. We start by initializing it wth our window manipulation bindings with vim keys:

2.3.1. Focus controls

Vi inspired keybindings to manipulate focus:

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], "space", lazy.layout.next(), desc="Move window focus to other window"),

    Key([mod, "shift"], "h", lazy.layout.shuffle_left(), desc="Move window to the left"),
    Key([mod, "shift"], "l", lazy.layout.shuffle_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, "control"], "h", lazy.layout.grow_left(), desc="Grow window to the left"),
    Key([mod, "control"], "l", lazy.layout.grow_right(), desc="Grow window to the right"),
    Key([mod, "control"], "j", lazy.layout.grow_down(), desc="Grow window down"),
    Key([mod, "control"], "k", lazy.layout.grow_up(), desc="Grow window up"),
    Key([mod], "n", lazy.layout.normalize(), desc="Reset all window sizes"),
    Key(
        [mod, "shift"],
        "Return",
        lazy.layout.toggle_split(),
        desc="Toggle between split and unsplit sides of stack",
    ),
    Key([mod], "Tab", lazy.next_layout(), desc="Toggle between layouts"),
]

2.3.2. Quit/Restart

keys.extend([
    Key([mod], "q", lazy.window.kill(), desc="Kill focused window"),
    Key([mod, "control"], "r", lazy.reload_config(), desc="Reload the config"),
    Key([mod, "control"], "q", lazy.shutdown(), desc="Shutdown Qtile"),
])

2.3.3. Programs

These are our keybindings for user programs.

keys.extend([
    Key([mod], "r", lazy.spawncmd(), desc="Spawn a command using a prompt widget"),
    Key([mod], "Return", lazy.spawn(terminal), desc="Launch terminal"),
    Key([mod], "e", lazy.spawn("emacs"), desc="Run emacs"),
    Key([mod], "w", lazy.spawn("qutebrowser"), desc="Run Qutebrowser"),
    Key([mod], "f", lazy.spawn("firefox"), desc="Run Firefox"),
    Key([mod], "b", lazy.spawn("blender"), desc="Run Blender"),
    Key([mod], "p", lazy.spawn("krita"), desc="Run Krita"),
    Key([mod], "v", lazy.spawn("inkscape"), desc="Run Inkscape"),
    Key([mod], "g", lazy.spawn("gimp"), desc="Run GIMP"),
    Key([mod], "t", lazy.spawn("torbrowser-launcher"), desc="Run Tor Browser"),
    Key([mod], "i", lazy.spawn("emacsclient --eval \"(emacs-everywhere)\""), desc="Emacs Everywhere!"),
    Key([mod], "d", lazy.spawn("rofi -show run"), desc="rofi command launcher"),
])

2.3.4. XF86

Now we need keybindings for the function keys:

keys.extend([
    Key([], 'XF86AudioLowerVolume', lazy.spawn("pactl set-sink-volume @DEFAULT_SINK@ -5%")),
    Key([], 'XF86AudioRaiseVolume', lazy.spawn("pactl set-sink-volume @DEFAULT_SINK@ +5%")),
    Key([], 'XF86AudioMute', lazy.spawn("pactl set-sink-mute @DEFAULT_SINK@ toggle")),
    Key([], 'XF86MonBrightnessUp', lazy.spawn("light -A 10")),
    Key([], 'XF86MonBrightnessDown', lazy.spawn("light -U 10")),
    Key([], 'XF86AudioNext', lazy.spawn("mpc next")),
    Key([], 'XF86AudioPrev', lazy.spawn("mpc prev")),
    Key([], "XF86AudioPlay", lazy.spawn("mpc toggle"), desc="Play/Pause player"),
    Key([], "Print", lazy.spawn("scrot '%Y-%m-%d-%s_screenshot_$wx$h.jpg' -e 'mv $f ~/img/scrot")),
])

2.4. Groups

Now we name our groups:

groups = [Group(i) for i in "123456789"]

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),
            ),
        ]
    )

2.5. Layouts

This is our list of enabled layouts. You can enable more of them if you want.

layouts = [
    layout.Columns(border_focus=colors[2], border_normal=colors[0], border_width=4, margin=7),
    layout.Max(),
    # Try more layouts by unleashing below layouts.
    # layout.Stack(num_stacks=2),
    # layout.Bsp(),
    # layout.Matrix(),
    layout.MonadTall(border_focus=colors[2], border_normal=colors[0], border_width=4, margin=7),
    # layout.MonadWide(),
    # layout.RatioTile(),
    # layout.Tile(),
    # layout.TreeTab(),
    # layout.VerticalTile(),
    # layout.Zoomy(),
]

2.6. Bar

Now we define our bar. I only have the need to see the time, current workspace, battery percentage, and MPD. Also, you may need to manually change your font size depending on your screen.

widget_defaults = dict(
    font="FiraCode Nerd Font",
    fontsize=16,
    padding=4,
    foreground=colors[17],
    background=colors[16],
)
extension_defaults = widget_defaults.copy()

# screens = [
#     Screen(
#         top=bar.Bar(
#             [
#                 # widget.CurrentLayout(),
#                 widget.GroupBox(active=colors[6], inactive=colors[15], this_current_screen_border=colors[4], highlight_colorsr=colors[3]),
#                 widget.Prompt(),
#                 widget.WindowName(),
#                 widget.Chord(
#                     chords_colors={
#                         "launch": ("#ff0000", "#ffffff"),
#                     },
#                     name_transform=lambda name: name.upper(),
#                 ),
#                 # widget.StatusNotifier(),

#                 widget.Systray(),
#                 widget.Battery(charge_char="🔋", discharge_char="🔋", full_char="🔋", format="{char} {percent:2.0%}"),
#                 # widget.TextBox("|", foreground=colors[1]),
#                 widget.Sep(padding=16, size_percent=80, foreground=colors[1]),
#                 widget.Clock(format="🕒 %a %I:%M %p"),
#                 widget.Sep(padding=16, size_percent=80, foreground=colors[1]),
#                 widget.Mpd2(),
#                 widget.TextBox("  "),

#             ],
#             24,

#             # border_width=[2, 0, 2, 0],  # Draw top and bottom borders
#             # border_colorsr=["ff00ff", "000000", "ff00ff", "000000"]  # Borders are magenta
#         ),
#         bottom=bar.Gap(4),
#         left=bar.Gap(3),
#         right=bar.Gap(3),
#     ),
# ]

def pline(rl, fg, bg):
    if rl == 0:
        uc = ""
    else:
        uc = ""
    return widget.TextBox(text = uc,
                          padding = 0,
                          fontsize = 22,
                          foreground=fg,
                          background=bg)

screens = [
    Screen(
        wallpaper="~/.config/qtile/wallpaper",
        wallpaper_mode="fill",
        top=bar.Bar(
            [
                widget.CurrentLayoutIcon(
                    scale=0.75,
                    background=colors[3]
                ),
                pline(0, colors[3], colors[6]),
                widget.GroupBox(
                    highlight_method="block",
                    background=colors[6],
                    this_current_screen_border="#7daea3"
                ),
                pline(0, colors[6], colors[7]),
                widget.TaskList(
                    highlight_method="block",
                    max_title_width=300,
                    border="#d3869b",
                    padding=2,
                    background=colors[7]
                ),
                pline(0, colors[7], colors[0]),
                widget.Spacer(),

                pline(1, colors[2], colors[0]),
                widget.Net( # requires python-psutil
                    interface="wlp0s20f3",
                    format="📡 {total}",
                    update_interval=30,
                    background=colors[2]
                ),
                pline(1, colors[5], colors[2]),
                widget.Backlight(
                    format="💡 {percent:2.0%}",
                    backlight_name="intel_backlight",
                    background=colors[5]
                ),
                pline(1, colors[3], colors[5]),
                widget.Volume(
                    emoji=True,
                    background=colors[3]
                ),
                widget.Volume(
                    background=colors[3]
                ),
                pline(1, colors[4], colors[3]),
                widget.BatteryIcon(
                    background=colors[4]
                ),
                widget.Battery(
                    charge_char="now ",
                    discharge_char="left",
                    format="{percent:2.0%} {char}",
                    background=colors[4]
                    ),
                pline(1, colors[1], colors[4]),
                widget.Clock(
                    format="%Y-%m-%d %a %I:%M %p",
                    background=colors[1]
                ),
            ],
            26,
        ),
    ),
]

2.7. Mouse

We configure the mouse to interact with floating windows.

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()),
]

Also, we need to toggle some options:

dgroups_app_rules = []  # type: list
follow_mouse_focus = True
bring_front_click = False
cursor_warp = False

And then we add the applications that need to start in floating:

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
    ]
)

2.8. I have no idea what these are

but they work for some reason.

auto_fullscreen = True
focus_on_window_activation = "smart"
reconfigure_screens = True

2.9. Autostart

If we used wayland, then we must autostart here:

@hook.subscribe.startup_once
def autostart():
   home = os.path.expanduser("~")
   subprocess.call([home + '/.config/qtile/autostart.sh'])

2.10. Input Rules

in wayland, setxkbmap is not possible. Therefore:

wl_input_rules = {
    "1267:12377:ELAN1300:00 04F3:3059 Touchpad": InputConfig(left_handed=True),
    "*": InputConfig(left_handed=True, pointer_accel=True),
    "type:keyboard": InputConfig(kb_options="caps:swapescape,compose:ralt"),
}
Copyright © 2024 Preston Pan