Multiple changes:

- Wrap topbar in own class for customization purposes.
- Add stub methods for popup creation
- Add method and shortcut to display the WM class in a notification
- Add keyboard backlight control module, to control my keyboard backlight based on which app has focus
- Add debugging bar to easily display debugging messages
- Add display brightness shortcut keys
- Move some global vars into the theme class
- Lower update intervals for widgets to lower CPU usage
- Add debugging configuration for use with Xephyr
This commit is contained in:
Kevin Alberts 2017-08-26 16:54:45 +02:00
parent 19de16c8b7
commit b9224b667d
8 changed files with 755 additions and 30 deletions

View file

@ -109,3 +109,6 @@ def main(qtile):
qtile.cmd_info() qtile.cmd_info()
else: else:
qtile.cmd_warning() qtile.cmd_warning()
# Save qtile instance in theme
Theme.qtile = qtile

191
config_debug.py Normal file
View file

@ -0,0 +1,191 @@
# 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.
from libqtile.config import Key, Screen, Group, Drag, Click
from libqtile.command import lazy
from libqtile import layout, bar, widget
class KuroTopBar(bar.Bar):
def __init__(self, widgets, size, **config):
super(KuroTopBar, self).__init__(widgets, size, **config)
def _configure(self, qtile, screen):
super(KuroTopBar, self)._configure(qtile, screen)
self.window.handle_EnterNotify = self.handle_enter_notify
self.window.handle_LeaveNotify = self.handle_leave_notify
def handle_enter_notify(self, e):
self.window.opacity = 1.0
print("Bar Hover Enter")
try:
hovered_widget = [x for x in self.widgets if (x.offsetx + x.width) >= e.event_x][0]
except IndexError:
hovered_widget = None
if hasattr(hovered_widget, "handle_hover_enter"):
hovered_widget.handle_hover_enter(e)
self.draw()
def handle_leave_notify(self, e):
self.window.opacity = 0.6
print("Bar Hover Leave")
try:
hovered_widget = [x for x in self.widgets if (x.offsetx + x.width) >= e.event_x][0]
except IndexError:
hovered_widget = None
if hasattr(hovered_widget, "handle_hover_leave"):
hovered_widget.handle_hover_leave(e)
self.draw()
mod = "mod4"
keys = [
# Switch between windows in current stack pane
Key([mod], "k", lazy.layout.down()),
Key([mod], "j", lazy.layout.up()),
# Move windows up or down in current stack
Key([mod, "control"], "k", lazy.layout.shuffle_down()),
Key([mod, "control"], "j", lazy.layout.shuffle_up()),
# Switch window focus to other pane(s) of stack
Key([mod], "space", lazy.layout.next()),
# Swap panes of split stack
Key([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([mod, "shift"], "Return", lazy.layout.toggle_split()),
Key([mod], "Return", lazy.spawn("xterm")),
# Toggle between different layouts as defined below
Key([mod], "Tab", lazy.next_layout()),
Key([mod], "w", lazy.window.kill()),
Key([mod, "control"], "r", lazy.restart()),
Key([mod, "control"], "q", lazy.shutdown()),
Key([mod], "r", lazy.spawncmd()),
]
groups = [Group(i) for i in "asdfuiop"]
for i in groups:
keys.extend([
# mod1 + letter of group = switch to group
Key([mod], i.name, lazy.group[i.name].toscreen()),
# mod1 + shift + letter of group = switch to & move focused window to group
Key([mod, "shift"], i.name, lazy.window.togroup(i.name)),
])
layouts = [
layout.Max(),
layout.Stack(num_stacks=2)
]
widget_defaults = dict(
font='sans',
fontsize=12,
padding=3,
)
extension_defaults = widget_defaults.copy()
widgets = [
widget.GroupBox(),
widget.Prompt(),
widget.WindowName(),
widget.TextBox("default config", name="default"),
widget.Systray(),
widget.Clock(format='%Y-%m-%d %a %I:%M %p'),
]
topbar = KuroTopBar(
background='#000000',
opacity=0.6,
widgets=widgets,
size=24
)
screens = [
Screen(top=topbar),
]
# Drag floating layouts.
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 = []
main = None
follow_mouse_focus = True
bring_front_click = False
cursor_warp = False
floating_layout = 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
focus_on_window_activation = "smart"
# 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"

View file

@ -19,6 +19,7 @@ class BaseTheme:
layouts = None layouts = None
widget_defaults = None widget_defaults = None
screens = None screens = None
qtile = None
# 'Static' variables # 'Static' variables
dgroups_key_binder = None dgroups_key_binder = None
@ -55,6 +56,9 @@ class BaseTheme:
# #
# We choose LG3D to maximize irony: it is a 3D non-reparenting WM written in # We choose LG3D to maximize irony: it is a 3D non-reparenting WM written in
# java that happens to be on java's whitelist. # java that happens to be on java's whitelist.
#
# Alternatively, you could add this to .xinitrc:
# 'export _JAVA_AWT_WM_NONREPARENTING=1'
wmname = "LG3D" wmname = "LG3D"
def initialize(self): def initialize(self):

View file

@ -10,6 +10,8 @@ class Config(BaseConfig):
# 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'" app_launcher = "dmenu_run -i -p '»' -nb '#000000' -fn 'Noto Sans-11' -nf '#777777' -sb '#1793d0' -sf '#ffffff'"
cmd_brightness_up = "sudo /usr/bin/xbacklight -inc 10"
cmd_brightness_down = "sudo /usr/bin/xbacklight -dec 10"
# Images # Images
desktop_bg = "/home/kevin/Pictures/wallpapers/desktop.png" desktop_bg = "/home/kevin/Pictures/wallpapers/desktop.png"
@ -29,7 +31,7 @@ class Config(BaseConfig):
# Sizes # Sizes
width_border = 1 width_border = 1
margin_layout = 4 margin_layout = 8
width_spacer = 1 width_spacer = 1
padding_spacer = 4 padding_spacer = 4
grow_amount = 5 grow_amount = 5
@ -41,6 +43,11 @@ class Config(BaseConfig):
colour_border_urgent = "#774400" colour_border_urgent = "#774400"
colour_spacer_background = "#777777" colour_spacer_background = "#777777"
# Bar variables
bar_background = "#000000"
bar_opacity = 0.65
bar_hover_opacity = 1
# Groupbox variables # Groupbox variables
font_groupbox = "FontAwesome" font_groupbox = "FontAwesome"
fontsize_groupbox = 15 fontsize_groupbox = 15

View file

@ -3,10 +3,12 @@ from libqtile.command import lazy
from libqtile import layout, bar, widget from libqtile import layout, bar, widget
# Import theme util functions # Import theme util functions
from kuro import utils from kuro.utils import general as utils
# Import variables # Import variables
from kuro.base import BaseTheme from kuro.base import BaseTheme
from kuro.utils.general import display_wm_class
from kuro.utils.kb_backlight import handle_focus_change as kb_handle_focus_change
try: try:
from kuro.config import Config from kuro.config import Config
@ -18,7 +20,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
from libqtile.log_utils import logger as log from libqtile.log_utils import logger
class Kuro(BaseTheme): class Kuro(BaseTheme):
@ -27,17 +29,46 @@ class Kuro(BaseTheme):
# Show debug messages # Show debug messages
debug = Config.get('debug', False) debug = Config.get('debug', False)
debug_textfields = []
debug_bars = []
# Screen count
num_screens = 0
# Top bars
topbars = []
# Window manager name
wmname = "QTile"
def set_debug_text(self, text):
for field in self.debug_textfields:
field.text = text
for bar in self.debug_bars:
bar.draw()
def log_debug(self, text):
if Config.get('verbose', False):
self.set_debug_text(text)
logger.debug(text)
def log_info(self, text):
self.set_debug_text(text)
logger.info(text)
def initialize(self): def initialize(self):
log.debug("Initializing Kuro Theme...") self.log_debug("Initializing Kuro Theme...")
super(Kuro, self).initialize() super(Kuro, self).initialize()
self.update()
def update(self):
# Update keys with keys for groups and layouts # Update keys with keys for groups and layouts
self.update_keys() self.update_keys()
def init_keys(self): def init_keys(self):
log.debug("Initializing keys") self.log_debug("Initializing keys")
return [ return [
# Switch between windows in current stack pane # Switch between windows in current stack pane
@ -76,23 +107,42 @@ class Kuro(BaseTheme):
Key([self.mod, "shift"], "r", lazy.spawncmd()), Key([self.mod, "shift"], "r", lazy.spawncmd()),
# Backlight keys
Key([], "XF86MonBrightnessUp", lazy.spawn(Config.get('cmd_brightness_up', 'xbacklight -inc 10'))),
Key([], "XF86MonBrightnessDown", lazy.spawn(Config.get('cmd_brightness_down', 'xbacklight -dec 10'))),
# 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()),
# Kill the current window
Key([self.mod], "w", lazy.window.kill()), Key([self.mod], "w", lazy.window.kill()),
# Restart QTile
Key([self.mod, "control"], "r", lazy.restart()), Key([self.mod, "control"], "r", lazy.restart()),
# Redraw the top bar
Key([self.mod, "shift", "control"], "r", lazy.function(self.redraw_bar)),
# Shutdown QTile
Key([self.mod, "control"], "q", lazy.shutdown()), Key([self.mod, "control"], "q", lazy.shutdown()),
# Key([self.mod, "shift"], "e", self.evaluate()),
##
# Debug keyboard shortcuts
##
Key([self.mod, "control"], "w", lazy.function(display_wm_class))
] ]
def init_groups(self): def init_groups(self):
log.debug("Initializing groups") self.log_debug("Initializing groups")
# http://fontawesome.io/cheatsheet # http://fontawesome.io/cheatsheet
return [Group(i) for i in ""] return [Group(i) for i in ""]
def init_layouts(self): def init_layouts(self):
log.debug("Initializing layouts") self.log_debug("Initializing layouts")
return [ return [
layout.Wmii( layout.Wmii(
@ -112,7 +162,7 @@ class Kuro(BaseTheme):
] ]
def init_widget_defaults(self): def init_widget_defaults(self):
log.debug("Initializing widget_defaults") self.log_debug("Initializing widget_defaults")
return { return {
"font": Config.get('font_topbar', "Sans"), "font": Config.get('font_topbar', "Sans"),
@ -121,20 +171,17 @@ class Kuro(BaseTheme):
} }
def init_screens(self): def init_screens(self):
log.debug("Initializing screens") self.log_debug("Initializing screens")
num_screens = utils.get_screen_count() self.num_screens = utils.get_screen_count()
if num_screens == 0: if self.num_screens == 0:
num_screens = 1 self.num_screens = 1
screens = [] screens = []
for x in range(num_screens): for x in range(self.num_screens):
self.log_info("Initializing bars for screen {}".format(x))
widgets = [] widgets = []
widgets.extend([ widgets.extend([
utils.AppLauncherIcon(
filename=Config.get('applauncher_image', 'apps.png')
),
utils.bar_separator(Config),
widget.GroupBox( widget.GroupBox(
active=Config.get('colour_groupbox_icon_active', '#ffffff'), active=Config.get('colour_groupbox_icon_active', '#ffffff'),
borderwidth=Config.get('width_groupbox_border', 1), borderwidth=Config.get('width_groupbox_border', 1),
@ -148,7 +195,6 @@ class Kuro(BaseTheme):
this_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) margin=Config.get('margin_groupbox', 0)
), ),
utils.bar_separator(Config),
widget.Prompt(**self.widget_defaults), widget.Prompt(**self.widget_defaults),
widget.TaskList( widget.TaskList(
@ -171,7 +217,8 @@ class Kuro(BaseTheme):
foreground_alert=Config.get('thermal_colour_alert', '#ff0000'), foreground_alert=Config.get('thermal_colour_alert', '#ff0000'),
tag_sensor=Config.get('thermal_sensor', 'temp1'), tag_sensor=Config.get('thermal_sensor', 'temp1'),
chip=Config.get('thermal_chip', None), chip=Config.get('thermal_chip', None),
threshold=Config.get('thermal_threshold', 70) threshold=Config.get('thermal_threshold', 70),
update_interval=5,
), ),
widget.CPUGraph( widget.CPUGraph(
@ -181,6 +228,7 @@ class Kuro(BaseTheme):
border_width=Config.get('cpu_graph_width', 0), border_width=Config.get('cpu_graph_width', 0),
line_width=Config.get('cpu_line_width', 1), line_width=Config.get('cpu_line_width', 1),
samples=Config.get('cpu_samples', 10), samples=Config.get('cpu_samples', 10),
frequency=2,
), ),
widget.MemoryGraph( widget.MemoryGraph(
@ -190,6 +238,7 @@ class Kuro(BaseTheme):
border_width=Config.get('mem_graph_width', 0), border_width=Config.get('mem_graph_width', 0),
line_width=Config.get('mem_line_width', 1), line_width=Config.get('mem_line_width', 1),
samples=Config.get('mem_samples', 10), samples=Config.get('mem_samples', 10),
frequency=2,
), ),
widget.HDDBusyGraph( widget.HDDBusyGraph(
@ -199,6 +248,7 @@ class Kuro(BaseTheme):
border_width=Config.get('hdd_border_width', 0), border_width=Config.get('hdd_border_width', 0),
line_width=Config.get('hdd_line_width', 1), line_width=Config.get('hdd_line_width', 1),
samples=Config.get('hdd_samples', 10), samples=Config.get('hdd_samples', 10),
frequency=2,
), ),
widget.NetGraph( widget.NetGraph(
@ -208,6 +258,7 @@ class Kuro(BaseTheme):
border_width=Config.get('net_border_width', 0), border_width=Config.get('net_border_width', 0),
line_width=Config.get('net_line_width', 1), line_width=Config.get('net_line_width', 1),
samples=Config.get('net_samples', 10), samples=Config.get('net_samples', 10),
frequency=2,
), ),
widget.BatteryIcon( widget.BatteryIcon(
@ -277,29 +328,40 @@ class Kuro(BaseTheme):
), ),
widget.TextBox("#{}".format(x), name="default", **self.widget_defaults), widget.TextBox("#{}".format(x), name="default", **self.widget_defaults),
]) ])
screens.append(Screen(top=bar.Bar(
topbar = utils.KuroTopBar(
theme=self,
background=Config.get('bar_background', '#000000'),
opacity=Config.get('bar_opacity', 1.0),
widgets=widgets, widgets=widgets,
size=Config.get('height_groupbox', 30) size=Config.get('height_groupbox', 30)
))) )
self.topbars.append(topbar)
screens.append(Screen(top=topbar))
# Add debug bars on each window if debugging is enabled # Add debug bars on each window if debugging is enabled
if Config.get('debug', False): if Config.get('debug', False):
for x in range(num_screens): self.debug_textfields = []
for x in range(self.num_screens):
textfield = widget.TextBox("...", name="debugtext", **self.widget_defaults)
self.debug_textfields.append(textfield)
widgets = [] widgets = []
widgets.extend([ widgets.extend([
widget.TextBox(" Debugging bar ", name="default", **self.widget_defaults), widget.TextBox(" Debugging bar ", name="default", **self.widget_defaults),
widget.Notify(), textfield,
widget.DebugInfo()
]) ])
screens[x].bottom = bar.Bar( screens[x].bottom = bar.Bar(
widgets=widgets, widgets=widgets,
size=Config.get('height_debugbar', 30) size=Config.get('height_debugbar', 30)
) )
self.debug_bars.append(screens[x].bottom)
return screens return screens
def init_mouse(self): def init_mouse(self):
log.debug("Initializing mouse") self.log_debug("Initializing mouse")
# Drag floating layouts. # Drag floating layouts.
mouse = [ mouse = [
@ -313,7 +375,7 @@ class Kuro(BaseTheme):
return mouse return mouse
def update_keys(self): def update_keys(self):
log.debug("Updating keys") self.log_debug("Updating keys")
for i, g in enumerate(self.groups): for i, g in enumerate(self.groups):
# mod1 + number = switch to group # mod1 + number = switch to group
@ -350,6 +412,32 @@ class Kuro(BaseTheme):
) )
]) ])
# Util functions
@staticmethod
def redraw_bar(qtile):
for b in qtile.topbars:
b.draw()
# QTile base callbacks
def callback_startup(self): def callback_startup(self):
utils.execute("sleep 3") utils.execute("sleep 3")
self.log_info("Restoring wallpaper...")
utils.execute_once("nitrogen --restore") utils.execute_once("nitrogen --restore")
#
# display = os.environ['DISPLAY']
#
# if not display:
# display = ":0"
#
# # Start compton for each screen
# for x in range(self.num_screens):
# self.log_info("Launching compton for screen {}.{}".format(display, x))
# utils.execute_once("compton --config ~/.config/compton.conf -b -d {}.{}".format(display, x))
# def callback_screen_change(self, *args, **kwargs):
# self.num_screens = utils.get_screen_count()
# return True
def callback_focus_change(self, *args, **kwargs):
kb_handle_focus_change(self)

0
kuro/utils/__init__.py Normal file
View file

View file

@ -5,6 +5,7 @@ import subprocess
import cairocffi import cairocffi
import notify2 import notify2
from libqtile import widget, bar from libqtile import widget, bar
from libqtile.bar import Bar
from libqtile.utils import catch_exception_and_warn, UnixCommandNotFound from libqtile.utils import catch_exception_and_warn, UnixCommandNotFound
from libqtile.widget import base from libqtile.widget import base
from libqtile.widget.battery import default_icon_path from libqtile.widget.battery import default_icon_path
@ -15,6 +16,7 @@ from libqtile.widget.volume import Volume
from libqtile.widget.wlan import get_status from libqtile.widget.wlan import get_status
from libqtile.log_utils import logger from libqtile.log_utils import logger
from notify2 import Notification, URGENCY_NORMAL from notify2 import Notification, URGENCY_NORMAL
notify2.init("QTileWM") notify2.init("QTileWM")
BUTTON_LEFT = 1 BUTTON_LEFT = 1
@ -77,6 +79,29 @@ def notify(title, content, urgency=URGENCY_NORMAL, timeout=5000, image=None):
return notification.show() return notification.show()
def spawn_popup(qtile, x, y, text):
# Create textwidget for in window
pass
# window.Internal.create(
# qtile, x, y, width, height, opacity=1
# )
def display_wm_class(qtile):
window = qtile.currentWindow if qtile else None
if window:
wm_class = window.window.get_wm_class() or None
name = window.name
if wm_class:
notify(title="WM_Class of {}".format(name),
content="{}".format(wm_class),
urgency=notify2.URGENCY_CRITICAL)
def bluetooth_audio_sink(): def bluetooth_audio_sink():
try: try:
output = subprocess.check_output("pamixer --list-sinks".split()).decode("utf-8") output = subprocess.check_output("pamixer --list-sinks".split()).decode("utf-8")
@ -101,11 +126,61 @@ def bluetooth_audio_connected():
return bluetooth_audio_sink() != -1 return bluetooth_audio_sink() != -1
class KuroTopBar(Bar):
def __init__(self, theme, widgets, size, **config):
self.theme = theme
super(KuroTopBar, self).__init__(widgets, size, **config)
def _configure(self, qtile, screen):
super(KuroTopBar, self)._configure(qtile, screen)
self.window.handle_EnterNotify = self.handle_enter_notify
self.window.handle_LeaveNotify = self.handle_leave_notify
def handle_enter_notify(self, e):
# self.theme.log_debug("Bar HandleEnterNotify")
#
# self.window.opacity = Config.get('bar_hover_opacity', 1.0)
# print("Bar Hover Enter")
#
# try:
# hovered_widget = [x for x in self.widgets if (x.offsetx + x.width) >= e.event_x][0]
# except IndexError:
# hovered_widget = None
#
# self.theme.log_debug("Hovered over {}".format(hovered_widget))
#
# if hasattr(hovered_widget, "handle_hover_enter"):
# hovered_widget.handle_hover_enter(e)
self.draw()
def handle_leave_notify(self, e):
# self.theme.log_debug("Bar HandleLeaveNotify")
#
# self.window.opacity = Config.get('bar_opacity', 1.0)
# print("Bar Hover Leave")
#
# try:
# hovered_widget = [x for x in self.widgets if (x.offsetx + x.width) >= e.event_x][0]
# except IndexError:
# hovered_widget = None
#
# self.theme.log_debug("Hovered over {}".format(hovered_widget))
#
# if hasattr(hovered_widget, "handle_hover_leave"):
# hovered_widget.handle_hover_leave(e)
self.draw()
class AppLauncherIcon(Image): class AppLauncherIcon(Image):
def button_press(self, x, y, button): def button_press(self, x, y, button):
if button == BUTTON_LEFT: if button == BUTTON_LEFT:
execute("dmenu_run -i -p '»' -nb '#000000' -fn 'Noto Sans-11' -nf '#777777' -sb '#1793d0' -sf '#ffffff'") execute("dmenu_run -i -p '»' -nb '#000000' -fn 'Noto Sans-11' -nf '#777777' -sb '#1793d0' -sf '#ffffff'")
def handle_hover(self, event):
spawn_popup(self.qtile, self.offsetx, self.offsety, "Hovered over AppLauncherIcon!")
class CheckUpdatesYaourt(CheckUpdates): class CheckUpdatesYaourt(CheckUpdates):
def __init__(self, **config): def __init__(self, **config):
@ -113,12 +188,13 @@ class CheckUpdatesYaourt(CheckUpdates):
# 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.status_cmd = "yaourt -Qua".split()
self.update_cmd = "yaourt -Sy" self.update_cmd = "sudo yaourt -Sya".split()
self.subtr = 0 self.subtr = 0
def _check_updates(self): def _check_updates(self):
subprocess.check_output(self.update_cmd) #subprocess.check_output(self.update_cmd)
super(CheckUpdatesYaourt, self)._check_updates() res = super(CheckUpdatesYaourt, self)._check_updates()
return res
def button_press(self, x, y, button): def button_press(self, x, y, button):
if button == BUTTON_LEFT: if button == BUTTON_LEFT:

356
kuro/utils/kb_backlight.py Normal file
View file

@ -0,0 +1,356 @@
import subprocess
# Initialize logging
from libqtile.log_utils import logger
class State:
ON = "on"
OFF = "off"
LIST = ["on", "off"]
class Mode:
RANDOM = "random"
CUSTOM = "custom"
BREATHE = "breathe"
CYCLE = "cycle"
WAVE = "wave"
DANCE = "dance"
TEMPO = "tempo"
FLASH = "flash"
LIST = ["random", "custom", "breathe", "cycle", "wave", "dance", "tempo", "flash"]
class Brightness:
LOW = 0
MEDIUM = 1
HIGH = 2
FULL = 3
LIST = [0, 1, 2, 3]
class Side:
LEFT = "left"
MIDDLE = "middle"
RIGHT = "right"
ALL = "all"
LIST = ["left", "middle", "right", "all"]
class Color:
BLACK = "black"
BLUE = "blue"
RED = "red"
MAGENTA = "magenta"
GREEN = "green"
CYAN = "cyan"
YELLOW = "yellow"
WHITE = "white"
LIST = ["black", "blue", "red", "magenta", "green", "cyan", "yellow", "white"]
def handle_focus_change(theme):
qtile = theme.qtile
window = qtile.currentWindow if qtile else None
if window:
wm_class = window.window.get_wm_class() or None
name = window.name
if wm_class:
theme.log_info(str(wm_class))
# Check which window we entered and do some special effects if it is a special window.
# Make keyboard red/white (pink) while in Osu!
if "osu!.exe" in wm_class[0]:
BacklightController.reset_backlight(state=KeyboardState(values={
'brightness': Brightness.FULL,
'left': Color.WHITE,
'middle': Color.RED,
'right': Color.WHITE,
}))
elif "chromium" in wm_class[0]:
BacklightController.reset_backlight(state=KeyboardState(values={
'brightness': Brightness.FULL,
'left': Color.WHITE,
'middle': Color.BLUE,
'right': Color.WHITE,
}))
elif "pycharm" in wm_class[1]:
BacklightController.reset_backlight(state=KeyboardState(values={
'brightness': Brightness.MEDIUM,
'left': Color.WHITE,
'middle': Color.GREEN,
'right': Color.WHITE,
}))
elif "franz" in wm_class[0]:
BacklightController.reset_backlight(state=KeyboardState(values={
'brightness': Brightness.MEDIUM,
'left': Color.BLUE,
'middle': Color.WHITE,
'right': Color.BLUE,
}))
else:
BacklightController.reset_backlight()
class KeyboardState:
_instance = None
state = State.ON
mode = Mode.CUSTOM
brightness = Brightness.LOW
left = Color.WHITE
middle = Color.WHITE
right = Color.WHITE
def __init__(self, values=None):
"""
:param values: Default values
:type values: dict
"""
if values is not None:
keys = values.keys()
if 'state' in keys:
self.state = values['state']
if 'mode' in keys:
self.mode = values['mode']
if 'brightness' in keys:
self.brightness = values['brightness']
if 'left' in keys:
self.left = values['left']
if 'middle' in keys:
self.middle = values['middle']
if 'right' in keys:
self.right = values['right']
def __str__(self):
return "KBState({}, {}, {}, {}, {}, {})".format(
self.state, self.mode, self.brightness, self.left, self.middle, self.right
)
def get_copy(self):
c = KeyboardState()
c.state = self.state
c.mode = self.mode
c.brightness = self.brightness
c.left = self.left
c.middle = self.middle
c.right = self.right
return c
@classmethod
def get_instance(cls):
"""
:rtype: KeyboardState
"""
if cls._instance is None:
cls._instance = KeyboardState()
return cls._instance
class BacklightController:
@staticmethod
def reset_backlight(force=False, state=None):
"""
Resets the keyboard backlight to the default colors / states
:param force: Force the reset
:type force: bool
:param state: A state to reset to
:type state: KeyboardState
"""
if state is None:
# Create state with default values.
state = KeyboardState()
logger.debug("Resetting KB backlight to {}".format(state))
flags = [BacklightController.set_colors([state.left, state.middle, state.right], force),
BacklightController.set_brightness(state.brightness, force),
BacklightController.set_state(state.state, force),
BacklightController.set_mode(state.mode, force)]
BacklightController.exec_flags(flags)
@staticmethod
def exec_flags(flags):
"""
Removes duplicate flags and executes the command with the resulting flags, and
updates the current keyboard state.
:param flags: List of list of flags, to be executed.
:return: The return code of the execution
"""
final_flags = {}
changes = {}
for flag in flags:
for (k, v) in flag:
final_flags[k] = v
if k == "-p":
changes['state'] = v
elif k == "-t":
changes['mode'] = v
elif k == "-b":
changes['brightness'] = v
elif k == "-l":
changes['left'] = v
elif k == "-m":
changes['middle'] = v
elif k == "-r":
changes['right'] = v
elif k == "-c":
changes['left'] = v
changes['middle'] = v
changes['right'] = v
args = []
for (k, v) in final_flags.items():
args.append(k)
args.append(v)
res = BacklightController._call(args)
if res == 0:
# Update state
css = KeyboardState.get_instance()
for (k, v) in changes.items():
css.__setattr__(k, v)
@staticmethod
def set_state(state, force=False):
"""
Turns the backlight on or off
:param state: State you want ('on' or 'off')
:type state: str
:param force: Force execution.
:type force: bool
"""
if state not in State.LIST:
return
logger.debug("Setting KB state to {}".format(state))
css = KeyboardState.get_instance()
if css.state != state or force:
return [('-p', state)]
return []
@staticmethod
def set_mode(mode, force=False):
"""
Set the backlight mode
:param mode: One of "random", "custom", "breathe", "cycle", "wave", "dance", "tempo" or "flash"
:type mode: str
:param force: Force execution.
:type force: bool
"""
if mode not in Mode.LIST:
return
logger.debug("Setting KB mode to {}".format(mode))
css = KeyboardState.get_instance()
if css.mode != mode or force:
return [('-t', mode)]
return []
@staticmethod
def set_brightness(level, force=False):
"""
Set the brightness level
:param level: Brightness (0 to 3)
:type level: int
:param force: Force execution.
:type force: bool
"""
if level not in Brightness.LIST:
return
logger.debug("Setting KB brightness to {}".format(level))
css = KeyboardState.get_instance()
if css.brightness != level or force:
return [('-b', '{}'.format(level))]
return []
@staticmethod
def set_color(side, color, force=False):
"""
Set the backlight color
:param side: Side of backlight to change, from left, middle, right or all.
:type side: str
:param color: The new color, one of "black", "blue", "red", "magenta", "green", "cyan", "yellow" or "white"
:type color: str
:param force: Force execution.
:type force: bool
"""
if side not in Side.LIST:
return
if color not in Color.LIST:
return
logger.debug("Setting KB side {} to color {}".format(side, color))
css = KeyboardState.get_instance()
if side == "all":
if css.left != color or css.right != color or css.right != color or force:
return [('-c', color)]
elif side == "left":
if css.left != color or force:
return [('-l', color)]
elif side == "right":
if css.right != color or force:
return [('-r', color)]
elif side == "middle":
if css.middle != color or force:
return [('-m', color)]
return []
@staticmethod
def set_colors(colors, force=False):
"""
Set the backlight colors in one go
:param colors: The new colors, list of three colors, [left, middle, right]. Colors must be one of
"black", "blue", "red", "magenta", "green", "cyan", "yellow" or "white"
:type colors: list
:param force: Force execution.
:type force: bool
"""
if len(colors) != 3:
return
for color in colors:
if color not in Color.LIST:
return
logger.debug("Setting KB colors to {}, {}, {}".format(colors[0], colors[1], colors[2]))
css = KeyboardState.get_instance()
if css.left != colors[0] or css.middle != colors[1] or css.right != colors[2] or force:
return [('-l', '{}'.format(colors[0])),
('-m', '{}'.format(colors[1])),
('-r', '{}'.format(colors[2]))]
return []
@staticmethod
def _call(args):
"""
Call the script.
:param args: Arguments to the script
:type args: list
:return The exit code of the script
:rtype: int
"""
logger.debug("Calling kb_backlight' with args {}".format(args))
return subprocess.call(["sudo", "/home/kevin/bin/kb_backlight"] + args)