Base theme, built on a class-based model. 9 workspaces based on function, basic system tray with cpu monitor, battery monitor and update indicator. Built-in multi-monitor support.
1
.gitignore
vendored
|
@ -139,3 +139,4 @@ ENV/
|
|||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
.idea
|
||||
|
|
94
config.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
# 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.
|
||||
|
||||
# Import Theme
|
||||
from libqtile import hook
|
||||
|
||||
try:
|
||||
from kuro.theme import Kuro
|
||||
Theme = Kuro()
|
||||
except ImportError:
|
||||
from kuro.base import BaseTheme
|
||||
Kuro = None
|
||||
Theme = BaseTheme()
|
||||
|
||||
# Import theme configuration
|
||||
try:
|
||||
from kuro.config import Config
|
||||
except ImportError:
|
||||
try:
|
||||
from kuro.baseconfig import BaseConfig as Config
|
||||
except ImportError:
|
||||
Config = None
|
||||
raise ImportError("Could not load theme Config or BaseConfig!")
|
||||
|
||||
# Initialize the Theme
|
||||
Theme.initialize()
|
||||
|
||||
# Hook theme into all hooks we know of
|
||||
hook.subscribe.startup_once(Theme.callback_startup_once)
|
||||
hook.subscribe.startup(Theme.callback_startup)
|
||||
hook.subscribe.startup_complete(Theme.callback_startup_complete)
|
||||
hook.subscribe.setgroup(Theme.callback_setgroup)
|
||||
hook.subscribe.addgroup(Theme.callback_addgroup)
|
||||
hook.subscribe.delgroup(Theme.callback_delgroup)
|
||||
hook.subscribe.changegroup(Theme.callback_changegroup)
|
||||
hook.subscribe.focus_change(Theme.callback_focus_change)
|
||||
hook.subscribe.float_change(Theme.callback_float_change)
|
||||
hook.subscribe.group_window_add(Theme.callback_group_window_add)
|
||||
hook.subscribe.window_name_change(Theme.callback_window_name_change)
|
||||
hook.subscribe.client_new(Theme.callback_client_new)
|
||||
hook.subscribe.client_managed(Theme.callback_client_managed)
|
||||
hook.subscribe.client_killed(Theme.callback_client_killed)
|
||||
hook.subscribe.client_state_changed(Theme.callback_client_state_changed)
|
||||
hook.subscribe.client_type_changed(Theme.callback_client_type_changed)
|
||||
hook.subscribe.client_focus(Theme.callback_client_focus)
|
||||
hook.subscribe.client_mouse_enter(Theme.callback_client_mouse_enter)
|
||||
hook.subscribe.client_name_updated(Theme.callback_client_name_updated)
|
||||
hook.subscribe.client_urgent_hint_changed(Theme.callback_client_urgent_hint_changed)
|
||||
hook.subscribe.layout_change(Theme.callback_layout_change)
|
||||
hook.subscribe.net_wm_icon_change(Theme.callback_net_wm_icon_change)
|
||||
hook.subscribe.selection_notify(Theme.callback_selection_notify)
|
||||
hook.subscribe.selection_change(Theme.callback_selection_change)
|
||||
hook.subscribe.screen_change(Theme.callback_screen_change)
|
||||
hook.subscribe.current_screen_change(Theme.callback_current_screen_change)
|
||||
|
||||
# Initialize variables from theme
|
||||
keys = Theme.keys
|
||||
groups = Theme.groups
|
||||
layouts = Theme.layouts
|
||||
widget_defaults = Theme.widget_defaults
|
||||
screens = Theme.screens
|
||||
dgroups_key_binder = Theme.dgroups_key_binder
|
||||
dgroups_app_rules = Theme.dgroups_app_rules
|
||||
main = Theme.main
|
||||
follow_mouse_focus = Theme.follow_mouse_focus
|
||||
bring_front_click = Theme.bring_front_click
|
||||
cursor_warp = Theme.cursor_warp
|
||||
floating_layout = Theme.floating_layout
|
||||
auto_fullscreen = Theme.auto_fullscreen
|
||||
focus_on_window_activation = Theme.focus_on_window_activation
|
||||
extentions = Theme.extensions
|
145
kuro/base.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
from libqtile import layout as libqtile_layout
|
||||
|
||||
|
||||
class BaseConfig:
|
||||
@classmethod
|
||||
def get(cls, key, default):
|
||||
if hasattr(cls, key):
|
||||
return cls.__dict__[key]
|
||||
else:
|
||||
return default
|
||||
|
||||
|
||||
class BaseTheme:
|
||||
# Changing variables initialized by function
|
||||
keys = None
|
||||
groups = None
|
||||
layouts = None
|
||||
widget_defaults = None
|
||||
screens = None
|
||||
|
||||
# 'Static' variables
|
||||
dgroups_key_binder = None
|
||||
dgroups_app_rules = []
|
||||
main = None
|
||||
follow_mouse_focus = True
|
||||
bring_front_click = False
|
||||
cursor_warp = False
|
||||
floating_layout = libqtile_layout.Floating()
|
||||
auto_fullscreen = True
|
||||
focus_on_window_activation = "smart"
|
||||
extensions = []
|
||||
|
||||
# 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"
|
||||
|
||||
def initialize(self):
|
||||
self.widget_defaults = self.init_widget_defaults()
|
||||
self.keys = self.init_keys()
|
||||
self.groups = self.init_groups()
|
||||
self.layouts = self.init_layouts()
|
||||
self.screens = self.init_screens()
|
||||
|
||||
def init_keys(self):
|
||||
return []
|
||||
|
||||
def init_groups(self):
|
||||
return []
|
||||
|
||||
def init_layouts(self):
|
||||
return []
|
||||
|
||||
def init_widget_defaults(self):
|
||||
return {}
|
||||
|
||||
def init_screens(self):
|
||||
return []
|
||||
|
||||
def init_mouse(self):
|
||||
return []
|
||||
|
||||
# Callbacks
|
||||
def callback_startup_once(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_startup(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_startup_complete(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_setgroup(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_addgroup(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_delgroup(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_changegroup(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_focus_change(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_float_change(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_group_window_add(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_window_name_change(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_new(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_managed(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_killed(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_state_changed(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_type_changed(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_focus(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_mouse_enter(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_name_updated(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_client_urgent_hint_changed(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_layout_change(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_net_wm_icon_change(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_selection_notify(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_selection_change(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_screen_change(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def callback_current_screen_change(self, *args, **kwargs):
|
||||
pass
|
58
kuro/config.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from kuro.base import BaseConfig
|
||||
|
||||
|
||||
# Config variables used in the main configuration
|
||||
class Config(BaseConfig):
|
||||
# Show debug messages
|
||||
debug = True
|
||||
|
||||
# Default Applications
|
||||
app_terminal = "terminator"
|
||||
|
||||
# Images
|
||||
desktop_bg = "/home/kevin/Pictures/wallpapers/desktop.png"
|
||||
|
||||
# Fonts
|
||||
font_default = "Noto Sans"
|
||||
font_topbar = "Noto Sans"
|
||||
font_clock = "Noto Sans"
|
||||
font_titlebar = "Noto Sans"
|
||||
font_groupbox = "FontAwesome"
|
||||
|
||||
# Font sizes
|
||||
fontsize_default = 11
|
||||
fontsize_topbar = 11
|
||||
fontsize_clock = 11
|
||||
fontsize_titlebar = 11
|
||||
fontsize_groupbox = 15
|
||||
|
||||
# Sizes
|
||||
width_border = 1
|
||||
margin_layout = 4
|
||||
width_groupbox_border = 1
|
||||
height_groupbox = 24
|
||||
margin_groupbox = 0
|
||||
width_spacer = 1
|
||||
padding_spacer = 4
|
||||
|
||||
# Variables
|
||||
bool_groupbox_disable_drag = True
|
||||
bool_groupbox_rounded_borders = True
|
||||
|
||||
# Colours
|
||||
colour_border_normal = "#333333"
|
||||
colour_border_focus = "#ffffff"
|
||||
colour_groupbox_border_normal = "#333333"
|
||||
colour_groupbox_border_focus = "#aaaaaa"
|
||||
colour_groupbox_icon_active = "#ffffff"
|
||||
colour_groupbox_icon_inactive = "#777777"
|
||||
colour_spacer_background = "#777777"
|
||||
|
||||
# Battery variables
|
||||
battery_theme_path = "/home/kevin/.config/qtile/kuro/resources/battery"
|
||||
battery_update_delay = 2
|
||||
|
||||
# CheckUpdates variables
|
||||
updates_display_format = "{updates}"
|
||||
updates_execute_command = "terminator -e 'echo Updating\ via\ yaourt\ -Sayu...; yaourt -Sayu'"
|
||||
updates_colour_available = '#f4d742'
|
BIN
kuro/resources/battery/battery-caution-charging.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
kuro/resources/battery/battery-caution.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
kuro/resources/battery/battery-full-charged.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
kuro/resources/battery/battery-full-charging.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
kuro/resources/battery/battery-full.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
kuro/resources/battery/battery-good-charging.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
kuro/resources/battery/battery-good.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
kuro/resources/battery/battery-low-charging.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
kuro/resources/battery/battery-low.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
kuro/resources/battery/battery-missing.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
201
kuro/theme.py
Normal file
|
@ -0,0 +1,201 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
from libqtile.config import Key, Screen, Group, Drag, Click
|
||||
from libqtile.command import lazy
|
||||
from libqtile import layout, bar, widget
|
||||
|
||||
# Import theme util functions
|
||||
from kuro import utils
|
||||
|
||||
# Import variables
|
||||
from kuro.base import BaseTheme
|
||||
|
||||
try:
|
||||
from kuro.config import Config
|
||||
except ImportError:
|
||||
try:
|
||||
from kuro.baseconfig import BaseConfig as Config
|
||||
except ImportError:
|
||||
Config = None
|
||||
raise ImportError("Could not load theme Config or BaseConfig!")
|
||||
|
||||
# Initialize logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Kuro(BaseTheme):
|
||||
# Shorthand for modifier key
|
||||
mod = Config.get("modifier", "mod4")
|
||||
|
||||
# Show debug messages
|
||||
debug = Config.get('debug', False)
|
||||
|
||||
def initialize(self):
|
||||
log.debug("Initializing Kuro Theme...")
|
||||
|
||||
super(Kuro, self).initialize()
|
||||
|
||||
# Update keys with keys for groups
|
||||
self.update_keys()
|
||||
|
||||
def init_keys(self):
|
||||
log.debug("Initializing keys")
|
||||
|
||||
return [
|
||||
# Switch between windows in current stack pane
|
||||
Key([self.mod], "k", lazy.layout.down()),
|
||||
Key([self.mod], "j", lazy.layout.up()),
|
||||
|
||||
# Move windows up or down in current stack
|
||||
Key([self.mod, "control"], "k", lazy.layout.shuffle_down()),
|
||||
Key([self.mod, "control"], "j", lazy.layout.shuffle_up()),
|
||||
|
||||
# Switch window focus to other pane(s) of stack
|
||||
Key([self.mod], "space", lazy.layout.next()),
|
||||
|
||||
# Swap panes of split stack
|
||||
Key([self.mod, "shift"], "space", lazy.layout.rotate()),
|
||||
|
||||
# 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([self.mod, "shift"], "Return", lazy.layout.toggle_split()),
|
||||
Key([self.mod], "Return", lazy.spawn(Config.get('app_terminal', "xterm"))),
|
||||
|
||||
# Toggle between different layouts as defined below
|
||||
Key([self.mod], "Tab", lazy.next_layout()),
|
||||
Key([self.mod], "w", lazy.window.kill()),
|
||||
|
||||
Key([self.mod, "control"], "r", lazy.restart()),
|
||||
Key([self.mod, "control"], "q", lazy.shutdown()),
|
||||
Key([self.mod], "r", lazy.spawncmd()),
|
||||
# Key([self.mod, "shift"], "e", self.evaluate()),
|
||||
]
|
||||
|
||||
def init_groups(self):
|
||||
log.debug("Initializing groups")
|
||||
|
||||
# http://fontawesome.io/cheatsheet
|
||||
return [Group(i) for i in ""]
|
||||
|
||||
def init_layouts(self):
|
||||
log.debug("Initializing layouts")
|
||||
|
||||
return [
|
||||
layout.Matrix(columns=2,
|
||||
border_focus=Config.get('colour_border_focus', "#ffffff"),
|
||||
border_normal=Config.get('colour_border_normal', "#777777"),
|
||||
border_width=Config.get('width_border', "1"),
|
||||
margin=Config.get('margin_layout', "0"),
|
||||
),
|
||||
layout.Max(),
|
||||
layout.Stack(num_stacks=2)
|
||||
]
|
||||
|
||||
def init_widget_defaults(self):
|
||||
log.debug("Initializing widget_defaults")
|
||||
|
||||
return {
|
||||
"font": Config.get('font_default', "Sans"),
|
||||
"fontsize": Config.get('fontsize_default', 16),
|
||||
"padding": 3,
|
||||
}
|
||||
|
||||
def init_screens(self):
|
||||
log.debug("Initializing screens")
|
||||
|
||||
num_screens = utils.get_screen_count()
|
||||
|
||||
screens = []
|
||||
for x in range(num_screens):
|
||||
widgets = []
|
||||
widgets.extend([
|
||||
utils.bar_separator(Config),
|
||||
widget.GroupBox(
|
||||
active=Config.get('colour_groupbox_icon_active', '#ffffff'),
|
||||
borderwidth=Config.get('width_groupbox_border', 1),
|
||||
disable_drag=Config.get('bool_groupbox_disable_drag', False),
|
||||
font=Config.get('font_groupbox', 'Arial'),
|
||||
fontsize=Config.get('fontsize_groupbox', 15),
|
||||
highlight_color=Config.get("colour_groupbox_border_normal", '#444444'),
|
||||
inactive=Config.get('colour_groupbox_icon_inactive', '#444444'),
|
||||
rounded=Config.get('bool_groupbox_rounded_borders', True),
|
||||
this_current_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'),
|
||||
this_screen_border=Config.get('colour_groupbox_border_focus', '#ffffff'),
|
||||
margin=Config.get('margin_groupbox', 0)
|
||||
),
|
||||
utils.bar_separator(Config),
|
||||
widget.Prompt(**self.widget_defaults),
|
||||
|
||||
widget.WindowName(**self.widget_defaults),
|
||||
|
||||
widget.CPUGraph(
|
||||
width=25,
|
||||
border_color="#000000",
|
||||
border_width=0,
|
||||
line_width=1,
|
||||
samples=10,
|
||||
),
|
||||
widget.BatteryIcon(
|
||||
battery_name=Config.get('battery_name', 'BAT0'),
|
||||
energy_full_file=Config.get('battery_energy_full_file', 'charge_full'),
|
||||
energy_now_file=Config.get('battery_energy_now_file', 'charge_now'),
|
||||
theme_path=Config.get('battery_theme_path', '/home/docs/checkouts/readthedocs.org/user_builds/qtile/checkouts/latest/libqtile/resources/battery-icons'),
|
||||
update_delay=Config.get('battery_update_delay', 30)
|
||||
),
|
||||
])
|
||||
|
||||
# Systray only on first screen
|
||||
if x == 0:
|
||||
widgets.append(widget.Systray(**self.widget_defaults))
|
||||
|
||||
widgets.extend([
|
||||
utils.bar_separator(Config),
|
||||
widget.Clock(format="%a %d %b, %H:%M", **self.widget_defaults),
|
||||
utils.CheckUpdatesYaourt(
|
||||
colour_no_updates=Config.get('updates_colour_none', '#ffffff'),
|
||||
colour_have_updates=Config.get('updates_colour_available', '#ff0000'),
|
||||
display_format=Config.get('updates_display_format', 'Updates: {updates}'),
|
||||
execute=Config.get('updates_execute_command', None),
|
||||
**self.widget_defaults
|
||||
),
|
||||
widget.TextBox("#{}".format(x), name="default", **self.widget_defaults),
|
||||
])
|
||||
screens.append(Screen(top=bar.Bar(
|
||||
widgets=widgets,
|
||||
size=Config.get('height_groupbox', 30)
|
||||
)))
|
||||
|
||||
return screens
|
||||
|
||||
def init_mouse(self):
|
||||
log.debug("Initializing mouse")
|
||||
|
||||
# Drag floating layouts.
|
||||
return [
|
||||
Drag([self.mod], "Button1", lazy.window.set_position_floating(),
|
||||
start=lazy.window.get_position()),
|
||||
Drag([self.mod], "Button3", lazy.window.set_size_floating(),
|
||||
start=lazy.window.get_size()),
|
||||
Click([self.mod], "Button2", lazy.window.bring_to_front())
|
||||
]
|
||||
|
||||
def update_keys(self):
|
||||
log.debug("Updating keys")
|
||||
|
||||
for i, g in enumerate(self.groups):
|
||||
# mod1 + number = switch to group
|
||||
self.keys.append(
|
||||
Key([self.mod], str(i + 1), lazy.group[g.name].toscreen())
|
||||
)
|
||||
|
||||
# mod1 + shift + number = switch to & move focused window to group
|
||||
self.keys.append(
|
||||
Key([self.mod, "shift"], str(i + 1), lazy.window.togroup(g.name))
|
||||
)
|
||||
|
||||
def callback_startup(self):
|
||||
utils.execute("sleep 3")
|
||||
utils.execute_once("nitrogen --restore")
|
50
kuro/utils.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
import re
|
||||
import subprocess
|
||||
from libqtile import widget, bar
|
||||
from libqtile.widget.check_updates import CheckUpdates
|
||||
|
||||
|
||||
def is_running(process):
|
||||
s = subprocess.Popen(["ps", "axuw"], stdout=subprocess.PIPE)
|
||||
for x in s.stdout:
|
||||
if re.search(process, x.decode('utf-8')):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def execute(process):
|
||||
return subprocess.Popen(process.split())
|
||||
|
||||
|
||||
def execute_once(process):
|
||||
if not is_running(process):
|
||||
return subprocess.Popen(process.split())
|
||||
|
||||
|
||||
def get_screen_count():
|
||||
try:
|
||||
output = subprocess.check_output("xrandr -q".split()).decode('utf-8')
|
||||
output = [x for x in output.split("\n") if " connected" in x]
|
||||
except subprocess.CalledProcessError:
|
||||
return 1
|
||||
|
||||
if output:
|
||||
return len(output)
|
||||
else:
|
||||
return 1
|
||||
|
||||
|
||||
def bar_separator(config):
|
||||
return widget.Sep(foreground=config.get('colour_spacer_background', '#777777'),
|
||||
linewidth=config.get('width_spacer', 1),
|
||||
padding=config.get('padding_spacer', 4),
|
||||
)
|
||||
|
||||
|
||||
class CheckUpdatesYaourt(CheckUpdates):
|
||||
def __init__(self, **config):
|
||||
super(CheckUpdatesYaourt, self).__init__(**config)
|
||||
# Override command and output with yaourt command
|
||||
self.cmd = "yaourt -Qua".split()
|
||||
self.subtr = 0
|
||||
|