#+TITLE: Qtile Config #+AUTHOR: Derek Taylor #+PROPERTY: header-args :tangle config.py #+auto_tangle: t #+STARTUP: showeverything * Table of Contents :toc: - [[#about-this-config][About This Config]] - [[#imports][Imports]] - [[#variables][Variables]] - [[#custom-functions][Custom Functions]] - [[#keybindings][Keybindings]] - [[#groups][Groups]] - [[#colorscheme][Colorscheme]] - [[#default-settings-for-all-layouts][Default Settings For All Layouts]] - [[#layouts][Layouts]] - [[#default-settings-for-all-widgets][Default Settings For All Widgets]] - [[#widgets][Widgets]] - [[#screens][Screens]] - [[#some-important-functions][Some Important Functions]] - [[#drag-floating-layouts][Drag Floating Layouts]] - [[#window-rules][Window Rules]] - [[#autostart-programs][Autostart Programs]] - [[#java-apps-might-need-this][Java Apps Might Need This]] * About This Config #+CAPTION: Qtile Scrot #+ATTR_HTML: :alt Qtile Scrot :title Qtile Scrot :align left [[https://gitlab.com/dwt1/dotfiles/-/raw/master/.screenshots/dotfiles07-thumb.png]] Modified by Derek Taylor - My YouTube: [[http://www.youtube.com/c/DistroTube][http://www.youtube.com/c/DistroTube]] - My GitLab: [[http://www.gitlab.com/dwt1/][http://www.gitlab.com/dwt1/]] #+begin_src python # 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. #+end_src * Imports These are python modules that must be imported for this config. #+BEGIN_SRC python import os import subprocess from libqtile import bar, extension, hook, layout, qtile, widget from libqtile.config import Click, Drag, Group, Key, KeyChord, Match, Screen from libqtile.lazy import lazy import colors #+END_SRC * Variables Just some variables I am setting to make my life easier. #+BEGIN_SRC python mod = "mod4" # Sets mod key to SUPER/WINDOWS myTerm = "alacritty" # My terminal of choice myBrowser = "brave" # My browser of choice myEmacs = "emacsclient -c -a 'emacs' " # The space at the end is IMPORTANT! #+END_SRC * Custom Functions #+begin_src python # Allows you to input a name when adding treetab section. @lazy.layout.function def add_treetab_section(layout): prompt = qtile.widgets_map["prompt"] prompt.start_input("Section name: ", layout.cmd_add_section) # A function for hide/show all the windows in a group @lazy.function def minimize_all(qtile): for win in qtile.current_group.windows: if hasattr(win, "toggle_minimize"): win.toggle_minimize() # A function for toggling between MAX and MONADTALL layouts @lazy.function def maximize_by_switching_layout(qtile): current_layout_name = qtile.current_group.layout.name if current_layout_name == 'monadtall': qtile.current_group.layout = 'max' elif current_layout_name == 'max': qtile.current_group.layout = 'monadtall' #+end_src * Keybindings A list of available commands that can be bound to keys can be found at: https://docs.qtile.org/en/latest/manual/config/lazy.html | IMPORTANT KEYBINDINGS | ASSOCIATED ACTION | |-------------------------+----------------------------------------------------------------| | MODKEY + RETURN | opens terminal (alacritty) | | MODKEY + SHIFT + RETURN | opens run launcher (rofi) | | MODKEY + TAB | rotates through the available layouts | | MODKEY + SHIFT + c | closes window with focus | | MODKEY + SHIFT + r | restarts qtile | | MODKEY + SHIFT + q | quits qtile | | MODKEY + 1-9 | switch focus to workspace (1-9) | | MODKEY + SHIFT + 1-9 | send focused window to workspace (1-9) | | MODKEY + j | lazy layout down (switches focus between windows in the stack) | | MODKEY + k | lazy layout up (switches focus between windows in the stack) | | MODKEY + SHIFT + j | lazy layout shuffle_down (rotates the windows in the stack) | | MODKEY + SHIFT + k | lazy layout shuffle_up (rotates the windows in the stack) | | MODKEY + equals | expand size of window (MondadTall layout) | | MODKEY + minus | shrink size of window (MondadTall layout) | | MODKEY + period | switch focus to next monitor | | MODKEY + comma | switch focus to prev monitor | #+begin_src python keys = [ # The essentials Key([mod], "Return", lazy.spawn(myTerm), desc="Terminal"), Key([mod, "shift"], "Return", lazy.spawn("rofi -show drun"), desc='Run Launcher'), Key([mod], "w", lazy.spawn(myBrowser), desc='Web browser'), Key([mod], "b", lazy.hide_show_bar(position='all'), desc="Toggles the bar to show/hide"), Key([mod], "Tab", lazy.next_layout(), desc="Toggle between layouts"), Key([mod, "shift"], "c", lazy.window.kill(), desc="Kill focused window"), Key([mod, "shift"], "r", lazy.reload_config(), desc="Reload the config"), Key([mod, "shift"], "q", lazy.spawn("dm-logout -r"), desc="Logout menu"), Key([mod], "r", lazy.spawncmd(), desc="Spawn a command using a prompt widget"), # Switch between windows # Some layouts like 'monadtall' only need to use j/k to move # through the stack, but other layouts like 'columns' will # require all four directions h/j/k/l to move around. 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"), # Move windows between left/right columns or move up/down in current stack. # Moving out of range in Columns layout will create new column. Key([mod, "shift"], "h", lazy.layout.shuffle_left(), lazy.layout.move_left().when(layout=["treetab"]), desc="Move window to the left/move tab left in treetab"), Key([mod, "shift"], "l", lazy.layout.shuffle_right(), lazy.layout.move_right().when(layout=["treetab"]), desc="Move window to the right/move tab right in treetab"), Key([mod, "shift"], "j", lazy.layout.shuffle_down(), lazy.layout.section_down().when(layout=["treetab"]), desc="Move window down/move down a section in treetab" ), Key([mod, "shift"], "k", lazy.layout.shuffle_up(), lazy.layout.section_up().when(layout=["treetab"]), desc="Move window downup/move up a section in treetab" ), # Toggle between split and unsplit sides of stack. # Split = all windows displayed # Unsplit = 1 window displayed, like Max layout, but still with # multiple stack panes Key([mod, "shift"], "space", lazy.layout.toggle_split(), desc="Toggle between split and unsplit sides of stack"), # Treetab prompt Key([mod, "shift"], "a", add_treetab_section, desc='Prompt to add new section in treetab'), # Grow/shrink windows left/right. # This is mainly for the 'monadtall' and 'monadwide' layouts # although it does also work in the 'bsp' and 'columns' layouts. Key([mod], "equal", lazy.layout.grow_left().when(layout=["bsp", "columns"]), lazy.layout.grow().when(layout=["monadtall", "monadwide"]), desc="Grow window to the left" ), Key([mod], "minus", lazy.layout.grow_right().when(layout=["bsp", "columns"]), lazy.layout.shrink().when(layout=["monadtall", "monadwide"]), desc="Grow window to the left" ), # Grow windows up, down, left, right. Only works in certain layouts. # Works in 'bsp' and 'columns' layout. 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], "m", lazy.layout.maximize(), desc='Toggle between min and max sizes'), Key([mod], "t", lazy.window.toggle_floating(), desc='toggle floating'), Key([mod], "f", maximize_by_switching_layout(), lazy.window.toggle_fullscreen(), desc='toggle fullscreen'), Key([mod, "shift"], "m", minimize_all(), desc="Toggle hide/show all windows on current group"), # Switch focus of monitors Key([mod], "period", lazy.next_screen(), desc='Move focus to next monitor'), Key([mod], "comma", lazy.prev_screen(), desc='Move focus to prev monitor'), # Emacs programs launched using the key chord SUPER+e followed by 'key' KeyChord([mod],"e", [ Key([], "e", lazy.spawn(myEmacs), desc='Emacs Dashboard'), Key([], "a", lazy.spawn(myEmacs + "--eval '(emms-play-directory-tree \"~/Music/\")'"), desc='Emacs EMMS'), Key([], "b", lazy.spawn(myEmacs + "--eval '(ibuffer)'"), desc='Emacs Ibuffer'), Key([], "d", lazy.spawn(myEmacs + "--eval '(dired nil)'"), desc='Emacs Dired'), Key([], "i", lazy.spawn(myEmacs + "--eval '(erc)'"), desc='Emacs ERC'), Key([], "s", lazy.spawn(myEmacs + "--eval '(eshell)'"), desc='Emacs Eshell'), Key([], "v", lazy.spawn(myEmacs + "--eval '(vterm)'"), desc='Emacs Vterm'), Key([], "w", lazy.spawn(myEmacs + "--eval '(eww \"distro.tube\")'"), desc='Emacs EWW'), Key([], "F4", lazy.spawn("killall emacs"), lazy.spawn("/usr/bin/emacs --daemon"), desc='Kill/restart the Emacs daemon') ]), # Dmenu/rofi scripts launched using the key chord SUPER+p followed by 'key' KeyChord([mod], "p", [ Key([], "h", lazy.spawn("dm-hub -r"), desc='List all dmscripts'), Key([], "a", lazy.spawn("dm-sounds -r"), desc='Choose ambient sound'), Key([], "b", lazy.spawn("dm-setbg -r"), desc='Set background'), Key([], "c", lazy.spawn("dtos-colorscheme -r"), desc='Choose color scheme'), Key([], "e", lazy.spawn("dm-confedit -r"), desc='Choose a config file to edit'), Key([], "i", lazy.spawn("dm-maim -r"), desc='Take a screenshot'), Key([], "k", lazy.spawn("dm-kill -r"), desc='Kill processes '), Key([], "m", lazy.spawn("dm-man -r"), desc='View manpages'), Key([], "n", lazy.spawn("dm-note -r"), desc='Store and copy notes'), Key([], "o", lazy.spawn("dm-bookman -r"), desc='Browser bookmarks'), Key([], "p", lazy.spawn("rofi-pass"), desc='Logout menu'), Key([], "q", lazy.spawn("dm-logout -r"), desc='Logout menu'), Key([], "r", lazy.spawn("dm-radio -r"), desc='Listen to online radio'), Key([], "s", lazy.spawn("dm-websearch -r"), desc='Search various engines'), Key([], "t", lazy.spawn("dm-translate -r"), desc='Translate text') ]) ] #+end_src * Groups Groups are really workspaces. group_names should remain 1-9 so the MOD+1-9 keybindings work as expected. group_labels are the labels of the groups that are displayed in the bar. Feel free to change group_labels to anything you wish. group_layouts sets the default layout for each group. #+begin_src python groups = [] group_names = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] #group_labels = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] #group_labels = ["DEV", "WWW", "SYS", "DOC", "VBOX", "CHAT", "MUS", "VID", "GFX", "MISC"] group_labels = ["", "", "", "", "", "", "⧳", "", "", "⛨"] #group_labels = [" ", " ", " ", " ", " ", " ", "⛨ ", " ", " "] group_layouts = ["monadtall", "monadtall", "monadtall", "monadtall", "monadtall", "monadtall", "monadtall", "monadtall", "monadtall", "monadtall"] for i in range(len(group_names)): groups.append( Group( name=group_names[i], layout=group_layouts[i].lower(), label=group_labels[i], )) 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 = move focused window to group Key( [mod, "shift"], i.name, lazy.window.togroup(i.name, switch_group=False), desc="Move focused window to group {}".format(i.name), ), ] ) #+end_src * Colorscheme Colors are defined in a separate 'colors.py' file. It is best not manually change the colorscheme; instead run 'dtos-colorscheme' which is set to 'MOD + p c'. There are 10 colorschemes available to choose from: + colors.DoomOne + colors.Dracula + colors.GruvboxDark + colors.MonokaiPro + colors.Nord + colors.OceanicNext + colors.Palenight + colors.SolarizedDark + colors.SolarizedLight + colors.TomorrowNight #+begin_src python colors = colors.DoomOne #+end_src * Default Settings For All Layouts Some settings that I use on almost every layout, which saves me from having to type these out for each individual layout. #+begin_src python layout_theme = {"border_width": 2, "margin": 12, "border_focus": colors[8], "border_normal": colors[0] } #+end_src * Layouts The layouts that I use, plus several that I don't use. Uncomment the layouts you want; comment out the ones that you don't want to use. #+begin_src python layouts = [ layout.MonadTall(**layout_theme), layout.MonadWide(**layout_theme), layout.Tile(**layout_theme), layout.Max(**layout_theme), #layout.Bsp(**layout_theme), #layout.Floating(**layout_theme) #layout.RatioTile(**layout_theme), #layout.VerticalTile(**layout_theme), #layout.Matrix(**layout_theme), #layout.Stack(**layout_theme, num_stacks=2), #layout.Columns(**layout_theme), #layout.TreeTab( # font = "Ubuntu Bold", # fontsize = 11, # border_width = 0, # bg_color = colors[0], # active_bg = colors[8], # active_fg = colors[2], # inactive_bg = colors[1], # inactive_fg = colors[0], # padding_left = 8, # padding_x = 8, # padding_y = 6, # sections = ["ONE", "TWO", "THREE"], # section_fontsize = 10, # section_fg = colors[7], # section_top = 15, # section_bottom = 15, # level_shift = 8, # vspace = 3, # panel_width = 240 # ), #layout.Zoomy(**layout_theme), ] #+end_src * Default Settings For All Widgets Some settings that I use on almost every widget, which saves me from having to type these out for each individual widget. #+begin_src python widget_defaults = dict( font="Ubuntu Bold", fontsize = 12, padding = 0, background=colors[0] ) extension_defaults = widget_defaults.copy() #+end_src * Widgets This is the bar (the panel) and the widgets within the bar. #+begin_src python def init_widgets_list(): widgets_list = [ widget.Spacer(length = 8), widget.Image( filename = "~/.config/qtile/icons/dt-icon.png", scale = "False", mouse_callbacks = {'Button1': lambda: qtile.cmd_spawn(myTerm)}, ), widget.Prompt( font = "Ubuntu Mono", fontsize=14, foreground = colors[1] ), widget.GroupBox( fontsize = 10, margin_y = 5, margin_x = 12, padding_y = 0, padding_x = 0, borderwidth = 3, active = colors[8], inactive = colors[9], rounded = False, highlight_color = colors[2], highlight_method = "line", this_current_screen_border = colors[7], this_screen_border = colors [4], other_current_screen_border = colors[7], other_screen_border = colors[4], ), widget.TextBox( text = '|', font = "Ubuntu Mono", foreground = colors[9], padding = 2, fontsize = 14 ), widget.LaunchBar( progs = [("🦁", "brave", "Brave web browser"), ("🚀", "alacritty", "Alacritty terminal"), ("📁", "pcmanfm", "PCManFM file manager"), ("🎸", "vlc", "VLC media player") ], fontsize = 10, padding = 10, foreground = colors[3], ), widget.TextBox( text = '|', font = "Ubuntu Mono", foreground = colors[9], padding = 2, fontsize = 14 ), widget.CurrentLayout( foreground = colors[1], padding = 5 ), widget.TextBox( text = '|', font = "Ubuntu Mono", foreground = colors[9], padding = 2, fontsize = 14 ), widget.WindowName( foreground = colors[6], padding = 4, max_chars = 40 ), widget.GenPollText( update_interval = 300, func = lambda: subprocess.check_output("printf $(uname -r)", shell=True, text=True), foreground = colors[3], padding = 6, fmt = '❤ {}', ), widget.CPU( format = ' Cpu: {load_percent}%', foreground = colors[4], padding = 6, ), widget.Memory( foreground = colors[8], padding = 6, mouse_callbacks = {'Button1': lambda: qtile.cmd_spawn(myTerm + ' -e htop')}, format = '{MemUsed: .0f}{mm}', fmt = '🖥 Mem: {}', ), widget.DF( update_interval = 60, foreground = colors[5], padding = 6, mouse_callbacks = {'Button1': lambda: qtile.cmd_spawn(myTerm + ' -e df')}, partition = '/', #format = '[{p}] {uf}{m} ({r:.0f}%)', format = '{uf}{m} free', fmt = '🖴 Disk: {}', visible_on_warn = False, ), widget.Volume( foreground = colors[7], padding = 6, fmt = '🕫 Vol: {}', ), widget.Clock( foreground = colors[8], padding = 6, format = "⧗ %a, %b %d - %H:%M", ), widget.Systray(padding = 3), widget.Spacer(length = 8), ] return widgets_list #+end_src * Screens Screen settings for my triple monitor setup. Monitor 1 will display ALL widgets in widgets_list. It is important that this is the *only* monitor that displays all widgets because the systray widget will crash if you try to run multiple instances of it. #+begin_src python def init_widgets_screen1(): widgets_screen1 = init_widgets_list() return widgets_screen1 # All other monitors' bars will display everything but widgets 22 (systray) and 23 (spacer). def init_widgets_screen2(): widgets_screen2 = init_widgets_list() del widgets_screen2[16:17] return widgets_screen2 # For adding transparency to your bar, add (background="#00000000") to the "Screen" line(s) # For ex: Screen(top=bar.Bar(widgets=init_widgets_screen2(), background="#00000000", size=24)), def init_screens(): return [Screen(top=bar.Bar(widgets=init_widgets_screen1(), margin=[8, 12, 0, 12], size=28)), Screen(top=bar.Bar(widgets=init_widgets_screen2(), margin=[8, 12, 0, 12], size=28)), Screen(top=bar.Bar(widgets=init_widgets_screen2(), margin=[8, 12, 0, 12], size=28))] if __name__ in ["config", "__main__"]: screens = init_screens() widgets_list = init_widgets_list() widgets_screen1 = init_widgets_screen1() widgets_screen2 = init_widgets_screen2() #+end_src * Some Important Functions #+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) def window_to_previous_screen(qtile): i = qtile.screens.index(qtile.current_screen) if i != 0: group = qtile.screens[i - 1].group.name qtile.current_window.togroup(group) def window_to_next_screen(qtile): i = qtile.screens.index(qtile.current_screen) if i + 1 != len(qtile.screens): group = qtile.screens[i + 1].group.name qtile.current_window.togroup(group) def switch_screens(qtile): i = qtile.screens.index(qtile.current_screen) group = qtile.screens[i - 1].group qtile.current_screen.set_group(group) #+end_src * Drag Floating Layouts Sets MOD + left mouse to drag floating windows. Sets MOD + right mouse to resize floating windows. #+begin_src python 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()), ] #+end_src * Window Rules #+begin_src python dgroups_key_binder = None dgroups_app_rules = [] # type: list follow_mouse_focus = True bring_front_click = False cursor_warp = False floating_layout = layout.Floating( border_focus=colors[8], border_width=2, 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="dialog"), # dialog boxes Match(wm_class="download"), # downloads Match(wm_class="error"), # error msgs Match(wm_class="file_progress"), # file progress boxes Match(wm_class='kdenlive'), # kdenlive Match(wm_class="makebranch"), # gitk Match(wm_class="maketag"), # gitk Match(wm_class="notification"), # notifications Match(wm_class='pinentry-gtk-2'), # GPG key password entry Match(wm_class="ssh-askpass"), # ssh-askpass Match(wm_class="toolbar"), # toolbars Match(wm_class="Yad"), # yad boxes Match(title="branchdialog"), # gitk Match(title='Confirmation'), # tastyworks exit box Match(title='Qalculate!'), # qalculate-gtk Match(title="pinentry"), # GPG key password entry Match(title="tastycharts"), # tastytrade pop-out charts Match(title="tastytrade"), # tastytrade pop-out side gutter Match(title="tastytrade - Portfolio Report"), # tastytrade pop-out allocation Match(wm_class="tasty.javafx.launcher.LauncherFxApp"), # tastytrade settings ] ) auto_fullscreen = True focus_on_window_activation = "smart" reconfigure_screens = True # If things like steam games want to auto-minimize themselves when losing # focus, should we respect this or not? auto_minimize = True # When using the Wayland backend, this can be used to configure input devices. wl_input_rules = None #+end_src * Autostart Programs Executes a bash script (autostart.sh) which launches programs for autostart. #+begin_src python @hook.subscribe.startup_once def start_once(): home = os.path.expanduser('~') subprocess.call([home + '/.config/qtile/autostart.sh']) #+end_src * Java Apps Might Need This #+begin_src python # XXX: Gasp! We're lying here. In fact, nobody really uses or cares about this # string besides java UI toolkits; you can see several discussions on the # mailing lists, GitHub issues, and other WM documentation that suggest setting # this string if your java app doesn't work correctly. We may as well just lie # and say that we're a working one by default. # # We choose LG3D to maximize irony: it is a 3D non-reparenting WM written in # java that happens to be on java's whitelist. wmname = "LG3D" #+end_src