""" Infinidoge's Qtile Configuration Sets up - Keybindings - Widgets - Screens """ import subprocess import os from typing import List, Any # noqa: F401 from libqtile import bar, layout, widget, hook from libqtile.config import Click, Drag, Group, Key, KeyChord, Match, Screen from libqtile.lazy import lazy from libqtile.utils import guess_terminal colors = [ # panel background ["#282c34", "#282c34"], # 0 # background for current screen tab ["#3d3f4b", "#434758"], # 1 # font color for group names ["#ffffff", "#ffffff"], # 2 # border line color for current tab ["#ff5555", "#ff5555"], # 3 # border line color for 'other tabs' and color for 'odd widgets' ["#74438f", "#74438f"], # 4 # color for the 'even widgets' ["#4f76c7", "#4f76c7"], # 5 # window name ["#e1acff", "#e1acff"], # 6 # background for inactive screens ["#ecbbfb", "#ecbbfb"], # 7 ] class Apps: """ Commonly referenced apps, such as the terminal or editor """ TERMINAL = guess_terminal(preference="kitty") # Set preference if necessary SHELL = "zsh" EDITOR = "emacsclient -c -a 'emacs'" @classmethod def terminal_command(cls, program, args=tuple(), *, terminal=None): """ Returns a string of a command to open a program in the terminal, with the given arguments. """ return f"{terminal or cls.TERMINAL} {' '.join(args)} -- {program}" @classmethod def open_in_terminal(cls, program, args=tuple(), *, terminal=None): """ Opens a program in the terminal, with the given arguments. """ return lazy.spawn(cls.terminal_command(program, args=args, terminal=terminal)) @classmethod def open_in_editor(cls, file_, args=tuple()): """ Opens a file in the editor, with the given arguments """ return lazy.spawn(f"{cls.EDITOR} {' '.join(args)} {file_}") class Keys: """ Common modifier keys used in key configuration. """ SUPER = "mod4" # Windows key/super key CONTROL = "control" SHIFT = "shift" ALT = "mod1" ALT_GR = "mod5" META = None # "mod3" # Not assigned to key yet CAPS_LOCK = "lock" NUM_LOCK = "mod2" # fmt: off keys = [ # Switch between windows Key( [Keys.SUPER], "Left", lazy.layout.left(), desc="Move focus to left", ), Key( [Keys.SUPER], "Right", lazy.layout.right(), desc="Move focus to right", ), Key( [Keys.SUPER], "Down", lazy.layout.down(), desc="Move focus down", ), Key( [Keys.SUPER], "Up", lazy.layout.up(), desc="Move focus up", ), Key( [Keys.SUPER], "space", lazy.layout.next(), desc="Move window focus to other window", ), # Switch focus of monitors Key( [Keys.SUPER, Keys.CONTROL], "1", lazy.to_screen(1), desc="Keyboard focus to left monitor" ), Key( [Keys.SUPER, Keys.CONTROL], "2", lazy.to_screen(0), desc="Keyboard focus to middle monitor" ), Key( [Keys.SUPER, Keys.CONTROL], "3", lazy.to_screen(2), desc="Keyboard focus to right monitor" ), Key( [Keys.SUPER, Keys.CONTROL, Keys.SHIFT], "comma", lazy.next_screen(), desc="Move focus to next monitor" ), Key( [Keys.SUPER, Keys.CONTROL, Keys.SHIFT], "period", lazy.prev_screen(), desc="Move focus to prev monitor" ), # 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( [Keys.SUPER, Keys.SHIFT], "Left", lazy.layout.shuffle_left(), desc="Move window to the left", ), Key( [Keys.SUPER, Keys.SHIFT], "Right", lazy.layout.shuffle_right(), desc="Move window to the right", ), Key( [Keys.SUPER, Keys.SHIFT], "Down", lazy.layout.shuffle_down(), desc="Move window down", ), Key( [Keys.SUPER, Keys.SHIFT], "Up", lazy.layout.shuffle_up(), desc="Move window up", ), # 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( [Keys.SUPER, Keys.SHIFT], "Return", lazy.layout.toggle_split(), desc="Toggle between split and unsplit sides of stack", ), # Grow windows. If current window is on the edge of screen and direction # will be to screen edge - window would shrink. Key( [Keys.SUPER, Keys.CONTROL], "Left", lazy.layout.grow_left(), desc="Grow window to the left", ), Key( [Keys.SUPER, Keys.CONTROL], "Right", lazy.layout.grow_right(), desc="Grow window to the right", ), Key( [Keys.SUPER, Keys.CONTROL], "Down", lazy.layout.grow_down(), desc="Grow window down", ), Key( [Keys.SUPER, Keys.CONTROL], "Up", lazy.layout.grow_up(), desc="Grow window up", ), Key( [Keys.SUPER, Keys.CONTROL], "n", lazy.layout.normalize(), desc="Reset all window sizes", ), Key( [Keys.SUPER, Keys.CONTROL], "m", lazy.layout.maximize(), desc="Maximize the selected window", ), # Toggle between different layouts as defined below Key( [Keys.SUPER], "Tab", lazy.next_layout(), desc="Move forward through layouts", ), Key( [Keys.SUPER, Keys.SHIFT], "Tab", lazy.prev_layout(), desc="Move backward through layouts", ), # Qtile management keys (close, restart, shutdown, lock, spawn) Key( [Keys.SUPER], "w", lazy.window.kill(), desc="Kill focused window", ), Key( [Keys.SUPER, Keys.CONTROL], "r", lazy.restart(), desc="Restart Qtile", ), Key( [Keys.SUPER, Keys.CONTROL], "q", lazy.shutdown(), desc="Shutdown Qtile", ), Key( [Keys.SUPER, Keys.CONTROL], "l", lazy.spawn("xsecurelock"), desc="Lock Screen", ), Key( [Keys.SUPER], "r", lazy.spawncmd(), desc="Spawn a command using a prompt widget", ), Key( [Keys.SUPER, Keys.SHIFT], "r", lazy.spawncmd(prompt="shell", command=Apps.terminal_command("%s", args=("--hold",))), desc="Spawn a command in a shell using a prompt widget", ), # Power management KeyChord( [Keys.SUPER], "p", [ Key( [], "h", lazy.spawn("systemctl hibernate"), desc="Hibernates the system", ), Key( [], "s", lazy.spawn("systemctl suspend"), desc="Suspends the system", ), Key( [], "b", lazy.spawn("xset dpms force off"), desc="Turns off the screen", ), Key( [], "r", lazy.spawn("reboot"), desc="Reboots the system", ), Key( [], "p", lazy.spawn("shutdown now"), desc="Shuts down the system", ), Key( [], "k", lazy.spawn("systemctl kexec"), desc="Quick reboots system, directly loading kernel into memory", ), Key( [], "l", lazy.spawn("kill -9 -1"), desc="Logs out of the system", ), ], ), # Volume Key( [], "XF86AudioRaiseVolume", lazy.spawn("amixer set Master 2%+"), desc="Raise volume", ), Key( [], "XF86AudioLowerVolume", lazy.spawn("amixer set Master 2%-"), desc="Lower volume", ), Key( [], "XF86AudioMute", lazy.spawn("amixer set Master toggle"), desc="Toggle mute", ), # Application shortcuts Key( [Keys.SUPER], "Return", lazy.spawn(Apps.TERMINAL), desc="Launch terminal", ), Key( [Keys.SUPER, Keys.ALT], "f", lazy.spawn("firefox --new-window"), desc="Start Firefox (New Window)", ), Key( [Keys.SUPER, Keys.ALT], "e", lazy.spawn("emacsclient -c -a 'emacs'"), desc="Launch Emacs", ), Key( [Keys.SUPER, Keys.ALT], "d", lazy.spawn("discord-canary"), desc="Launch Discord", ), ] # fmt: on groups = [ *[Group(i) for i in "123456789"], Group("0", layouts=[layout.TreeTab()]), ] for i in groups: keys.extend( [ Key( [Keys.SUPER], i.name, lazy.group[i.name].toscreen(), desc=f"Switch to group {i.name}", ), Key( [Keys.SUPER, Keys.SHIFT], i.name, lazy.window.togroup(i.name, switch_group=True), desc=f"Switch to & move focused window to group {i.name}", ), Key( [Keys.SUPER, Keys.CONTROL, Keys.SHIFT], i.name, lazy.window.togroup(i.name), desc=f"Move focused window to group {i.name}", ), ] ) layouts = [ layout.Columns(border_focus_stack="#d75f5f"), layout.Max(), layout.TreeTab(), # Try more layouts by unleashing below layouts. # layout.Stack(num_stacks=2), # layout.Bsp(), # layout.Matrix(), # layout.MonadTall(), # layout.MonadWide(), # layout.RatioTile(), # layout.Tile(), # layout.VerticalTile(), ] widget_defaults = dict(font="sans", fontsize=12, padding=2, background=colors[0]) extension_defaults = widget_defaults.copy() def create_powerline( widgets: List[List[Any]], *, base, foreground, backgrounds, sep="", padding=0, fontsize=37, ): """ Takes in a list of widgets and produces a powerline-style bar as the result. Cycles through the provided colors and uses the provided character as a separator. Takes input as a list of widgets, then base, foreground, and backgrounds as keyword arguments. Setting sep changes the separator """ output = [] for i, widgets_list in enumerate(widgets): bg_index = (len(widgets) - i) % len(backgrounds) output.append( widget.TextBox( text=sep, foreground=backgrounds[bg_index], background=base if i == 0 else backgrounds[bg_index - (len(backgrounds) - 1)], fontsize=fontsize, padding=padding, ) ) for elem in widgets_list: elem.foreground = foreground elem.background = backgrounds[bg_index] output.append(elem) return output def init_widget_list(main=True): """ Returns a list of widgets suitable for a qtile bar """ widget_list = [ widget.Sep(linewidth=0, padding=6, foreground=colors[2], background=colors[0]), widget.GroupBox( font="Ubuntu Mono", fontsize=14, margin_y=3, margin_x=0, padding_y=5, padding_x=3, borderwidth=3, active=colors[2], inactive=colors[7], rounded=False, highlight_color=colors[1], highlight_method="line", this_current_screen_border=colors[5], this_screen_border=colors[6], other_current_screen_border=colors[5], other_screen_border=colors[4], foreground=colors[2], background=colors[0], invert_mouse_wheel=True, ), widget.Chord( font="Ubuntu Mono", padding=10, foreground=colors[3], background=colors[1], max_chars=10, ), widget.Sep(linewidth=0, padding=6, foreground=colors[2], background=colors[0]), widget.Prompt( prompt="{prompt}: ", font="Ubuntu Mono", 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.Systray(background=colors[0], padding=5), ] if main else [] ), widget.Sep(linewidth=0, padding=6, foreground=colors[0], background=colors[0]), *create_powerline( [ # Widgets only found on the main screen's powerline *( [ [ widget.TextBox(text="Wireless (wlp41s0):", padding=2), widget.Net( interface="wlp41s0", format="{down} ↓↑ {up}", padding=5, ), widget.Sep(linewidth=2, padding=3), widget.TextBox(text="Ethernet (eth0):", padding=2), widget.Net( interface="eth0", format="{down} ↓↑ {up}", padding=5, ), ], [ widget.TextBox( text=" 🖬", padding=0, fontsize=14, ), widget.Memory( padding=5, ), ], [ widget.TextBox( text=" Vol:", padding=0, ), widget.Volume( padding=5, ), ], *( [ widget.Battery( format="{char} {percent:2.1%} {hour:d}h:{min:02d}m", update_interval=30, padding=5, ), ] if laptop else [] ), ] if main else [] ), # Widgets found on the powerline of all screens [ widget.CurrentLayoutIcon( custom_icon_paths=[os.path.expanduser("~/.config/qtile/icons")], padding=0, scale=0.7, ), widget.CurrentLayout( padding=5, ), ], [ widget.Clock( format="%A, %B %d, %Y - %H:%M:%S ", ), ], ], base=colors[0], foreground=colors[2], backgrounds=[ colors[4], colors[5], ], ), ] return widget_list screens = [ Screen( bottom=bar.Bar( init_widget_list(main=(i == 0), laptop=os.getenv("LAPTOP", False)), size=20, opacity=1.0, ), wallpaper="~/Pictures/BotanBackground.jpg", wallpaper_mode="fill", ) for i in range( int( os.popen( "xrandr --listmonitors | grep 'Monitors:' | awk {'print $2'}" ).read() ) ) ] # Drag floating layouts. mouse = [ Drag( [Keys.SUPER], "Button1", lazy.window.set_position_floating(), start=lazy.window.get_position(), ), Drag( [Keys.SUPER], "Button3", lazy.window.set_size_floating(), start=lazy.window.get_size(), ), Click( [Keys.SUPER], "Button2", lazy.window.bring_to_front(), ), ] 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 ] ) dgroups_key_binder = None dgroups_app_rules = [] # type: List follow_mouse_focus = True bring_front_click = False cursor_warp = True auto_fullscreen = True focus_on_window_activation = "never" 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 # 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"