From 5b7475e50f4dff8b9f73d9f6c642cdcd7e3a51ca Mon Sep 17 00:00:00 2001 From: Kevin Alberts Date: Sat, 29 Dec 2018 21:49:27 +0100 Subject: [PATCH] Multiple changes including random wallpaper, theme colors and switching to yay instead of yaourt --- config.py | 4 +- kuro/base.py | 3 - kuro/config.py | 55 ++-- .../layout_icons/layout-kurowmii.png | Bin 0 -> 266 bytes kuro/theme.py | 257 ++++++++++++++- kuro/utils/general.py | 301 +++++++++++++++++- kuro/utils/kb_backlight.py | 35 +- 7 files changed, 609 insertions(+), 46 deletions(-) create mode 100644 kuro/resources/layout_icons/layout-kurowmii.png diff --git a/config.py b/config.py index c2a1cbb..d502141 100644 --- a/config.py +++ b/config.py @@ -65,7 +65,6 @@ 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) @@ -112,3 +111,6 @@ def main(qtile): # Save qtile instance in theme Theme.qtile = qtile + + # Save theme instance in qtile + qtile.theme_instance = Theme diff --git a/kuro/base.py b/kuro/base.py index 2785e17..ee47b1a 100644 --- a/kuro/base.py +++ b/kuro/base.py @@ -183,9 +183,6 @@ class BaseTheme: 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 diff --git a/kuro/config.py b/kuro/config.py index 7af4f55..8eb1f53 100644 --- a/kuro/config.py +++ b/kuro/config.py @@ -7,9 +7,17 @@ class Config(BaseConfig): debug = False verbose = False + # Colors + foreground = "#ffffff" + background = "#000000" + highlight = "#1793d0" + inactive_light = "#777777" + inactive_dark = "#333333" + # Default Applications 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 '" + background + "' -fn 'Noto Sans-11' -nf '" + inactive_light + \ + "' -sb '" + highlight + "' -sf '" + foreground + "'" web_browser = "firefox-developer-edition" file_manager = "thunar" cmd_brightness_up = "sudo /usr/bin/xbacklight -inc 10" @@ -18,7 +26,9 @@ class Config(BaseConfig): # Images desktop_bg = "/home/kevin/Pictures/wallpapers/desktop.png" + desktop_bg_folder = "/home/kevin/Pictures/wallpapers/desktop_rotation" applauncher_image = "/home/kevin/.config/qtile/kuro/resources/arch.png" + custom_layout_icon_paths = ['/home/kevin/.config/qtile/kuro/resources/layout_icons/'] # Fonts font_default = "Noto Sans" @@ -41,14 +51,14 @@ class Config(BaseConfig): width_zoomy_column = 300 # Colours - colour_border_normal = "#333333" - colour_border_focus = "#ffffff" - colour_border_urgent = "#774400" - colour_spacer_background = "#777777" + colour_border_normal = inactive_dark + colour_border_focus = foreground + colour_border_urgent = highlight + colour_spacer_background = inactive_light # Bar variables - bar_background = "#000000" - bar_opacity = 0.65 + bar_background = background + bar_opacity = 0.8 bar_hover_opacity = 1 # Groupbox variables @@ -59,14 +69,14 @@ class Config(BaseConfig): margin_groupbox = 0 bool_groupbox_disable_drag = True bool_groupbox_rounded_borders = True - colour_groupbox_border_normal = "#333333" - colour_groupbox_border_focus = "#aaaaaa" - colour_groupbox_icon_active = "#ffffff" - colour_groupbox_icon_inactive = "#777777" + colour_groupbox_border_normal = inactive_dark + colour_groupbox_border_focus = foreground + colour_groupbox_icon_active = foreground + colour_groupbox_icon_inactive = inactive_light # Tasklist variables - tasklist_border = "#ffffff" - tasklist_urgent_border = "#774400" + tasklist_border = foreground + tasklist_urgent_border = highlight tasklist_font = "Noto Sans" tasklist_fontsize = 11 @@ -97,10 +107,10 @@ class Config(BaseConfig): 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_get_command = "pamixer --sink 2 --get-volume".split() + volume_mute_command = "pamixer --sink 2 -t".split() + volume_up_command = "pamixer --sink 2 -i 2".split() + volume_down_command = "pamixer --sink 2 -d 2".split() volume_is_bluetooth_icon = False volume_update_interval = 0.2 @@ -117,6 +127,15 @@ class Config(BaseConfig): # CheckUpdates variables updates_display_format = "{updates}" - updates_execute_command = "terminator -e 'echo Updating\ via\ yaourt\ -Sayu...; yaourt -Sayu'" + updates_execute_command = "terminator -e 'echo Updating\ via\ yay...; yay'" updates_colour_available = '#f4d742' + # Screen organization + laptop_screen = "eDP-1-1" + + # Keyboard colors + do_keyboard_updates = False + + # Show audio visualizer + show_audio_visualizer = True + diff --git a/kuro/resources/layout_icons/layout-kurowmii.png b/kuro/resources/layout_icons/layout-kurowmii.png new file mode 100644 index 0000000000000000000000000000000000000000..1dc2fb765c25e5b5505390ac8d067eba4d8f026e GIT binary patch literal 266 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=EX7WqAsj$Z!;#VfSxgyVRm&Kn9ExwV=iC-tH@^WWb~d}Lsq3q70E6xV zO`wiDd5o>QM6dZffDANEcc^a{glV>Rgech}dbP0l+XkK^l)I^ literal 0 HcmV?d00001 diff --git a/kuro/theme.py b/kuro/theme.py index 7c00083..8997c06 100644 --- a/kuro/theme.py +++ b/kuro/theme.py @@ -1,3 +1,7 @@ +import json +import os +import random + from libqtile.config import Key, Screen, Group, Drag, Click from libqtile.command import lazy from libqtile import layout, bar, widget @@ -39,6 +43,12 @@ class Kuro(BaseTheme): # Top bars topbars = [] + # Current wallpaper path + current_wallpaper = None + + # Whether or not to perform keyboard backlight updates + do_keyboard_updates = True + # Window manager name wmname = "QTile" @@ -60,6 +70,12 @@ class Kuro(BaseTheme): def initialize(self): self.log_debug("Initializing Kuro Theme...") + # Update color scheme + self.initialize_colorscheme() + + # Set settings + self.do_keyboard_updates = Config.get("do_keyboard_updates", True) + super(Kuro, self).initialize() self.update() @@ -108,7 +124,7 @@ class Kuro(BaseTheme): Key([self.mod], "b", lazy.spawn(Config.get('web_browser', "xterm links"))), # Super-F to start file manager -# Key([self.mod], "f", lazy.spawn(Config.get('file_manager', "thunar"))), +# Key([self.mod], "f", lazy.spawn(Config.get('file_m4anager', "thunar"))), # Super-Shift-R to start spawncmd Key([self.mod, "shift"], "r", lazy.spawncmd()), @@ -132,13 +148,20 @@ class Kuro(BaseTheme): # Shutdown QTile Key([self.mod, "control"], "q", lazy.shutdown()), + # Update wallpaper + Key([self.mod, "control"], "w", lazy.function(self.update_wallpaper)), + # Reload colorscheme + Key([self.mod, "control"], "t", lazy.function(self.update_colorscheme)), + + # Reorganize screens + Key([self.mod, "control"], "s", lazy.function(self.update_screens)), ## # Debug keyboard shortcuts ## - Key([self.mod, "control"], "w", lazy.function(display_wm_class)), + Key([self.mod, "control"], "c", lazy.function(display_wm_class)), # Redraw the top bar Key([self.mod, "shift", "control"], "r", lazy.function(self.redraw_bar)), @@ -153,7 +176,7 @@ class Kuro(BaseTheme): groups = [] # http://fontawesome.io/cheatsheet - groups.append(Group("", spawn=Config.get('web_browser', "xterm links"))) + groups.append(Group("", spawn=Config.get('web_browser', "xterm links"))) groups.append(Group("", spawn=Config.get('app_terminal', "xterm"))) groups.append(Group("")) groups.append(Group("", spawn="franz4-bin")) @@ -161,7 +184,7 @@ class Kuro(BaseTheme): groups.append(Group("", spawn=Config.get('file_manager', "thunar"))) groups.append(Group("", spawn="thunderbird")) groups.append(Group("")) - groups.append(Group("", spawn="qupzilla https://music.kurocon.nl/")) + groups.append(Group("")) groups.append(Group("")) return groups @@ -233,7 +256,23 @@ class Kuro(BaseTheme): urgent_alert_method=Config.get('tasklist_urgent_alert_method', 'border'), urgent_border=Config.get('tasklist_urgent_border', '#ff0000'), margin=Config.get('margin_groupbox', 0) - ), + ) + ]) + + if Config.get('show_audio_visualizer', False): + widgets.append(utils.AudioVisualizerWidget( + graph_color=Config.get('visualizer_graph_color', "#ffffff"), + fill_color=Config.get('visualizer_fill_color', "#ffffff.3"), + border_color=Config.get('visualizer_border_color', "#000000"), + border_width=Config.get('visualizer_graph_width', 0), + line_width=Config.get('visualizer_line_width', 1), + frequency=0.05 + )) + + widgets.extend([ + utils.MediaWidget(), + + utils.SeparatorWidget(), utils.ThermalSensorWidget( font=Config.get('font_topbar', 'Arial'), @@ -342,9 +381,9 @@ class Kuro(BaseTheme): widgets.append(widget.Systray(**self.widget_defaults)) widgets.extend([ - widget.CurrentLayoutIcon(), + utils.KuroCurrentLayoutIcon(custom_icon_paths=Config.get('custom_layout_icon_paths', [])), widget.Clock(format="%a %d %b, %H:%M", **self.widget_defaults), - utils.CheckUpdatesYaourt( + utils.CheckUpdatesYay( 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}'), @@ -442,16 +481,67 @@ class Kuro(BaseTheme): # Util functions @staticmethod def redraw_bar(qtile): - for b in qtile.topbars: - b.draw() + for s in qtile.screens: + s.top.draw() + + @staticmethod + def update_screens(qtile): + out = utils.call_process(["xrandr", "--current"]) + laptop_screen = None + screens = [] + for x in out.split("\n"): + if " connected " in x: + if Config.get("laptop_screen", None) is not None and Config.get("laptop_screen", None) in x: + laptop_screen = x + else: + screens.append(x) + + # Only configure two screens. Open arandr if more screens are present. + if laptop_screen is not None and len(screens) == 1: + laptop = laptop_screen.split()[0] + other = screens[0].split()[0] + utils.call_process(["xrandr", "--output", laptop, "--below", other]) + qtile.cmd_restart() + elif laptop_screen is not None and len(screens) > 1: + utils.execute("arandr") # QTile base callbacks + def callback_startup_once(self, *args, **kwargs): + pass + #Kuro.update_screens(self.qtile) + def callback_startup(self): utils.execute("sleep 3") - self.log_info("Restoring wallpaper...") - utils.execute_once("nitrogen --restore") - # + # self.log_info("Restoring previous wallpaper...") + # utils.execute_once("nitrogen --restore") + self.update_wallpaper(self.qtile) + + if self.current_wallpaper: + utils.execute_once(["wal", "-n", "-i", "{}".format(self.current_wallpaper)]) + else: + + wallpaper = None + if os.path.isfile("/home/kevin/.cache/wal/colors.json"): + with open("/home/kevin/.cache/wal/colors.json", 'r') as f: + try: + wallpaper = json.load(f)['wallpaper'] + except KeyError: + wallpaper = None + if wallpaper: + Kuro.set_wallpaper(self.qtile, wallpaper) + else: + utils.execute_once("nitrogen --restore") + + self.log_info("Starting compositor...") + utils.execute_once("compton -b") + + # Update color scheme + self.initialize_colorscheme() + + # Update color scheme + Kuro.update_colorscheme(self.qtile) + # display = os.environ['DISPLAY'] # # if not display: @@ -467,7 +557,8 @@ class Kuro(BaseTheme): # return True def callback_focus_change(self, *args, **kwargs): - kb_handle_focus_change(self) + if self.do_keyboard_updates: + kb_handle_focus_change(self) initial_windows = [] @@ -487,6 +578,9 @@ class Kuro(BaseTheme): self.callback_client_new() + # Update color scheme + Kuro.update_colorscheme(self.qtile) + def callback_client_new(self, *args, **kwargs): if len(self.initial_windows) > 0: init = self.initial_windows.copy() @@ -501,5 +595,140 @@ class Kuro(BaseTheme): if r.matches(window): self.qtile.dgroups.remove_rule(rid) self.initial_windows.remove((pid, gname)) - self.log_info("Removed group rule for PID {}, window {}".format(pid, window.name)) + self.log_info("Removed group rule for PID {}, window {}".format(pid, + window.name)) self.log_info(str(self.qtile.dgroups.rules_map)) + + @staticmethod + def update_wallpaper(qtile): + wallpapers = [] + wallpaper_dir = Config.get("desktop_bg_folder", "") + try: + wallpapers = os.listdir(wallpaper_dir) + except os.error: + pass + + if wallpapers: + qtile.theme_instance.current_wallpaper = os.path.join(wallpaper_dir, random.choice(wallpapers)) + Kuro.set_wallpaper(qtile, qtile.theme_instance.current_wallpaper) + else: + utils.execute_once("nitrogen --restore") + + + @staticmethod + def set_wallpaper(qtile, filename): + utils.execute_once("wal-nitrogen {}".format(filename)) + qtile.theme_instance.current_wallpaper = filename + Kuro.update_colorscheme(qtile) + + + @staticmethod + def update_mediaclients(*args, **kwargs): + return str(str(args) + " " + str(kwargs)) + + + @staticmethod + def initialize_colorscheme(): + colors = None + if os.path.isfile("/home/kevin/.cache/wal/colors.json"): + with open("/home/kevin/.cache/wal/colors.json", 'r') as f: + try: + colors = json.load(f)['colors'] + except KeyError: + colors = None + + if colors: + # Update Config + Config.foreground = colors['color15'] + Config.background = colors['color0'] + Config.highlight = colors['color3'] + Config.inactive_light = colors['color4'] + Config.inactive_dark = colors['color5'] + + @staticmethod + def update_colorscheme(qtile): + """ + :type qtile: libqtile.manager.Qtile + """ + if qtile.theme_instance.current_wallpaper: + utils.execute(["wal", "-n", "-i", "{}".format(qtile.theme_instance.current_wallpaper)]) + + colors = None + if os.path.isfile("/home/kevin/.cache/wal/colors.json"): + with open("/home/kevin/.cache/wal/colors.json", 'r') as f: + try: + colors = json.load(f)['colors'] + except KeyError: + colors = None + + if colors: + # Update Config + Config.foreground = colors['color15'] + Config.background = colors['color0'] + Config.highlight = colors['color3'] + Config.inactive_light = colors['color4'] + Config.inactive_dark = colors['color5'] + + # Update border colors in layouts + for group in qtile.groups: + for layout in group.layouts: + if isinstance(layout, kuro_layouts.KuroWmii): + layout.border_focus = colors['color15'] + layout.border_focus_stack = colors['color1'] + layout.border_normal = colors['color1'] + layout.border_normal_stack = colors['color1'] + + for screen in qtile.screens: + bar = screen.top + bar.background = colors['color1'] + bar.drawer.clear(bar.background) + for w in bar.widgets: + if hasattr(w, '_update_drawer'): + try: + w._update_drawer() + except Exception as e: + logger.error("Error while updating drawer: {}".format(e)) + + if hasattr(w, 'foreground'): + w.foreground = colors['color15'] + + if hasattr(w, 'foreground_normal'): + w.foreground_normal = colors['color15'] + + if hasattr(w, 'foreground_alert'): + w.foreground_alert = colors['color3'] + + if hasattr(w, 'border'): + w.border = colors['color15'] + + if hasattr(w, 'active'): + w.active = colors['color15'] + + if hasattr(w, 'highlight_color'): + w.highlight_color = colors['color3'] + + if hasattr(w, 'inactive'): + w.inactive = colors['color8'] + + if hasattr(w, 'this_current_screen_border'): + w.this_current_screen_border = colors['color15'] + + if hasattr(w, 'this_screen_border'): + w.this_screen_border = colors['color15'] + + if hasattr(w, 'other_current_screen_border'): + w.other_current_screen_border = colors['color8'] + + if hasattr(w, 'other_screen_border'): + w.other_screen_border = colors['color8'] + + if isinstance(w, utils.AudioVisualizerWidget): + w.graph_color = colors['color15'] + w.fill_color = colors['color8'] + + bar.draw() + + utils.notify( + "Updated colorscheme!", + "active: {}, inactive: {}".format(colors['color15'], colors['color1']) + ) diff --git a/kuro/utils/general.py b/kuro/utils/general.py index 255dc6a..8ab30bb 100644 --- a/kuro/utils/general.py +++ b/kuro/utils/general.py @@ -1,11 +1,18 @@ import os import re import subprocess +from asyncio import Queue +from threading import Thread from time import sleep import cairocffi import notify2 +import numpy +import pyaudio +import six from libqtile import widget, bar +from libqtile.widget.currentlayout import CurrentLayoutIcon +from libqtile.widget.graph import _Graph from libqtile.window import Internal from libqtile.bar import Bar from libqtile.utils import catch_exception_and_warn, UnixCommandNotFound @@ -38,12 +45,34 @@ def is_running(process): def execute(process): - return subprocess.Popen(process.split()) + if isinstance(process, list): + return subprocess.Popen(process) + elif isinstance(process, str): + return subprocess.Popen(process.split()) + else: + pass def execute_once(process): if not is_running(process): - return subprocess.Popen(process.split()) + if isinstance(process, list): + return subprocess.Popen(process) + elif isinstance(process, str): + return subprocess.Popen(process.split()) + else: + pass + + +def call_process(command, **kwargs): + """ + This method uses `subprocess.check_output` to run the given command + and return the string from stdout, which is decoded when using + Python 3. + """ + output = subprocess.check_output(command, **kwargs) + if six.PY3: + output = output.decode() + return output def get_screen_count(): @@ -217,18 +246,18 @@ class AppLauncherIcon(Image): spawn_popup(self.qtile, self.offsetx, self.offsety, "Hovered over AppLauncherIcon!") -class CheckUpdatesYaourt(CheckUpdates): +class CheckUpdatesYay(CheckUpdates): def __init__(self, **config): - super(CheckUpdatesYaourt, self).__init__(**config) - # Override command and output with yaourt command - self.cmd = "yaourt -Qua".split() - self.status_cmd = "yaourt -Qua".split() - self.update_cmd = "sudo yaourt -Sya".split() + super(CheckUpdatesYay, self).__init__(**config) + # Override command and output with yay command + self.cmd = "yay -Qua".split() + self.status_cmd = "yay -Qua --color never".split() + self.update_cmd = "sudo yay".split() self.subtr = 0 def _check_updates(self): #subprocess.check_output(self.update_cmd) - res = super(CheckUpdatesYaourt, self)._check_updates() + res = super(CheckUpdatesYay, self)._check_updates() return res def button_press(self, x, y, button): @@ -253,6 +282,7 @@ class CheckUpdatesYaourt(CheckUpdates): elif button == BUTTON_MIDDLE and self.execute is not None: subprocess.Popen(self.execute, shell=True) + class KuroBatteryIcon(BatteryIcon): status_cmd = "acpi" @@ -265,6 +295,7 @@ class KuroBatteryIcon(BatteryIcon): output ) + class PulseVolumeWidget(Volume): defaults = [ @@ -535,3 +566,255 @@ class ThermalSensorWidget(ThermalSensor): command.append("-f") sensors_out = self.call_process(command) return self._format_sensors_output(sensors_out) + + +class SeparatorWidget(base._TextBox): + def __init__(self): + super(SeparatorWidget, self).__init__(text="|", width=bar.CALCULATED, fontsize=14) + + +class MediaWidget(base.InLoopPollText): + """Media Status Widget""" + + class Status: + OFFLINE = 0 + PLAYING = 1 + PAUSED = 2 + STOPPED = 3 + + orientations = base.ORIENTATION_HORIZONTAL + defaults = [ + ('off_text', '', 'The pattern for the text if no players are found.'), + ('on_text_play', ' {}', 'The pattern for the text if music is playing.'), + ('on_text_pause', ' {}', 'The pattern for the text if music is paused.'), + ('on_text_stop', ' {}', 'The pattern for the text if music is stopped.'), + ('update_interval', 1, 'The update interval.'), + ] + + player_icons = { + 'spotify': '', + 'vlc': '', + 'firefox': '', + } + + custom_player_data = { + 'firefox': { + 'showing': False, + 'title': '', + 'state': Status.STOPPED, + } + } + + def __init__(self, **config): + super(MediaWidget, self).__init__(**config) + self.add_defaults(MediaWidget.defaults) + self.surfaces = {} + + def cmd_update_custom_player(self, player_name, data): + # Update firefox player + if player_name == "firefox": + if data['playing'] and data['muted']: + self.custom_player_data['firefox']['showing'] = True + self.custom_player_data['firefox']['state'] = MediaWidget.Status.PAUSED + self.custom_player_data['firefox']['title'] = data['title'] + elif data['playing'] and not data['muted']: + self.custom_player_data['firefox']['showing'] = True + self.custom_player_data['firefox']['state'] = MediaWidget.Status.PLAYING + self.custom_player_data['firefox']['title'] = data['title'] + elif not data['playing'] and data['muted']: + self.custom_player_data['firefox']['showing'] = True + self.custom_player_data['firefox']['state'] = MediaWidget.Status.STOPPED + self.custom_player_data['firefox']['title'] = data['title'] + elif not data['playing'] and not data['muted']: + self.custom_player_data['firefox']['showing'] = False + self.custom_player_data['firefox']['state'] = MediaWidget.Status.OFFLINE + self.custom_player_data['firefox']['title'] = data['title'] + + def _get_players(self): + players = [] + + # Playerctl players + command = ["playerctl", "-l"] + result = self.call_process(command) + if result: + players.extend([x for x in result.split("\n") if x]) + + # Custom players - Firefox + if self.custom_player_data['firefox']['showing']: + players.append('firefox') + + if players: + return players + else: + return None + + def _get_info(self): + players = self._get_players() + + if not players: + return {} + else: + result = {} + + for player in players: + if player in self.custom_player_data.keys(): + # Custom player -- Firefox + if player == "firefox": + result[player] = [self.custom_player_data['firefox']['state'], self.custom_player_data['firefox']['title']] + + # Other custom players -- generic attempt with error catching + else: + try: + result[player] = [self.custom_player_data[player]['state'], + self.custom_player_data[player]['title']] + except KeyError: + pass + + else: + # PlayerCtl player + command = ["playerctl", "-p", player, "status"] + cmd_result = self.call_process(command).strip() + + text = "Unknown" + if cmd_result in ["Playing", "Paused"]: + artist = self.call_process(['playerctl', '-p', player, 'metadata', 'artist']).strip() + title = self.call_process(['playerctl', '-p', player, 'metadata', 'title']).strip() + + if artist and title: + text = "{} - {}".format(artist, title) + elif artist: + text = artist + elif title: + text = title + + if cmd_result == "Playing": + result[player] = [MediaWidget.Status.PLAYING, text] + elif cmd_result == "Paused": + result[player] = [MediaWidget.Status.PAUSED, text] + elif cmd_result == "Stopped": + result[player] = [MediaWidget.Status.STOPPED, ""] + + return result + + def _get_formatted_text(self, status): + if status[0] == MediaWidget.Status.PLAYING: + return self.on_text_play.format(status[1]) + elif status[0] == MediaWidget.Status.PAUSED: + return self.on_text_pause.format(status[1]) + elif status[0] == MediaWidget.Status.STOPPED: + return self.on_text_stop.format(status[1]) + else: + return "Unknown" + + def poll(self): + text = [] + status = self._get_info() + if not status: + return self.off_text + else: + for player in status.keys(): + icon = self.player_icons.get(player, player) + logger.warning([player, status[player]]) + text.append("{} {}".format(icon, self._get_formatted_text(status[player]))) + + return " | ".join(text) if text else self.off_text + + +class AudioVisualizerWidget(_Graph): + """Display Audio Visualization graph""" + orientations = base.ORIENTATION_HORIZONTAL + defaults = [ + ("audio_channel", "default", "Which audio channel to show"), + ] + + stream = None + + fixed_upper_bound = True + + def __init__(self, **config): + _Graph.__init__(self, **config) + self.add_defaults(AudioVisualizerWidget.defaults) + self.maxvalue = 100 + self.samples = 1024 + self.max_observed = 1 + + # initialize communication queue + self.q = Queue() + self.t = None + self.stream = None + self.tries = 0 + + def initialize_stream(self): + # initialize portaudio + p = pyaudio.PyAudio() + try: + self.stream = p.open(format=pyaudio.paInt16, channels=1, rate=44100, input=True, frames_per_buffer=self.samples) + + # initialize thread + self.t = Thread(target=self.process, args=[self, self.q]) + self.t.start() + except OSError as e: + logger.warning("Could not open audio stream: ".format(e)) + + self.tries += 1 + + @staticmethod + def process(widget: 'AudioVisualizerWidget', queue: Queue): + + item = queue.get() + + if widget.max_observed > 100: + widget.max_observed -= 100 + # Discard all available frames + avail = widget.stream.get_read_available() + while avail > 1000: + _ = widget.stream.read(avail) + logger.debug("Discarded {} frames".format(avail)) + avail = widget.stream.get_read_available() + + if avail > 100: + data = widget.stream.read(widget.samples) + numpydata = numpy.abs(numpy.fromstring(data, dtype=numpy.int16)) + + if numpy.max(numpydata) > widget.max_observed: + widget.max_observed = numpy.max(numpydata) + + numpydata = numpydata * (100 / widget.max_observed) + numpydata = AudioVisualizerWidget.window_rms(numpydata, 25) + + widget.values = list(numpydata) + print(widget.values) + else: + widget.values = [0]*1024 + + @staticmethod + def window_rms(a, window_size): + a2 = numpy.power(a, 2) + window = numpy.ones(window_size) / float(window_size) + return numpy.sqrt(numpy.convolve(a2, window, 'valid')) + + def update_graph(self): + if not self.stream and self.tries < 10: + self.initialize_stream() + + else: + if self.q.empty(): + self.q.put(True) + self.draw() + + +class KuroCurrentLayoutIcon(CurrentLayoutIcon): + def _get_layout_names(self): + names = super(KuroCurrentLayoutIcon, self)._get_layout_names() + + from kuro.utils import layouts as kuro_layouts + from libqtile.layout.base import Layout + klayouts = [ + layout_class_name.lower() + for layout_class, layout_class_name + in map(lambda x: (getattr(kuro_layouts, x), x), dir(kuro_layouts)) + if isinstance(layout_class, six.class_types) and issubclass(layout_class, Layout) + ] + names.extend(klayouts) + + return list(set(names)) diff --git a/kuro/utils/kb_backlight.py b/kuro/utils/kb_backlight.py index 35f46a9..b818ec4 100644 --- a/kuro/utils/kb_backlight.py +++ b/kuro/utils/kb_backlight.py @@ -71,13 +71,20 @@ def handle_focus_change(theme): 'middle': Color.RED, 'right': Color.WHITE, })) - elif "chromium" in wm_class[0]: + elif "Firefox" in wm_class[1]: BacklightController.reset_backlight(state=KeyboardState(values={ 'brightness': Brightness.FULL, 'left': Color.WHITE, 'middle': Color.BLUE, 'right': Color.WHITE, })) + elif "Thunderbird" in wm_class[1]: + BacklightController.reset_backlight(state=KeyboardState(values={ + 'brightness': Brightness.FULL, + 'left': Color.BLUE, + 'middle': Color.WHITE, + 'right': Color.BLUE, + })) elif "pycharm" in wm_class[1]: BacklightController.reset_backlight(state=KeyboardState(values={ 'brightness': Brightness.MEDIUM, @@ -92,6 +99,32 @@ def handle_focus_change(theme): 'middle': Color.WHITE, 'right': Color.BLUE, })) + elif "quasselclient" in wm_class[0]: + BacklightController.reset_backlight(state=KeyboardState(values={ + 'brightness': Brightness.MEDIUM, + 'left': Color.BLUE, + 'middle': Color.BLUE, + 'right': Color.BLUE, + })) + elif "remmina" in wm_class[0]: + BacklightController.reset_backlight(state=KeyboardState(values={ + 'brightness': Brightness.FULL, + 'left': Color.RED, + 'middle': Color.GREEN, + 'right': Color.BLUE, + })) + elif "spotify" in wm_class[0]: + BacklightController.reset_backlight(state=KeyboardState(values={ + 'brightness': Brightness.FULL, + 'left': Color.GREEN, + 'middle': Color.GREEN, + 'right': Color.GREEN, + })) + elif "pulseeffects" in wm_class[0]: + BacklightController.reset_backlight(state=KeyboardState(values={ + 'brightness': Brightness.FULL, + 'mode': Mode.RANDOM, + })) else: BacklightController.reset_backlight()