Dotfiles/.config/qtile/config.org
2024-09-22 13:03:17 +02:00

591 lines
23 KiB
Org Mode
Executable file

#+TITLE: Qtile Config
#+PROPERTY: header-args :tangle config.py
* What is this?
This is my configuration for the Qtile Window Manager written and configured in Python.
* Table of contents
* Basic stuff
In the following points, stuff is defined, which is needed to be done for a properly working config, without directly influencing the UX in any way.
** Imports
Imports are needed for the Window Manager to work properly. They grab the needed libraries so we can do cool stuff inside the window manager.
#+BEGIN_SRC python
##################################################################################################################################################
### IF YOU ARE READING CONFIG.PY, SWITCH TO CONFIG.ORG IN EMACS!! THIS VERSION IS TANGELD FROM A LITERATE CONFIG AND HAS NEXT TO NO COMMENTS!! ###
##################################################################################################################################################
import os
import re
import subprocess
from libqtile.config import KeyChord, Key, Screen, Group, Drag, Click
from libqtile.lazy import lazy
from libqtile import layout, bar, widget, hook
from libqtile.lazy import lazy
from typing import List # noqa: F401
#+END_SRC
#+RESULTS:
: None
** Variables
Variables in a Programming language can contain a string or a number. While configuring a Window Manager, this is useful, if you want to repeat the same string a lot of times.
If, for example, I used my terminal a lot of times in the config and I were to change my default terminal, I would have to replace every occurence of the Terminal name in the config. In this case, where I only have to change that string once, that is when defining the string.
#+BEGIN_SRC python
mod = "mod4" # Sets mod key to SUPER/WINDOWS
myTerm = "alacritty" # My terminal of choice
#+END_SRC
* Keybindings
The Keybindings are the core point in using a tiling Window Manager. In Qtile, you can have both normal hotkey-like keybindings, as well as 'Keychords'.
** Normal Keybindings
These are normal Keybindings, accessed by pressing the mod key (together with shift, in some cases) plus the corresponding key to each binding.
#+BEGIN_SRC python
keys = [
#+END_SRC
*** Essentials and WM Controls
In this area of the config, basic programs (like Terminal and Run launcher) as well as Window Manger controls are located. I follow a simple rule there:
- ~Mod + Shift + [Key]~ for Window Manager controls like
- ~Mod + [Key]~ for a normal program
#+BEGIN_SRC python
Key([mod], "Return",
lazy.spawn(myTerm),
desc='Launches My Terminal'
),
Key([mod], "r",
lazy.spawn("rofi -show drun"),
desc='Rofi Run Launcher'
),
Key([mod], "Tab",
lazy.next_layout(),
desc='Toggle through layouts'
),
Key([mod], "x",
lazy.window.kill(),
desc='Kill active window'
),
Key([mod, "shift"], "r",
lazy.restart(),
desc='Restart Qtile'
),
Key([mod, "shift"], "q",
lazy.shutdown(),
desc='Shutdown Qtile'
),
### Window controls
Key([mod], "k",
lazy.layout.down(),
desc='Move focus down in current stack pane'
),
Key([mod], "j",
lazy.layout.up(),
desc='Move focus up in current stack pane'
),
Key([mod, "shift"], "k",
lazy.layout.shuffle_down(),
desc='Move windows down in current stack'
),
Key([mod, "shift"], "j",
lazy.layout.shuffle_up(),
desc='Move windows up in current stack'
),
Key([mod], "h",
lazy.layout.grow(),
lazy.layout.increase_nmaster(),
desc='Expand window (MonadTall), increase number in master pane (Tile)'
),
Key([mod], "l",
lazy.layout.shrink(),
lazy.layout.decrease_nmaster(),
desc='Shrink window (MonadTall), decrease number in master pane (Tile)'
),
Key([mod], "y",
lazy.layout.normalize(),
desc='normalize window size ratios'
),
Key([mod, "shift"], "f",
lazy.window.toggle_floating(),
desc='toggle floating'
),
Key([mod, "shift"], "w",
lazy.to_screen(0),
desc='Move focus to monitor one'
),
Key([mod, "shift"], "e",
lazy.to_screen(1),
desc='Move focus to monitor two'
),
#+END_SRC
*** Other Programs
Here are the most programms that I used listed. They are all launched by using Mod + [Key]
#+BEGIN_SRC python
Key([mod], "f",
lazy.spawn("firefox"),
desc='Firefox'
),
Key([mod], "n",
lazy.spawn("/home/emma/AppImages/Vesktop.AppImage"),
desc='Vesktop'
),
Key([mod, "shift"], "n",
lazy.spawn("discord"),
desc='Standard Discord Client'
),
Key([mod], "w",
lazy.spawn("lowriter"),
desc='Libre Office Writer'
),
Key([mod], "p",
lazy.spawn("pcmanfm"),
desc='PCmanFM'
),
Key([mod], "v",
lazy.spawn("vlc"),
desc='VLC Media Player'
),
Key([mod], "d",
lazy.spawn("deadbeef"),
desc='The DeaDBeeF Music Player'
),
Key([mod], "m",
lazy.spawn("mailspring --password-store='gnome-libsecret'"),
desc='My Email Client of Choice'
),
#+END_SRC
** KeyChords
KeyChords are a special kind of keybindings, which require a keycombination (like Mod + E) pressed, which is then followed up by another key. This is common behaviour in Emacs and useful if you want a program to be launched with different arguments.
#+BEGIN_SRC python
# Gscreenshot
KeyChord([mod], "g", [
Key([], "s", lazy.spawn("flameshot gui")),
Key([], "g", lazy.spawn("flameshot full -c -p /home/emma/Bilder/flameshots"))
]),
# Emacs
KeyChord([mod], "e", [
Key([], "e", lazy.spawn("emacsclient -c -a 'emacs'")),
Key([], "p", lazy.spawn("emacsclient -c -a 'emacs' ~/Projektordner/Hauptprojekt.org")),
Key([], "q", lazy.spawn("emacsclient -c -a 'emacs' ~/.config/qtile/config.org"))
]),
# Switching Keyboard Layouts
KeyChord([mod], "space", [
Key([], "e", lazy.spawn("setxkbmap -layout us -variant altgr-intl")),
Key([], "p", lazy.spawn("setxkbmap -layout pl")),
Key([], "g", lazy.spawn("setxkbmap -layout gr")),
]),
# Steam
KeyChord([mod], "z", [
### Steam Runtime
Key([], "z", lazy.spawn("steam")),
### Hollow Knight
Key([], "h", lazy.spawn("steam steam://rungameid/367520")),
#### Code Vein
Key([], "c", lazy.spawn("steam steam://rungameid/678960")),
### Red Dead Redemption
Key([], "r", lazy.spawn("steam steam://rungameid/1174180")),
### Souls Series
KeyChord([], "d", [
Key([], "1", lazy.spawn("steam steam://rungameid/570940")),
Key([], "3", lazy.spawn("steam steam://rungameid/374320"))
]),
### SAO Series
KeyChord([], "s", [
Key([], "1", lazy.spawn("steam steam://rungameid/607890")),
Key([], "2", lazy.spawn("steam steam://rungameid/626690"))
]),
### Outlast Series
KeyChord([], "o", [
Key([], "1", lazy.spawn("steam steam://rungameid/238320")),
Key([], "2", lazy.spawn("steam steam://rungameid/414700"))
]),
### Tomb Raider Series
KeyChord([], "t", [
Key([], "1", lazy.spawn("steam steam://rungameid/203160")),
Key([], "2", lazy.spawn("steam steam://rungameid/391220")),
Key([], "3", lazy.spawn("steam steam://rungameid/750920"))
])
])
]
#+END_SRC
#+RESULTS:
* Layouts and Workspaces
Layouts, or 'groups' in Qtile are basically a set of rules how a Windows behave when on a workspace with a certain layout. They are on of the most important features when using a tiling WM, so this is a pretty big Deal
** Workspaces and their default layouts
When the WM is started, each Workspace gets a default layout, which is the one they get started with.
In this Code Block, the Names of the Workspaces are defined and their default layout gets assigned.
There is a rule in my head, based on which the default layout gets assigned:
- Normal Windows: Pretty much every window (except the ones discussed later) fall under this category. Workspaces on which I intend to only open normal windows get assigned with the 'Monadtall' layout. It's the default layout in most tiling WMs and tends to be the most useful one, as well.
- Floating Windows: Some Windows should behave in a more "traditional" way, where they aren't automatically sized, but float over each other. This includes the Steam runtime. which tends to open up quite a few other windows, as well as programs like GIMP which behave similar.
#+BEGIN_SRC python
group_names = [("Term", {'layout': 'monadtall'}),
("Web", {'layout': 'monadtall'}),
("Chat", {'layout': 'monadtall'}),
("Dev", {'layout': 'monadtall'}),
("Game", {'layout': 'floating'}),
("Text", {'layout': 'monadtall'}),
("Music", {'layout': 'monadtall'}),
("Misc", {'layout': 'floating'}),
("Qbit", {'layout': 'monadtall'})]
groups = [Group(name, **kwargs) for name, kwargs in group_names]
#+END_SRC
** Numbering Workspaces
In Qtile's dafault behaviour, each workspace has the name of a letter, and by pressing mod + [workspace letter] the Workspace can be accessed. This does not work anymore, if we want to give our workspace more meaningful names (as defined above), because the string-comparisson does not work anymore. Also, if we want our letters to open programs, this is not very practical, since many of the letters were already used by basic Window Manager controls.
If you want your workspaces to have meaningful names, as well as being accessed by using a number (Which is defalt behaviour in many other tiling Window Managers), a little work is needed.
#+BEGIN_SRC python
for i, (name, kwargs) in enumerate(group_names, 1):
keys.append(Key([mod], str(i), lazy.group[name].toscreen())) # Switch to another group
keys.append(Key([mod, "shift"], str(i), lazy.window.togroup(name))) # Send current window to another group
#+END_SRC
** Layout theming
The Layout theme is an additional set of rules of a layout, like defining Window Margin or the border color. Since we want to have a unison look in all our layout, it makes sense to define the theme in a list (like a variable with multiple entries), which is then just called, when the workspaces are being set up on startup.
#+BEGIN_SRC python
layout_theme = {"border_width": 2,
"margin": 6,
"border_focus": "e1acff",
"border_normal": "1D2330"
}
#+END_SRC
** Layouts
In the following list, the layouts to be used in the session are defined. The layouts which are commented out will not be able to be used, while the others will.
#+BEGIN_SRC python
layouts = [
# layout.MonadWide(**layout_theme),
# layout.Bsp(**layout_theme),
# layout.Stack(stacks=2, **layout_theme),
# layout.Columns(**layout_theme),
# layout.Tile(shift_windows=True, **layout_theme),
# layout.RatioTile(**layout_theme),
# layout.VerticalTile(**layout_theme),
# layout.Zoomy(**layout_theme),
# layout.Matrix(**layout_theme),
# layout.Max(**layout_theme),
# layout.Stack(**layout_theme, num_stacks=2),
layout.MonadTall(**layout_theme),
layout.Floating(**layout_theme)
]
#+END_SRC
* The Bar
The Bar is displayed on the top and displays useful information or just looking nice. In the following area, everything concerning the bar is configured.
Note: This is NOT a traditional Taskbar, but a rather minimalist one, which displays basic information, in a way which is very riceable.
** Colors
If you want your colors to be consistant and your config to be readable, just defining a list with every color meant to be used is the best way:
#+BEGIN_SRC python
colors = [["#282c34", "#282c34"], # panel background
["#434758", "#434758"], # background for current screen tab
["#ffffff", "#ffffff"], # font color for group names
["#ff5555", "#ff5555"], # border line color for current tab
["#74438f", "#74438f"], # border line color for other tab and odd widgets
["#4f76c7", "#4f76c7"], # color for the even widgets
["#e1acff", "#e1acff"]] # window name
# Sets the format for Qtile's run prompt. The config doesn't work if this isn't set but I didn't want to make a new Source Block and a literrate explanation for this, since it's only a thing that makes stuff work
prompt = "{0}@{1}: ".format(os.environ["USER"], os.uname()[1])
#+END_SRC
** Widgets
Widgets are what actually gets displayed in the bar. Loke with every design thing, having a list with the default settings is a good practice.
#+BEGIN_SRC python
widget_defaults = dict(
font="Mononoki Nerd Font",
fontsize = 12,
padding = 2,
background=colors[2]
)
extension_defaults = widget_defaults.copy()
#+END_SRC
** Initializing the bar
A function is created here, which will later be called at the startup. This basically just is an amalgamation of different Widgets spammed after one another.
#+BEGIN_SRC python
def init_widgets_list():
widgets_list = [
widget.Sep(
linewidth = 0,
padding = 6,
foreground = colors[2],
background = colors[0]
),
widget.GroupBox(
font = "Mononoki Nerd Font",
fontsize = 9,
margin_y = 3,
margin_x = 0,
padding_y = 5,
padding_x = 3,
borderwidth = 3,
active = colors[2],
inactive = colors[2],
rounded = False,
highlight_color = colors[1],
highlight_method = "line",
this_current_screen_border = colors[3],
this_screen_border = colors [4],
other_current_screen_border = colors[0],
other_screen_border = colors[0],
foreground = colors[2],
background = colors[0]
),
widget.Prompt(
prompt = prompt,
font = "Mononoki Nerd Font",
padding = 10,
foreground = colors[3],
background = colors[1]
),
widget.Sep(
linewidth = 0,
padding = 40,
foreground = colors[2],
background = colors[0]
),
widget.WindowName(
foreground = colors[6],
background = colors[0],
padding = 0
),
widget.TextBox(
text='',
background = colors[0],
foreground = colors[4],
padding = -5,
fontsize = 37
),
widget.CPU(
foreground = colors[2],
background = colors[4],
padding = 3
),
widget.TextBox(
text = '',
background = colors[4],
foreground = colors[5],
padding = -5,
fontsize = 37
),
widget.Memory(
foreground = colors[2],
background = colors[5],
padding = 5
),
widget.TextBox(
text = '',
background = colors[5],
foreground = colors[4],
padding = -5,
fontsize = 37
),
widget.Net(
foreground = colors[2],
background = colors[4],
padding = 5
),
widget.TextBox(
text='',
background = colors[4],
foreground = colors[5],
padding = -5,
fontsize = 37
),
widget.TextBox(
text = "",
padding = 2,
foreground = colors[2],
background = colors[5],
fontsize = 14
),
widget.CheckUpdates(
update_interval = 1800,
distro = "Gentoo_eix",
display_format = "{updates} Updates",
foreground = colors[2],
execute = 'alacritty -e doas emerge --sync && doas emerge -auDN @world && doas emerge -acv',
background = colors[5]
),
widget.TextBox(
text = '',
background = colors[5],
foreground = colors[4],
padding = -5,
fontsize = 37
),
widget.CurrentLayout(
foreground = colors[2],
background = colors[4],
padding = 5
),
widget.TextBox(
text = '',
background = colors[4],
foreground = colors[5],
padding = -5,
fontsize = 37
),
widget.Clock(
foreground = colors[2],
background = colors[5],
execute = 'gnome-calendar',
format = "%A, %B %d [ %H:%M:%S ]"
),
widget.Sep(
linewidth = 0,
padding = 10,
foreground = colors[0],
background = colors[5]
),
widget.TextBox(
text = '',
background = colors[5],
foreground = colors[4],
padding = -5,
fontsize = 37
),
widget.Systray(
background = colors[4],
padding = 2
),
widget.Sep(
linewidth = 0,
padding = 5,
foreground = colors[2],
background = colors[4]
),
]
return widgets_list
#+END_SRC
* Setting everything up
Here are a couple of functions defined, which will be called once the Window manager starts
** Initializing the screen
Everything we got configured earkier will now be out on the screens, when the Window Manager starts.
#+BEGIN_SRC python
def init_widgets_screen1():
widgets_screen1 = init_widgets_list()
return widgets_screen1
def init_widgets_screen2():
widgets_screen2 = init_widgets_list()
widgets_screen2 = widgets_screen2[:-3] + widgets_screen2[-1:]
return widgets_screen2
def init_screens():
return [Screen(top=bar.Bar(widgets=init_widgets_screen1(), opacity=1.0, size=20)),
Screen(top=bar.Bar(widgets=init_widgets_screen2(), opacity=1.0, size=20))]
if __name__ in ["config", "__main__"]:
screens = init_screens()
#+END_SRC
** Making Windows movable
If we want to move windows between groups, we need to have it in our config.
#+BEGIN_SRC python
def window_to_prev_group(qtile):
if qtile.currentWindow is not None:
i = qtile.groups.index(qtile.currentGroup)
qtile.currentWindow.togroup(qtile.groups[i - 1].name)
def window_to_next_group(qtile):
if qtile.currentWindow is not None:
i = qtile.groups.index(qtile.currentGroup)
qtile.currentWindow.togroup(qtile.groups[i + 1].name)
#+END_SRC
** The Mouse
While a Tiling Window Manager is mostly navigated by using the Keyboard, having mouse functionality for Window controls can be very useful, especially in floating mode.
To get our mouse to do Window Controlling Stuff, we have need to define a few rules-
#+BEGIN_SRC python
# A list, which tells the mouse what to do when which button is pressed together with the mod key.
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())
]
dgroups_key_binder = None
dgroups_app_rules = [] # type: List
main = None
follow_mouse_focus = False
bring_front_click = True
cursor_warp = False
#+END_SRC
** Automatic floating
Some windows (like Splashscreens) are not meant to be displayed in a fullscreen way. By default, qtile does it anyway. By giving it these rules, it will let these windows float, no matter which layout the workspace has, the window is currently on.
#+BEGIN_SRC python
floating_layout = layout.Floating(float_rules=[
*layout.Floating.default_float_rules
])
#+END_SRC
** Additional Window rules
If you want the window manager to do stuff (which doesn't directly has anything to do with one of the earlier things in this config), it's time to define these rules:
#+Begin_SRC python
auto_fullscreen = True
focus_on_window_activation = "smart"
auto_minimize = False
#+END_SRC
** Autostart
Programs like Daemons or the ones that set wallpapers should automatically be started when the window manager starts. The easiest way is to write a bash script (with execute permissions) that just does all the things for us. This will be called at startup and we're gut to go.
#+BEGIN_SRC python
@hook.subscribe.startup_once
def start_once():
home = os.path.expanduser('~')
subprocess.Popen([home + '/.config/qtile/autostart.sh'])
#+END_SRC
** The Window Manager
The config gets finished by defining thesystem internal name of the Window Manager. In my case, just using 'qtile' is fine for me, but if you are running into trubble with some Java-GUIs, you might want to consider changing it to 'LG3D'.
#+BEGIN_SRC python
wmname = "qtile"
#+END_SRC