Mostly done now, except for some popups and tweaking. We have a top bar with dmenu launcher, 9 workspaces, window buttons, thermal monitor, graphs, battery monitor, wifi monitor, sound monitors, system tray, layout indicator, datetime, update counter and screen indicator. On the bottom is a debug bar (only shown when debug=true in config) which is pretty useless at the moment.

This commit is contained in:
Kevin Alberts 2017-06-07 12:07:30 +02:00
parent a5ef8739db
commit 2df1347241
24 changed files with 721 additions and 51 deletions

View file

@ -26,24 +26,30 @@
# Import Theme # Import Theme
from libqtile import hook from libqtile import hook
from libqtile.log_utils import logger
try: try:
from kuro.theme import Kuro from kuro.theme import Kuro
Theme = Kuro() Theme = Kuro()
except ImportError: except ImportError as e:
from kuro.base import BaseTheme logger.error("Could not load Kuro Theme. Trying to load BaseTheme. Error: {}".format(e))
Kuro = None try:
Theme = BaseTheme() from kuro.base import BaseTheme as Kuro
Theme = Kuro()
except ImportError as e:
Kuro = None
raise ImportError("Could not load theme Config or BaseTheme! Error: {}".format(e))
# Import theme configuration # Import theme configuration
try: try:
from kuro.config import Config from kuro.config import Config
except ImportError: except ImportError as e:
logger.error("Could not load Kuro Config. Trying to load BaseConfig. Error: {}".format(e))
try: try:
from kuro.baseconfig import BaseConfig as Config from kuro.baseconfig import BaseConfig as Config
except ImportError: except ImportError as e:
Config = None Config = None
raise ImportError("Could not load theme Config or BaseConfig!") raise ImportError("Could not load theme Config or BaseConfig! Error: {}".format(e))
# Initialize the Theme # Initialize the Theme
Theme.initialize() Theme.initialize()
@ -92,3 +98,14 @@ floating_layout = Theme.floating_layout
auto_fullscreen = Theme.auto_fullscreen auto_fullscreen = Theme.auto_fullscreen
focus_on_window_activation = Theme.focus_on_window_activation focus_on_window_activation = Theme.focus_on_window_activation
extentions = Theme.extensions extentions = Theme.extensions
def main(qtile):
# set logging level
if Config.get('debug', False):
if Config.get('verbose', False):
qtile.cmd_debug()
else:
qtile.cmd_info()
else:
qtile.cmd_warning()

View file

@ -1,4 +1,6 @@
from libqtile import layout as libqtile_layout from libqtile import layout as libqtile_layout, layout, bar, widget
from libqtile.command import lazy
from libqtile.config import Key, Group, Screen, Drag, Click
class BaseConfig: class BaseConfig:
@ -25,7 +27,22 @@ class BaseTheme:
follow_mouse_focus = True follow_mouse_focus = True
bring_front_click = False bring_front_click = False
cursor_warp = False cursor_warp = False
floating_layout = libqtile_layout.Floating() floating_layout = libqtile_layout.Floating(float_rules=[
{'wmclass': 'confirm'},
{'wmclass': 'dialog'},
{'wmclass': 'download'},
{'wmclass': 'error'},
{'wmclass': 'file_progress'},
{'wmclass': 'notification'},
{'wmclass': 'splash'},
{'wmclass': 'toolbar'},
{'wmclass': 'confirmreset'}, # gitk
{'wmclass': 'makebranch'}, # gitk
{'wmclass': 'maketag'}, # gitk
{'wname': 'branchdialog'}, # gitk
{'wname': 'pinentry'}, # GPG key password entry
{'wmclass': 'ssh-askpass'}, # ssh-askpass
])
auto_fullscreen = True auto_fullscreen = True
focus_on_window_activation = "smart" focus_on_window_activation = "smart"
extensions = [] extensions = []
@ -48,22 +65,88 @@ class BaseTheme:
self.screens = self.init_screens() self.screens = self.init_screens()
def init_keys(self): def init_keys(self):
return [] return [
# Switch between windows in current stack pane
Key(["mod4"], "k", lazy.layout.down()),
Key(["mod4"], "j", lazy.layout.up()),
# Move windows up or down in current stack
Key(["mod4", "control"], "k", lazy.layout.shuffle_down()),
Key(["mod4", "control"], "j", lazy.layout.shuffle_up()),
# Switch window focus to other pane(s) of stack
Key(["mod4"], "space", lazy.layout.next()),
# Swap panes of split stack
Key(["mod4", "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(["mod4", "shift"], "Return", lazy.layout.toggle_split()),
Key(["mod4"], "Return", lazy.spawn("xterm")),
# Toggle between different layouts as defined below
Key(["mod4"], "Tab", lazy.next_layout()),
Key(["mod4"], "w", lazy.window.kill()),
Key(["mod4", "control"], "r", lazy.restart()),
Key(["mod4", "control"], "q", lazy.shutdown()),
Key(["mod4"], "r", lazy.spawncmd()),
]
def init_groups(self): def init_groups(self):
return [] groups = [Group(i) for i in "asdfuiop"]
for i in groups:
self.keys.extend([
# mod1 + letter of group = switch to group
Key(["mod4"], i.name, lazy.group[i.name].toscreen()),
# mod1 + shift + letter of group = switch to & move focused window to group
Key(["mod4", "shift"], i.name, lazy.window.togroup(i.name)),
])
return groups
def init_layouts(self): def init_layouts(self):
return [] return [
layout.Max(),
layout.Stack(num_stacks=2)
]
def init_widget_defaults(self): def init_widget_defaults(self):
return {} return dict(
font='sans',
fontsize=12,
padding=3,
)
def init_screens(self): def init_screens(self):
return [] # noinspection PyUnresolvedReferences
return [
Screen(
bottom=bar.Bar(
[
widget.GroupBox(),
widget.Prompt(),
widget.WindowName(),
widget.TextBox("Kuro BaseConfig", foreground="#ff0000", name="default"),
widget.Systray(),
widget.Clock(format='%Y-%m-%d %a %I:%M %p'),
],
24,
),
),
]
def init_mouse(self): def init_mouse(self):
return [] return [
Drag(["mod4"], "Button1", lazy.window.set_position_floating(),
start=lazy.window.get_position()),
Drag(["mod4"], "Button3", lazy.window.set_size_floating(),
start=lazy.window.get_size()),
Click(["mod4"], "Button2", lazy.window.bring_to_front())
]
# Callbacks # Callbacks
def callback_startup_once(self, *args, **kwargs): def callback_startup_once(self, *args, **kwargs):

View file

@ -3,56 +3,110 @@ from kuro.base import BaseConfig
# Config variables used in the main configuration # Config variables used in the main configuration
class Config(BaseConfig): class Config(BaseConfig):
# Show debug messages # Show debug bar and messages
debug = True debug = True
verbose = False
# Default Applications # Default Applications
app_terminal = "terminator" app_terminal = "terminator"
app_launcher = "dmenu_run -i -p '»' -nb '#000000' -fn 'Noto Sans-11' -nf '#777777' -sb '#1793d0' -sf '#ffffff'"
# Images # Images
desktop_bg = "/home/kevin/Pictures/wallpapers/desktop.png" desktop_bg = "/home/kevin/Pictures/wallpapers/desktop.png"
applauncher_image = "/home/kevin/.config/qtile/kuro/resources/arch.png"
# Fonts # Fonts
font_default = "Noto Sans" font_default = "Noto Sans"
font_topbar = "Noto Sans" font_topbar = "Noto Sans"
font_clock = "Noto Sans" font_clock = "Noto Sans"
font_titlebar = "Noto Sans" font_titlebar = "Noto Sans"
font_groupbox = "FontAwesome"
# Font sizes # Font sizes
fontsize_default = 11 fontsize_default = 11
fontsize_topbar = 11 fontsize_topbar = 11
fontsize_clock = 11 fontsize_clock = 11
fontsize_titlebar = 11 fontsize_titlebar = 11
fontsize_groupbox = 15
# Sizes # Sizes
width_border = 1 width_border = 1
margin_layout = 4 margin_layout = 4
width_groupbox_border = 1
height_groupbox = 24
margin_groupbox = 0
width_spacer = 1 width_spacer = 1
padding_spacer = 4 padding_spacer = 4
grow_amount = 5
# Variables width_zoomy_column = 300
bool_groupbox_disable_drag = True
bool_groupbox_rounded_borders = True
# Colours # Colours
colour_border_normal = "#333333" colour_border_normal = "#333333"
colour_border_focus = "#ffffff" colour_border_focus = "#ffffff"
colour_border_urgent = "#774400"
colour_spacer_background = "#777777"
# Groupbox variables
font_groupbox = "FontAwesome"
fontsize_groupbox = 15
width_groupbox_border = 1
height_groupbox = 24
margin_groupbox = 0
bool_groupbox_disable_drag = True
bool_groupbox_rounded_borders = True
colour_groupbox_border_normal = "#333333" colour_groupbox_border_normal = "#333333"
colour_groupbox_border_focus = "#aaaaaa" colour_groupbox_border_focus = "#aaaaaa"
colour_groupbox_icon_active = "#ffffff" colour_groupbox_icon_active = "#ffffff"
colour_groupbox_icon_inactive = "#777777" colour_groupbox_icon_inactive = "#777777"
colour_spacer_background = "#777777"
# Tasklist variables
tasklist_border = "#ffffff"
tasklist_urgent_border = "#774400"
tasklist_font = "Noto Sans"
tasklist_fontsize = 11
# Thermal indicator variables
thermal_threshold = 75
thermal_sensor = "Package id 0"
thermal_chip = "coretemp-isa-0000"
# CPU graph variables
cpu_graph_colour = '#ff0000'
# Memory graph variables
mem_graph_colour = '#ff00ff'
# HDD graph variables
hdd_graph_colour = '#ffff00'
# Battery variables # Battery variables
battery_theme_path = "/home/kevin/.config/qtile/kuro/resources/battery" battery_theme_path = "/home/kevin/.config/qtile/kuro/resources/battery"
battery_update_delay = 2 battery_update_delay = 5
# Wifi variables
wifi_interface = "wlp4s0"
wifi_theme_path = "/home/kevin/.config/qtile/kuro/resources/wifi"
wifi_update_interval = 5
# Normal volume icon variables
volume_font = "Noto Sans"
volume_fontsize = 11
volume_theme_path = "/home/kevin/.config/qtile/kuro/resources/volume"
volume_get_command = "pamixer --sink 0 --get-volume".split()
volume_mute_command = "pamixer --sink 0 -t".split()
volume_up_command = "pamixer --sink 0 -i 2".split()
volume_down_command = "pamixer --sink 0 -d 2".split()
volume_is_bluetooth_icon = False
volume_update_interval = 0.2
# Bluetooth volume icon variables
bluevol_font = "Noto Sans"
bluevol_fontsize = 11
bluevol_theme_path = "/home/kevin/.config/qtile/kuro/resources/bluetooth_volume"
bluevol_get_command = "pamixer --sink {bsink} --get-volume".split()
bluevol_mute_command = "pamixer --sink {bsink} -t".split()
bluevol_up_command = "pamixer --sink {bsink} -i 2".split()
bluevol_down_command = "pamixer --sink {bsink} -d 2".split()
bluevol_is_bluetooth_icon = True
bluevol_update_interval = 0.2
# CheckUpdates variables # CheckUpdates variables
updates_display_format = "{updates}" updates_display_format = "{updates}"
updates_execute_command = "terminator -e 'echo Updating\ via\ yaourt\ -Sayu...; yaourt -Sayu'" updates_execute_command = "terminator -e 'echo Updating\ via\ yaourt\ -Sayu...; yaourt -Sayu'"
updates_colour_available = '#f4d742' updates_colour_available = '#f4d742'

BIN
kuro/resources/arch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

View file

@ -1,6 +1,3 @@
import logging
import os
from libqtile.config import Key, Screen, Group, Drag, Click from libqtile.config import Key, Screen, Group, Drag, Click
from libqtile.command import lazy from libqtile.command import lazy
from libqtile import layout, bar, widget from libqtile import layout, bar, widget
@ -21,7 +18,7 @@ except ImportError:
raise ImportError("Could not load theme Config or BaseConfig!") raise ImportError("Could not load theme Config or BaseConfig!")
# Initialize logging # Initialize logging
log = logging.getLogger(__name__) from libqtile.log_utils import logger as log
class Kuro(BaseTheme): class Kuro(BaseTheme):
@ -36,7 +33,7 @@ class Kuro(BaseTheme):
super(Kuro, self).initialize() super(Kuro, self).initialize()
# Update keys with keys for groups # Update keys with keys for groups and layouts
self.update_keys() self.update_keys()
def init_keys(self): def init_keys(self):
@ -57,20 +54,31 @@ class Kuro(BaseTheme):
# Swap panes of split stack # Swap panes of split stack
Key([self.mod, "shift"], "space", lazy.layout.rotate()), Key([self.mod, "shift"], "space", lazy.layout.rotate()),
# Fullscreen toggle
Key([self.mod], 'f', lazy.window.toggle_fullscreen()),
# Toggle between split and unsplit sides of stack. # Toggle between split and unsplit sides of stack.
# Split = all windows displayed # Split = all windows displayed
# Unsplit = 1 window displayed, like Max layout, but still with # Unsplit = 1 window displayed, like Max layout, but still with
# multiple stack panes # multiple stack panes
Key([self.mod, "shift"], "Return", lazy.layout.toggle_split()), Key([self.mod, "shift"], "Return", lazy.layout.toggle_split()),
# Super-Enter to start terminal
Key([self.mod], "Return", lazy.spawn(Config.get('app_terminal', "xterm"))), Key([self.mod], "Return", lazy.spawn(Config.get('app_terminal', "xterm"))),
# Super-R to start dmenu_run
Key([self.mod], "r", lazy.spawn(Config.get('app_launcher', "dmenu_run"))),
# Super-Shift-R to start spawncmd
Key([self.mod, "shift"], "r", lazy.spawncmd()),
# Toggle between different layouts as defined below # Toggle between different layouts as defined below
Key([self.mod], "Tab", lazy.next_layout()), Key([self.mod], "Tab", lazy.next_layout()),
Key([self.mod], "w", lazy.window.kill()), Key([self.mod], "w", lazy.window.kill()),
Key([self.mod, "control"], "r", lazy.restart()), Key([self.mod, "control"], "r", lazy.restart()),
Key([self.mod, "control"], "q", lazy.shutdown()), Key([self.mod, "control"], "q", lazy.shutdown()),
Key([self.mod], "r", lazy.spawncmd()),
# Key([self.mod, "shift"], "e", self.evaluate()), # Key([self.mod, "shift"], "e", self.evaluate()),
] ]
@ -84,22 +92,28 @@ class Kuro(BaseTheme):
log.debug("Initializing layouts") log.debug("Initializing layouts")
return [ return [
layout.Matrix(columns=2, layout.Wmii(
border_focus=Config.get('colour_border_focus', "#ffffff"), border_focus=Config.get('colour_border_focus', "#ffffff"),
border_normal=Config.get('colour_border_normal', "#777777"), border_focus_stack=Config.get('colour_border_normal', "#777777"),
border_width=Config.get('width_border', "1"), border_normal=Config.get('colour_border_normal', "#777777"),
margin=Config.get('margin_layout', "0"), border_normal_stack=Config.get('colour_border_normal', "#777777"),
), border_width=Config.get('width_border', "1"),
grow_amount=Config.get('grow_amount', "5"),
margin=Config.get('margin_layout', "0"),
),
layout.Max(), layout.Max(),
layout.Stack(num_stacks=2) layout.Zoomy(
columnwidth=Config.get('width_zoomy_column', 150),
margin=Config.get('margin_layout', "0"),
)
] ]
def init_widget_defaults(self): def init_widget_defaults(self):
log.debug("Initializing widget_defaults") log.debug("Initializing widget_defaults")
return { return {
"font": Config.get('font_default', "Sans"), "font": Config.get('font_topbar', "Sans"),
"fontsize": Config.get('fontsize_default', 16), "fontsize": Config.get('fontsize_topbar', 16),
"padding": 3, "padding": 3,
} }
@ -107,11 +121,16 @@ class Kuro(BaseTheme):
log.debug("Initializing screens") log.debug("Initializing screens")
num_screens = utils.get_screen_count() num_screens = utils.get_screen_count()
if num_screens == 0:
num_screens = 1
screens = [] screens = []
for x in range(num_screens): for x in range(num_screens):
widgets = [] widgets = []
widgets.extend([ widgets.extend([
utils.AppLauncherIcon(
filename=Config.get('applauncher_image', 'apps.png')
),
utils.bar_separator(Config), utils.bar_separator(Config),
widget.GroupBox( widget.GroupBox(
active=Config.get('colour_groupbox_icon_active', '#ffffff'), active=Config.get('colour_groupbox_icon_active', '#ffffff'),
@ -129,22 +148,114 @@ class Kuro(BaseTheme):
utils.bar_separator(Config), utils.bar_separator(Config),
widget.Prompt(**self.widget_defaults), widget.Prompt(**self.widget_defaults),
widget.WindowName(**self.widget_defaults), widget.TaskList(
border=Config.get('tasklist_border', '#ffffff'),
borderwidth=Config.get('tasklist_borderwidth', 1),
font=Config.get('tasklist_font', 'Arial'),
fontsize=Config.get('tasklist_fontsize', 15),
highlight_method=Config.get('tasklist_highlight_method', 'border'),
max_title_width=Config.get('tasklist_max_title_width', 200),
rounded=Config.get('tasklist_rounded', True),
urgent_alert_method=Config.get('tasklist_urgent_alert_method', 'border'),
urgent_border=Config.get('tasklist_urgent_border', '#ff0000'),
margin=Config.get('margin_groupbox', 0)
),
utils.ThermalSensorWidget(
font=Config.get('font_topbar', 'Arial'),
fontsize=Config.get('fontsize_topbar', 16),
foreground=Config.get('thermal_colour', '#ffffff'),
foreground_alert=Config.get('thermal_colour_alert', '#ff0000'),
tag_sensor=Config.get('thermal_sensor', 'temp1'),
chip=Config.get('thermal_chip', None),
threshold=Config.get('thermal_threshold', 70)
),
widget.CPUGraph( widget.CPUGraph(
width=25, width=Config.get('cpu_width', 25),
border_color="#000000", border_color=Config.get('cpu_border_colour', "#000000"),
border_width=0, graph_color=Config.get('cpu_graph_colour', "#00ffff"),
line_width=1, border_width=Config.get('cpu_graph_width', 0),
samples=10, line_width=Config.get('cpu_line_width', 1),
samples=Config.get('cpu_samples', 10),
), ),
widget.MemoryGraph(
width=Config.get('mem_width', 25),
border_color=Config.get('mem_border_colour', "#000000"),
graph_color=Config.get('mem_graph_colour', "#00ffff"),
border_width=Config.get('mem_graph_width', 0),
line_width=Config.get('mem_line_width', 1),
samples=Config.get('mem_samples', 10),
),
widget.HDDBusyGraph(
width=Config.get('hdd_width', 25),
border_color=Config.get('hdd_border_colour', "#000000"),
graph_color=Config.get('hdd_graph_colour', "#00ffff"),
border_width=Config.get('hdd_border_width', 0),
line_width=Config.get('hdd_line_width', 1),
samples=Config.get('hdd_samples', 10),
),
widget.NetGraph(
width=Config.get('net_width', 25),
border_color=Config.get('net_border_colour', "#000000"),
graph_color=Config.get('net_graph_colour', "#00ffff"),
border_width=Config.get('net_border_width', 0),
line_width=Config.get('net_line_width', 1),
samples=Config.get('net_samples', 10),
),
widget.BatteryIcon( widget.BatteryIcon(
battery_name=Config.get('battery_name', 'BAT0'), battery_name=Config.get('battery_name', 'BAT0'),
energy_full_file=Config.get('battery_energy_full_file', 'charge_full'), energy_full_file=Config.get('battery_energy_full_file', 'charge_full'),
energy_now_file=Config.get('battery_energy_now_file', 'charge_now'), 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'), 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) update_delay=Config.get('battery_update_delay', 30)
), ),
utils.WifiIconWidget(
interface=Config.get('wifi_interface', 'wlp4s0'),
theme_path=Config.get('wifi_theme_path', '/home/docs/checkouts/readthedocs.org/user_builds/qtile'
'/checkouts/latest/libqtile/resources/battery-icons'),
update_interval=Config.get('wifi_update_interval', 30)
),
utils.PulseVolumeWidget(
cardid=Config.get('volume_cardid', None),
channel=Config.get('volume_channel', 'Master'),
device=Config.get('volume_device', None),
font=Config.get('volume_font', 'Arial'),
fontsize=Config.get('volume_fontsize', 15),
foreground=Config.get('volume_foreground', '#ffffff'),
get_volume_command=Config.get('volume_get_command', None),
mute_command=Config.get('volume_mute_command', None),
theme_path=Config.get('volume_theme_path', '/home/docs/checkouts/readthedocs.org/user_builds/qtile'
'/checkouts/latest/libqtile/resources/volume-icons'),
volume_down_command=Config.get('volume_down_command', None),
volume_up_command=Config.get('volume_up_command', None),
is_bluetooth_icon=Config.get('volume_is_bluetooth_icon', False),
update_interval=Config.get('volume_update_interval', 0.2)
),
utils.PulseVolumeWidget(
cardid=Config.get('bluevol_cardid', None),
channel=Config.get('bluevol_channel', 'Master'),
device=Config.get('bluevol_device', None),
font=Config.get('bluevol_font', 'Arial'),
fontsize=Config.get('bluevol_fontsize', 15),
foreground=Config.get('bluevol_foreground', '#ffffff'),
get_volume_command=Config.get('bluevol_get_command', None),
mute_command=Config.get('bluevol_mute_command', None),
theme_path=Config.get('bluevol_theme_path', '/home/docs/checkouts/readthedocs.org/user_builds/qtile'
'/checkouts/latest/libqtile/resources/volume-icons'),
volume_down_command=Config.get('bluevol_down_command', None),
volume_up_command=Config.get('bluevol_up_command', None),
is_bluetooth_icon=Config.get('bluevol_is_bluetooth_icon', False),
update_interval=Config.get('bluevol_update_interval', 0.2)
)
]) ])
# Systray only on first screen # Systray only on first screen
@ -152,7 +263,7 @@ class Kuro(BaseTheme):
widgets.append(widget.Systray(**self.widget_defaults)) widgets.append(widget.Systray(**self.widget_defaults))
widgets.extend([ widgets.extend([
utils.bar_separator(Config), widget.CurrentLayoutIcon(),
widget.Clock(format="%a %d %b, %H:%M", **self.widget_defaults), widget.Clock(format="%a %d %b, %H:%M", **self.widget_defaults),
utils.CheckUpdatesYaourt( utils.CheckUpdatesYaourt(
colour_no_updates=Config.get('updates_colour_none', '#ffffff'), colour_no_updates=Config.get('updates_colour_none', '#ffffff'),
@ -168,13 +279,27 @@ class Kuro(BaseTheme):
size=Config.get('height_groupbox', 30) size=Config.get('height_groupbox', 30)
))) )))
# Add debug bars on each window if debugging is enabled
if Config.get('debug', False):
for x in range(num_screens):
widgets = []
widgets.extend([
widget.TextBox(" Debugging bar ", name="default", **self.widget_defaults),
widget.Notify(),
widget.DebugInfo()
])
screens[x].bottom = bar.Bar(
widgets=widgets,
size=Config.get('height_debugbar', 30)
)
return screens return screens
def init_mouse(self): def init_mouse(self):
log.debug("Initializing mouse") log.debug("Initializing mouse")
# Drag floating layouts. # Drag floating layouts.
return [ mouse = [
Drag([self.mod], "Button1", lazy.window.set_position_floating(), Drag([self.mod], "Button1", lazy.window.set_position_floating(),
start=lazy.window.get_position()), start=lazy.window.get_position()),
Drag([self.mod], "Button3", lazy.window.set_size_floating(), Drag([self.mod], "Button3", lazy.window.set_size_floating(),
@ -182,6 +307,8 @@ class Kuro(BaseTheme):
Click([self.mod], "Button2", lazy.window.bring_to_front()) Click([self.mod], "Button2", lazy.window.bring_to_front())
] ]
return mouse
def update_keys(self): def update_keys(self):
log.debug("Updating keys") log.debug("Updating keys")
@ -196,6 +323,30 @@ class Kuro(BaseTheme):
Key([self.mod, "shift"], str(i + 1), lazy.window.togroup(g.name)) Key([self.mod, "shift"], str(i + 1), lazy.window.togroup(g.name))
) )
# Keys for the Wmii layout
self.keys.extend([
Key(
[self.mod, "shift", "control"], "l",
lazy.layout.grow_right()
),
Key(
[self.mod, "shift"], "l",
lazy.layout.shuffle_right()
),
Key(
[self.mod, "shift", "control"], "h",
lazy.layout.grow_left()
),
Key(
[self.mod, "shift"], "h",
lazy.layout.shuffle_left()
),
Key(
[self.mod], "s",
lazy.layout.toggle_split()
)
])
def callback_startup(self): def callback_startup(self):
utils.execute("sleep 3") utils.execute("sleep 3")
utils.execute_once("nitrogen --restore") utils.execute_once("nitrogen --restore")

View file

@ -1,7 +1,27 @@
import os
import re import re
import subprocess import subprocess
import cairocffi
import notify2
from libqtile import widget, bar from libqtile import widget, bar
from libqtile.utils import catch_exception_and_warn, UnixCommandNotFound
from libqtile.widget import base
from libqtile.widget.battery import default_icon_path
from libqtile.widget.check_updates import CheckUpdates from libqtile.widget.check_updates import CheckUpdates
from libqtile.widget.image import Image
from libqtile.widget.sensors import ThermalSensor
from libqtile.widget.volume import Volume
from libqtile.widget.wlan import get_status
from libqtile.log_utils import logger
from notify2 import Notification, URGENCY_NORMAL
notify2.init("QTileWM")
BUTTON_LEFT = 1
BUTTON_MIDDLE = 2
BUTTON_RIGHT = 3
BUTTON_SCROLL_UP = 4
BUTTON_SCROLL_DOWN = 5
def is_running(process): def is_running(process):
@ -41,10 +61,355 @@ def bar_separator(config):
) )
def notify(title, content, urgency=URGENCY_NORMAL, timeout=5000, image=None):
if image is not None:
notification = Notification(
summary=title, message=content,
icon=image
)
else:
notification = Notification(
summary=title, message=content
)
notification.set_timeout(timeout)
notification.set_urgency(urgency)
return notification.show()
def bluetooth_audio_sink():
try:
output = subprocess.check_output("pamixer --list-sinks".split()).decode("utf-8")
output = [x for x in output.split('\n') if "blue" in x.lower()]
except subprocess.CalledProcessError:
return -1
sink = -1
try:
sink = int(output[0].split()[0])
except IndexError:
pass
except AttributeError:
pass
except ValueError:
pass
return sink
def bluetooth_audio_connected():
return bluetooth_audio_sink() != -1
class AppLauncherIcon(Image):
def button_press(self, x, y, button):
if button == BUTTON_LEFT:
execute("dmenu_run -i -p '»' -nb '#000000' -fn 'Noto Sans-11' -nf '#777777' -sb '#1793d0' -sf '#ffffff'")
class CheckUpdatesYaourt(CheckUpdates): class CheckUpdatesYaourt(CheckUpdates):
def __init__(self, **config): def __init__(self, **config):
super(CheckUpdatesYaourt, self).__init__(**config) super(CheckUpdatesYaourt, self).__init__(**config)
# Override command and output with yaourt command # Override command and output with yaourt command
self.cmd = "yaourt -Qua".split() self.cmd = "yaourt -Qua".split()
self.status_cmd = "yaourt -Qua".split()
self.update_cmd = "yaourt -Sy"
self.subtr = 0 self.subtr = 0
def _check_updates(self):
subprocess.check_output(self.update_cmd)
super(CheckUpdatesYaourt, self)._check_updates()
def button_press(self, x, y, button):
if button == BUTTON_LEFT:
output = subprocess.check_output(self.status_cmd).decode('utf-8').split('\n')
num_updates = len(output)-1
msg = "{} updates available.".format(num_updates)
if num_updates > 0:
msg += "\n\n"
for x in range(min(num_updates, 9)):
msg += output[x] + "\n"
if num_updates > 9:
msg += "and {} more...".format(num_updates-9)
notify(
"System updates",
msg
)
elif button == BUTTON_MIDDLE and self.execute is not None:
subprocess.Popen(self.execute, shell=True)
class PulseVolumeWidget(Volume):
defaults = [
("cardid", None, "Card Id"),
("device", "default", "Device Name"),
("channel", "Master", "Channel"),
("padding", 3, "Padding left and right. Calculated if None."),
("theme_path", None, "Path of the icons"),
("update_interval", 0.2, "Update time in seconds."),
("emoji", False, "Use emoji to display volume states, only if ``theme_path`` is not set."
"The specified font needs to contain the correct unicode characters."),
("mute_command", None, "Mute command"),
("volume_up_command", None, "Volume up command"),
("volume_down_command", None, "Volume down command"),
("get_volume_command", None, "Command to get the current volume"),
("is_bluetooth_icon", False, "Is this icon for a Bluetooth Audio device?"),
]
_old_length = 0
def __init__(self, **config):
super(PulseVolumeWidget, self).__init__(**config)
self._old_length = self._length
# Augment commands with bluetooth sink ID if this is a bluetooth icon
if self.is_bluetooth_icon and bluetooth_audio_connected():
bsink = bluetooth_audio_sink()
self.mute_command = " ".join(self._user_config['mute_command']).format(bsink=bsink).split()
self.volume_up_command = " ".join(self._user_config['volume_up_command']).format(bsink=bsink).split()
self.volume_down_command = " ".join(self._user_config['volume_down_command']).format(bsink=bsink).split()
self.get_volume_command = " ".join(self._user_config['get_volume_command']).format(bsink=bsink).split()
logger.info("Updated bluetooth commands with bluetooth sink {}".format(bsink))
self._length = self._old_length
self.commands_need_reset = False
elif self.is_bluetooth_icon:
self.commands_need_reset = True
else:
self.commands_need_reset = False
self._old_length = self._length
def reset_bluetooth_commands(self):
if self.is_bluetooth_icon and bluetooth_audio_connected():
bsink = 0 if bluetooth_audio_sink() == -1 else bluetooth_audio_sink()
self.mute_command = " ".join(self._user_config['mute_command']).format(bsink=bsink).split()
self.volume_up_command = " ".join(self._user_config['volume_up_command']).format(bsink=bsink).split()
self.volume_down_command = " ".join(self._user_config['volume_down_command']).format(bsink=bsink).split()
self.get_volume_command = " ".join(self._user_config['get_volume_command']).format(bsink=bsink).split()
logger.info("Updated bluetooth commands with bluetooth sink {}".format(bsink))
self._length = self._old_length
self.commands_need_reset = False
def get_volume(self):
try:
get_volume_cmd = "echo 0".split()
if self.get_volume_command:
if self.is_bluetooth_icon and bluetooth_audio_sink() == -1:
pass
else:
get_volume_cmd = self.get_volume_command
mixer_out = self.call_process(get_volume_cmd)
except subprocess.CalledProcessError:
return -1
try:
return int(mixer_out.strip())
except ValueError:
return -1
def _update_drawer(self):
super(PulseVolumeWidget, self)._update_drawer()
self.text = ""
if self.is_bluetooth_icon and not bluetooth_audio_connected():
self._length = 0
def draw(self):
if self.is_bluetooth_icon and not bluetooth_audio_connected():
if not self.commands_need_reset:
logger.info("Bluetooth device disconnected. Hiding bluetooth audio mixer")
self.commands_need_reset = True
base._TextBox.draw(self)
else:
if self.commands_need_reset:
self.reset_bluetooth_commands()
if self.theme_path:
self.drawer.draw(offsetx=self.offset, width=self.length)
else:
base._TextBox.draw(self)
def button_press(self, x, y, button):
if button == BUTTON_LEFT:
volume = self.get_volume()
width = 15
if volume >= 0:
volume_amount = round((volume/100)*width)
else:
volume_amount = 0
msg = "[{}{}]".format(
"".join(["#" for x in range(volume_amount)]),
"".join(["-" for x in range(width-volume_amount)])
)
notify(
"{}Volume : {}%".format("Bluetooth " if self.is_bluetooth_icon else "", volume),
msg
)
else:
super(PulseVolumeWidget, self).button_press(x, y, button)
class WifiIconWidget(base._TextBox):
"""WiFi connection strength indicator widget."""
orientations = base.ORIENTATION_HORIZONTAL
defaults = [
('interface', 'wlan0', 'The interface to monitor'),
('update_interval', 1, 'The update interval.'),
('theme_path', default_icon_path(), 'Path of the icons'),
('custom_icons', {}, 'dict containing key->filename icon map'),
]
def __init__(self, **config):
super(WifiIconWidget, self).__init__("WLAN", bar.CALCULATED, **config)
self.add_defaults(WifiIconWidget.defaults)
if self.theme_path:
self.length_type = bar.STATIC
self.length = 0
self.surfaces = {}
self.current_icon = 'wireless-disconnected'
self.icons = dict([(x, '{0}.png'.format(x)) for x in (
'wireless-disconnected',
'wireless-none',
'wireless-low',
'wireless-medium',
'wireless-high',
'wireless-full',
)])
self.icons.update(self.custom_icons)
def _get_info(self):
try:
essid, quality = get_status(self.interface)
disconnected = essid is None
if disconnected:
return self.disconnected_message
return {
'error': False,
'essid': essid,
'quality': quality,
'percent': (quality / 70)
}
except EnvironmentError:
logger.error(
'%s: Probably your wlan device is switched off or '
' otherwise not present in your system.',
self.__class__.__name__)
return {'error': True}
def timer_setup(self):
self.update()
self.timeout_add(self.update_interval, self.timer_setup)
def _configure(self, qtile, bar):
super(WifiIconWidget, self)._configure(qtile, bar)
self.setup_images()
def _get_icon_key(self):
key = 'wireless'
info = self._get_info()
if info is False or info.get('error'):
key += '-none'
elif info.get('essid') is None:
key += '-disconnected'
else:
percent = info['percent']
if percent < 0.2:
key += '-low'
elif percent < 0.4:
key += '-medium'
elif percent < 0.8:
key += '-high'
else:
key += '-full'
return key
def update(self):
icon = self._get_icon_key()
if icon != self.current_icon:
self.current_icon = icon
self.draw()
def draw(self):
if self.theme_path:
self.drawer.clear(self.background or self.bar.background)
self.drawer.ctx.set_source(self.surfaces[self.current_icon])
self.drawer.ctx.paint()
self.drawer.draw(offsetx=self.offset, width=self.length)
else:
self.text = self.current_icon[8:]
base._TextBox.draw(self)
def setup_images(self):
for key, name in self.icons.items():
try:
path = os.path.join(self.theme_path, name)
img = cairocffi.ImageSurface.create_from_png(path)
except cairocffi.Error:
self.theme_path = None
logger.warning('Wireless Icon switching to text mode')
return
input_width = img.get_width()
input_height = img.get_height()
sp = input_height / (self.bar.height - 1)
width = input_width / sp
if width > self.length:
# cast to `int` only after handling all potentially-float values
self.length = int(width + self.actual_padding * 2)
imgpat = cairocffi.SurfacePattern(img)
scaler = cairocffi.Matrix()
scaler.scale(sp, sp)
scaler.translate(self.actual_padding * -1, 0)
imgpat.set_matrix(scaler)
imgpat.set_filter(cairocffi.FILTER_BEST)
self.surfaces[key] = imgpat
class ThermalSensorWidget(ThermalSensor):
defaults = [
('metric', True, 'True to use metric/C, False to use imperial/F'),
('show_tag', False, 'Show tag sensor'),
('update_interval', 2, 'Update interval in seconds'),
('tag_sensor', None,
'Tag of the temperature sensor. For example: "temp1" or "Core 0"'),
('chip', None, 'Chip argument for sensors command'),
(
'threshold',
70,
'If the current temperature value is above, '
'then change to foreground_alert colour'
),
('foreground_alert', 'ff0000', 'Foreground colour alert'),
]
@catch_exception_and_warn(warning=UnixCommandNotFound, excepts=OSError)
def get_temp_sensors(self):
"""calls the unix `sensors` command with `-f` flag if user has specified that
the output should be read in Fahrenheit.
"""
command = ["sensors", ]
if self.chip:
command.append(self.chip)
if not self.metric:
command.append("-f")
sensors_out = self.call_process(command)
return self._format_sensors_output(sensors_out)